09 июня 2012

Stumpwm модуль сохранения раскладок клавиатуры между окнами

Введение

Долгое время переключаясь между фаерфоксом, скайпом и емаксом испытывал неудобство в постояной необходимости переключать раскладку клавиатуры, так как состояние раскладки было глобальным для всех окон. С задачей сохранения раскладки для каждого окна прекрасно справляется программа perWindowLayoutD. Однако это целый процесс со своим циклом сообщений, съедающий пусть небольшие но ресурсы компьютера. То ли дело подключиться слаймом к stumpwm, и добавить нужный функционал. Посмотрев на исходный код вышеназванной программы, я сделал вывод о том, что для работы с раскладкой клавиатуры требуется всего пару функций: XkbGetState и XkbLockGroup, которые находятся в библиотеке Xkblib, которая входит в пакет libx11. XkbGetState возвращает текущую раскладку, XkbLockGroup устанавливает указанную. К ним-то и нужно получить доступ из stumpwm. Но сначала обо всём по порядку.

Графическая система X

Из википедии X Window System (Иксы)

X Window System использует клиент-серверную модель: X-сервер обменивается сообщениями с различными клиентскими программами. Сервер принимает запросы на вывод графики (окон) и отправляет обратно пользовательский ввод (от клавиатуры, мыши или сенсорного экрана). X-сервер может быть:

  • системной программой, контролирующей вывод видео на персональном компьютере;
  • приложением, отображающим графику в окно какой-то другой дисплейной системы;
  • выделенным компонентом аппаратного обеспечения.

Статья об Иксах http://rus-linux.net/papers/xwin/X-Window.html

http://rus-linux.net/papers/xwin/X-Window_html_m1cb95d2e.png

Архитектура системы X Window

В данном случае сервер является механизмом отрисовки окон и передаёт события от устройств ввода. А клиент, подключившись к серверу, обрабатывает присланные события и указывает что и как нарисовать.

Любое приложение, которое хочет предоставить графический интерфейс, является клиентом икс сервера.

Протокол общения между клиентом и сервером состоит из следующих пакетов:

Запрос
клиент требует нарисовать что-либо в окне или запрашивает у сервера информацию;
Ответ
сервер отвечает на запрос;
Событие
сервер сообщает клиенту о событии (например, о нажатии клавиши пользователем);
Ошибка
сервер сообщает об ошибке.

X-сервер состоит из набора расширений, каждое из которых реализует определённые функции: от прорисовки геометрических примитивов до ускорения обработки и вывода на экран трёхмерной графики с использованием возможностей видеоаппаратуры. Почти каждый из этих модулей можно отключить или настроить в конфигурационном файле.

Описание базового протокола и расширений расположены по адресу http://www.x.org/releases/X11R7.6/doc/

Стандартный протокол: http://www.x.org/releases/X11R7.6/doc/xproto/x11protocol.pdf

Помимо стандартного протокола существует расширение XKeyboard, которое позволяет более гибко работать с клавиатурой. Оно-то мне и необходимо для того, чтобы сохранять и восстанавливать раскладку.

Протокол XKeyboard: www.x.org/docs/XKB/XKBproto.pdf

Данное расширение и реализует вышеназванные функции: XkbGetState и XkbLockGroup.

Сишники и не только они могут уже готовые клиентские библиотеки: низкоуровневые libx11 или xcb, высокоуровневые gtk, qt (ну эти так за компанию привёл).

Коммон лисперы (помимо биндингов к вышеназванным библиотекам) могут использовать написаную только на лиспе библиотеку clx, которая, мне кажется, берёт начало ещё в genere. С одной стороны clx реализует только базовый протокол. С другой стороны она содержит API для создания расширений.

clx также используется менеджером окон Stumpwm. Хотя он у меня уже больше похож на окружение, чем на просто менеджер.

Итак, после беглого просмотра исходников xkblib и clx я решил, что напишу:

  • маленькую часть расширения XKeyboard для clx
  • модуль для Stumpwm, который будет сохранять раскладки между окнами

Далее будут идти подробности моей работы, но пользователи Stumpwm могут сразу перейти к разделу "Установка".

CLX

Работа с библиотекой CLX проще-простого:

  • открыть дисплей
  • выполнить рисование
  • циклично обработать события
  • закрыть дисплей

Документация: http://www.stud.uni-karlsruhe.de/~unk6/clxman/contents.html

Расширение XKeyboard для clx

В целом написание расширения для clx состоит из следующих определений:

расширение
макрос define-extension
ошибки
макросы define-condition и define-error
типы данных
макросы deftype и define-accessor
необходимые константы
defconstant
события
макросы define-event и declare-event
функции для запросов/ответов
макросы with-buffer-request, with-buffer-request-and-reply

Создаём новый проект с помощью quickproject.

В зависимостях только библиотека clx.

В папке clx-xkeyboard автоматически будут сгенерированы три файла:

xkeyboard.asd
содержит asdf систему для загрузки данной библиотеки
package.lisp
содержит определение коммон лиспового пакета
xkeyboard.lisp
содержит основные наши функции
README.txt
здесь всё понятно

Необходимо отметить, что дальнейшая работа будет происходить в clx-овом пакете :xlib, поэтому файл package.lisp, можно очистить.

Для простоты в некоторые моменты можно пользоваться xml спецификацией протокола XKeyboard: http://cgit.freedesktop.org/xcb/proto/plain/src/xkb.xml

Приступим к непосредственно рутине. В файле xkeyboard.lisp добавим:

Последняя строка добавляет простой ключевой символ в глобальную переменную features. Таким образом мы рассказываем другим лисповым системам о своём существовании, мало ли чья-нибудь программа захочет воспользоватся XKeyboard расширением.

Определяем расширение, важно написать имя заглавными буквами, также как в спецификации:

В данном виде мы указываем, что для ошибок при работе с XKeyboard использовать тип условия xkeyboard-error.

Далее определяем условие и указываем это для clx:

Определяем новые типы, сначала для коммон лиспа, затем для clx:

Вспомогательные макросы для определения синонимов типов:

Указываем для clx синонимы типов:

Каждый пакет-запрос от клиента к серверу содержит код операции или, проще говоря, номер функции. Коды операций для базового протокола прописаны жестко. Коды операций для расширений также жёстко прописаны, но при работе должны увеличиваться на величину выдаваемую сервером для каждого расширения отдельно.

Создаём список кодов для запросов:

Макрос, который будет возвращать величину приращения для номеров функций расширения.

Задаём версию расширения:

Для того, чтобы клиент мог вызывать функции расширения, он сначала для своего сеанса должен его включить. Это производится запросом UseExtension.

Его спецификация выглядит так:

SizeType or ValueName
1??opcode
10xkb-opcode
22request-length
2CARD16wantedMajor
2CARD16wantedMinor
11Reply
1BOOLsupported
2CARD16sequence number
40reply length
21serverMajor
20serverMinor
20unused

Теперь подключение к иксам может выглядеть так:

Основные два макроса для взаимодействия с сервером:

  • для отправки запроса и получения ответа with-buffer-request-and-reply
  • для только отправки запроса with-buffer-request

Macros with-buffer-request-and-reply
         (buffer opcode reply-size &key sizes multiple-reply inline)
                       type-args &body reply-forms

В аргумент buffer передаётся открытый с помощью open-display дисплей.

opcode
является приращением для номеров функций для используемого расширения.
reply-size
указывает на размер принимаемого ответа по идее в битах.
sizes
содержит размеры отправляемых полей ….
type-args
содержит формы по отправке данных запроса; может также содержать любой исполняемый код; данные будут отосланы в той последовательности, в которой вызывались формы
reply-forms
содержит формы по получению данных ответа; может также содержать любой код; при получении данных нужно указывать смещение относительно начала пакета

Итак теперь функция XkbGetState. Вот как она выглядит для пользователя Xkblib http://www.x.org/releases/X11R7.7/doc/libX11/XKB/xkblib.html#Determining_Keyboard_State

typedef struct {
      unsigned char            group;                /* effective group index */
      unsigned char            base_group;           /* base group index */
      unsigned char            latched_group;        /* latched group index */
      unsigned char            locked_group;         /* locked group index */
      unsigned char            mods;                 /* effective modifiers */
      unsigned char            base_mods;            /* base modifiers */
      unsigned char            latched_mods;         /* latched modifiers */
      unsigned char            locked_mods;          /* locked modifiers */
      unsigned char            compat_state;         /* effective group => modifiers */
      unsigned char            grab_mods;            /* modifiers used for grabs */
      unsigned char            compat_grab_mods;     /* mods used for compatibility mode grabs */
      unsigned char            lookup_mods;          /* modifiers used to lookup symbols */
      unsigned char            compat_lookup_mods;   /* mods used for compatibility lookup */
      unsigned short            ptr_buttons;         /* 1 bit => corresponding pointer btn is down */
} 
XkbStateRec
*XkbStatePtr;

Status XkbGetState ( display , device_spec , state_return )
Display * display ; /* connection to the X server */
unsigned int device_spec ; /* device ID, or XkbUseCoreKbd */
XkbStatePtr state_return ; /* backfilled with Xkb state */ 

А вот она же но в спецификации протокола:

SizeType or ValueName
1??opcode
14xkb-opcode
22request-length
2KB_DEVICESPECdeviceSpec
2unused
11Reply
1CARD8deviceID
2CARD16sequence number
40length
1SETofKEYMASKmods
1SETofKEYMASKbaseMods
1SETofKEYMASKlatchedMods
1SETofKEYMASKlockedMods
1KP_GROUPgroup
1KP_GROUPlockedGroup
2INT16baseGroup
2INT16latchedGroup
1SETofKEYMASKcompatState
1SETofKEYMASKgrabMods
1SETofKEYMASKcompatGrabMods
1SETofKEYMASKlookupMods
1SETofKEYMASKcompatLookupMods
1unused
2SETofBUTMASKptrBtnState
6unused

Начнём со структуры, которая будет возвращаться пользователю. Тип полей структуры взят из спецификации протокола.

Теперь функция:

Теперь определим функцию XkbLockGroup. Для пользователя она выглядит так: http://www.x.org/releases/X11R7.7/doc/libX11/XKB/xkblib.html#Changing_Groups

Bool XkbLockGroup ( display, device_spec, group )
Display * display ; /* connection to the X server */
unsigned int device_spec ; /* device ID, or XkbUseCoreKbd */
unsigned int group ; /* index of the keysym group to lock */ 

Внутри себя она содержит вызов XkbLatchLockState, которая описана так:

SizeType or ValueName
1??opcode
15xkb-opcode
24request-length
2KB_DEVICESPECdeviceSpec
1SETofKEYMASKaffectModLocks
1SETofKEYMASKmodLocks
1BOOLlockGroup
1KB_GROUPgroupLock
1SETofKEYMASKaffectModLatches
1SETofKEYMASKmodLatches
1unused
1BOOLlatchGroup
2INT16groupLatch

Функция не трубет ответа от сервера, поэтому воспользуемся макросом with-buffer-request

Теперь определим lock-group

Ну что же, как я уже и говорил текущую раскладку можно получить из поля locked-group структуры device-state, а установить с помощью функции lock-group.

Поэтому теперь пришло время патчить Stumpwm.

Модуль для Stumpwm

Документация Stumpwm: http://stumpwm.org/manual/stumpwm.html#NOD1

Я разместил исходный код в папке clx-xkeyboard/test/stumperwindowlayout.lisp

Проверка загрузки расширения, переключение в пакет stumpwm:

Функция для получения текущей раскладки клавиатуры:

Теперь вообще элементарные вещи остались. Определим функцию-ловушку переключения фокуса между окнами:

В stumpwm:*display* хранится текущий дисплей, для которого и выполняется задача управления окнами.

Аргументы window и previos-window содержат соответственно окна получившее и отдавшее фокус. Структура окна определена в stumpwm, нам понадобиться её поле с xlib'овской структурой — (window-xwin previous-window). У каждого окна есть свой список свойств: добавим туда свойство :keyboard-layout.

Сохранение раскладки для предыдущего окна производится формой: (setf (getf (xlib:window-plist (window-xwin previous-window)) :keyboard-layout) current-layout)

Установка раскладки для окна, получившего фокус, производится формой: (xlib:lock-group *display* :group window-layout)

Следует отметить, что при переключении между рабочими столами, фокус предыдущего окна не теряется и параметр previous-window будет равен nil. Необходимо проигнорировать эту ситуацию и отловить событие переключения между рабочими столами:

Теперь определим команды включения и выключения режима запоминания раскладки. Это делается с помощью макроса stumpwm:defcommand.

Установка

Необходимо скопировать репозитарий

git clone http://github.com/filonenko-mikhail/clx-xkeyboard.git --depth 1

А затем в ~/.stumpwmrc добавить строки:

5 комментариев:

  1. Вот бы всё это ещё и пропихнуть в апстрим того и другого... :-)

    ОтветитьУдалить
  2. Вообще по-лисповски было бы целиком переделать clx чтобы он генерил всё из xml-xcb описания протокола. На лоре кто-то уже сделал такое на elisp, осталось на CL сделать.

    ОтветитьУдалить
  3. На полноценное расширение для XKeyboard нужно время. Сунусь в quicklisp как и clx-xembed, который позволяет в stumpwm сделать трей. Таким образом установка будет в один клик.

    xml-xcb показался мне каким-то неполноценным. Я имею ввиду в сравнении с описанием протокола в пдфках.

    ОтветитьУдалить
  4. "Хотя он у меня уже больше похож на окружение, чем на просто менеджер"
    А могли бы поделится?

    ОтветитьУдалить
    Ответы
    1. Я забросил stumpwm. Да и окружение вещь очень персональная. Могу лишь предложить посмотреть разные чужие конфиги.

      Удалить