Переключение раскладки в X11 командой из скрипта

Опубликовано 23.02.2024 в OpenBSD

Вот настроили мы по-старинке переключение раскладок в X11, например, добавив куда-нибудь в ~/.xsession что-то по своему вкусу, у меня так: setxkbmap -layout us,ru -option grp:caps_toggle,compose=ralt. Ну или даже, чтоб олдскулы свело, прописали это же самое в формате конфига для иксов, как деды воевали^W конфигурировали.

И есть у нас простое и незатейливое требование: из скрипта командою переключать в некоторых случаях раскладку на заведомо нам нужную. Ну, например, так: в моем привычном до боли тайловом менеджере окон i3 на первом рабочем столе (workspace) традиционно висят открытыми пара терминалов, привык я так. И удобно мне весьма при переключении на этот самый первый workspace и раскладку клавиатуры переключать на язык потенциального^W состоявшегося противника, ну, чтобы не начать вводить команды кириллицей. Чего проще, забиндить в ~/.config/i3/config на соответствующую комбинацию клавиш не только переход на первый workspace, но и переключение на нужную раскладку? Или вот, ещё нагляднее: мы блокируем экран, скажем, при бездействии 10 минут и для разблокировки его требуем ввода пароля. Было бы весьма удобно перед блокировкой экрана и клавиатуру сразу переключить на ту раскладку, на которой предстоит потом вводить пароль.

Взгуглив, вы легко найдете решение, предлагаемое на каждом углу: а используйте-ка что-то вроде setxkbmap layout en и будет вам счастье. Да, это сработает, но есть нюанс - таким образом, вы включите единственную раскладку (layout), а все те настройки и способ переключения, что описаны в первом абзаце, просто перестанут работать. И если ко всем прочим радостям вы пользуетесь каким-то индикатором раскладки, тот вовсе станет показывать нерелевантную чушь.

Заметим, однако, что в иксах всё нам нужное уже заложено из коробки: есть чудесные клавиши с keysym = ISO_First_Group для переключения на первую раскладку, ISO_Last_Group для переключения, соответственно, на последнюю, и ISO_Next_Group для переключения в цикле. И всё, что нам нужно - это эмулировать нажатие ISO_First_Group, мы достигнем искомого. А что у нас есть такого старого, доброго и привычного для эмуляции всякого нажатия и движения в иксах? Правильно, xdotool.

И тут мы встречаем первый в нашей истории жирный такой нюанс: в xdotool есть давно известный... то ли баг, то ли так и было задумано. В общем, xdotool перед эмуляцией клавиш программно сбрасывает все "модификаторы ввода", к коим относят и всё связанное с раскладкой. Так что эмулировать ISO_First_Group легче лёгкого, да только вот эффект это даст ровно нулевой. И исправлять это поведение никто, кажется, не планирует.

Ладно, не огорчаемся, ловим рабочий способ. Есть ведь другой эмулятор, работоспособный: виртуальная экранная клавиатура xvkbd, которую мы поставим (посредством doas pkg_add xvkbd) и добьемся искомого поведения вызовом: xvkbd -text '\[ISO_Next_Group]' (будучи вызванной с опцией -text эта программа никаких экранных клавиатур не рисует, а просто эмулирует ввод переданного текста). Собственно, всё, решение найдено, на этом повествование можно было бы заканчивать. Вписывайте в ~/.config/i3/config строчку вроде bindsym $mod+1 workspace number $ws1; exec "xvkbd -text '\[ISO_First_Group]'" и вуаля. Ну для полного вуаля можно еще добавить оное к комбинации клавиш для запуска программ, у меня вот так:

bindcode $mod+40 exec "xvkbd -text '\[ISO_First_Group]'; rofi -show combi -modes combi -combi-modes 'drun,run,window' -show-icons -icon-theme 'Adwaita' -drun-match-fields 'name,exec' -font 'FantasqueSansM Nerd Font Regular 18'"

И в скрипт для блокировки экрана тоже можно вписать xvkbd -text '\[ISO_First_Group]'. Всё.

Но как быть пуристам и минималистам, которые не хотят тащить в систему пусть крохотную, но все-таки избыточную программу? А давайте напишем сами мини-переключалку, которая будет делать ровно то, что нам надо и ничего больше, использовать исключительно X11 API и не иметь никаких зависимостей, окромя самих иксов? Ловите:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <X11/XKBlib.h>

void printHelpMessage() {
   printf("syntax : \n  --help - this message \n");
   printf("  --set N - set layout N, 0 = first, 1 = second... \n\n");
   return;
}

void setKbdLayout(int layoutId) {

/* Вызовом функции XOpenDisplay подключаемся к Х-серверу.
   В качестве аргумента displayName передаем NULL - это оз-
   начает, что будет выбран сервер по умолчанию, данные о 
   котором хранятся в переменной окружения DISPLAY */

Display* _display;
char* displayName = "";
_display = XOpenDisplay(displayName);

/* Используя XksUseCoreKeyboard мы указываем основную кла-
   виатуру без необходимости определения ее идентификатора */

int _deviceId = XkbUseCoreKbd;

/* Устанавливаем заданную раскладку и закрываем соединение 
   с X-сервером */

XkbLockGroup(_display, _deviceId, layoutId);
XCloseDisplay(_display);
return;
}

int main(int argc, char **argv) {

/* Займемся обработкой параметров командной строки. */

if (argc <= 1) {
   printHelpMessage();
   } 
else if (!strcmp(argv[1], "--set")) {
   if(argc <= 2) {
             printf("'set' operation requires a parameter.\n");
         } else {
         int res = atoi(argv[2]);         
         setKbdLayout(res);
         }
}
else if (!strcmp(argv[1], "--help")) {
   printHelpMessage();
}
else {
   printf("\nUnknown parameter!\n");
   printHelpMessage();
}

return 0;

}

Сохраним этот код в файлик с названием вроде swxkb.c и скомпилируем в OpenBSD так: clang -o swxkb swxkb.c -L/usr/X11R6/lib -lX11. Впрочем, с тем же успехом оно скомпилируется и в любом Linux-дистрибутиве при помощи gcc (разве что, может потребоваться установка пакета libx11-dev или чего-то наподобие, в OpenBSD оно идет из коробки с иксами). По использованию - там в коде хелп виднеется. Куда вызов вписать - выше по тексту указано.

Ну и под занавес. Если вдруг потребуется скриптом раскладку не только устанавливать, но и узнавать текущуюю, то сделать это можно вот так:

#!/bin/sh 

COMMAND=$(xset -q | grep LED | awk '{ print $10 }') 

case "$COMMAND" in 
"00000000"|"00000001") LAYOUT="en" ;; 
"00000010"|"00000011") LAYOUT="ru" ;; 
*) LAYOUT="??" ;; 
esac 

echo $LAYOUT 

Наслаждайтесь.

P.S. Ностальгия... лет, наверное, не менее 12-ти назад я уже решал подобную задачу - настраивая под себя i3 (или даже вовсе wmii) под Arch Linux на Asus EeePC 901. Тогда я нашел готовую реализацию, что-то минималистичное в AUR. До чего же приятно иногда окунуться в прошлое!