Я хотел бы подключиться, перехватить и сгенерировать события клавиатуры (сделать/разбить) в Linux до того, как они будут доставлены в какое-либо приложение. Точнее, я хочу обнаруживать шаблоны в ключевом потоке событий и иметь возможность отбрасывать/вставлять события в поток в зависимости от обнаруженных шаблонов.
Я видел некоторые связанные вопросы по SO, но:
- либо они имеют дело только с тем, как получить ключевые события (кейлоггеры и т. д.), а не с тем, как манипулировать их распространением (они только слушают, но не перехватывают/генерируют).
- или они используют пассивные/активные захваты в X (подробнее об этом ниже).
Маленький DSL
Я объясню проблему ниже, но чтобы сделать ее немного более компактной и понятной, сначала небольшое определение DSL.
A_
: сделать (нажать) клавишу AA^
: для прерывания (отпускания) клавиши AA^->[C_,C^,U_,U^]
: вA^
отправьте комбинацию make/break для C, а затем для U дальше по цепочке обработки (и, наконец, в приложение). Если->
нет, то ничего не отправляется (но внутреннее состояние может быть изменено для обнаружения последующих событий).$X
: выполнить произвольное действие. Это может быть отправка какой-либо настраиваемой последовательности событий клавиш (может быть, что-то вродеC-x C-s
для emacs) или выполнение функции. Если бы я мог отправлять только ключевые события, этого было бы достаточно, так как я мог бы затем обработать их в оконном менеджере в зависимости от того, какое приложение активно.
описание проблемы
Итак, с этой нотацией вот шаблоны, которые я хочу обнаружить, и какие события я хочу передать по цепочке обработки.
A_, A^->[A_,A^]
: поясн. см. выше, обратите внимание, что отправка происходитA^
.A_, B_, A^->[A_,A^], B^->[B_,B^]
: в основном то же, что и 1. но перекрывающиеся события не меняют поток обработки.A_, B_, B^->[$X], A^
: если произошло полное замыкание/размыкание ключа (B) при удерживании другого ключа (A), выполняется X (см. выше), а разрыв A отбрасывается.
(в принципе, это простой конечный автомат, реализованный для ключевых событий, который может генерировать (несколько) ключевых событий в качестве вывода).
Дополнительные замечания
- Решение должно работать на скорости набора текста.
- Потребители измененного потока ключевых событий работают под X в Linux (консоли, браузеры, редакторы и т. д.).
- На обработку влияют только события клавиатуры (без мыши и т. д.)
- Сопоставление может происходить по символам клавиш (немного проще) или по кодам клавиш (немного сложнее). В последнем случае мне просто нужно будет прочитать сопоставление, чтобы перевести код в keysym.
- Если возможно, я бы предпочел решение, которое работает как с USB-клавиатурами, так и внутри виртуальной машины (могут быть проблемы при работе на уровне драйвера, другие уровни должны быть в порядке).
- Я довольно открыто говорю о языке реализации.
Возможные решения и вопросы
Итак, основной вопрос заключается в том, как это реализовать.
Я реализовал решение в оконном менеджере, используя пассивные захваты (XGrabKey
) и XSendEvent
. К сожалению, в этом случае пассивные захваты не работают, так как они неправильно захватывают B^
во втором шаблоне выше. Причина в том, что преобразованный захват заканчивается на A^
и не продолжается до B^
. Новый захват преобразуется в захват B, если он все еще удерживается, но только через ~1 секунду. В противном случае приложению отправляется обычный B^
. Это можно проверить с помощью xev
.
Я мог бы преобразовать свою реализацию, чтобы использовать активный захват (XGrabKeyboard
), но я не уверен в эффекте на другие приложения, если оконный менеджер все время активно захватывает клавиатуру. Документация X описывает активные захваты как навязчивые и предназначенные для краткосрочного использования. Если у кого-то есть опыт с этим и нет серьезных недостатков с долгосрочными активными захватами, то я бы рассмотрел это как решение.
Я готов рассмотреть другие уровни обработки ключевых событий помимо оконных менеджеров (которые работают как X-клиенты). Драйверы клавиатуры или сопоставления возможны, если я могу решить с ними вышеуказанную проблему. Это также означает, что решение не обязательно должно быть отдельным приложением. Меня вполне устраивает драйвер или модуль ядра, делающий это за меня. Имейте в виду, однако, что я никогда не занимался программированием ядра или драйвера, поэтому я был бы признателен за хорошие ресурсы.
Спасибо за любые подсказки!