Повышаем удобство управления пультом ДУ в Linux (lirc->python->xdotool)

в 7:58, , рубрики: linux, lirc, python, xdotool, метки: , ,
1. Предисловие

Предприняв недавно очередную попытку немного освоить Linux (конкретней — Xubuntu 12.04), я заметил, что система меня вполне устраивает и желания плюнуть на затею и загрузить Windows не возникает. Легко нашлись аналоги привычных приложений, устройства заработали «из коробки».

Возникшие проблемы — не удалось настроить tv-тюнер и управление пультом от него через lirc работает совсем не так, как я привык в Windows. Первая проблема остаётся, вторую я решил небольшим скриптом на python'е.

2. Как я пользовался пультом в Windows

В Windows я использовал программу SlyControl в качестве WinLIRC-сервера и HIP для управления программами. То есть от программ не требуется поддерживать WinLIRC — HIP служит прослойкой, принимая команды с пульта и эмулируя нажатия клавиш (и не только, возможности гораздо шире) в приложении. Причём кнопки для каждого приложения настраиваются отдельно и работают только в случае, когда оно в фокусе (когда я настроил lirc для управления VLC и mplayer, был неприятно удивлён, что команда посылается всем запущенным приложениям, даже если они свёрнуты). Плюс можно настроить кнопки «по умолчанию», то есть, если для активного приложения не определена какая-либо кнопка, она выполняет дефолтное действие.

Также в HIP есть режим Enhanced Master Control, активировав который, можно запускать приложения, переключаться между ними, сворачивать/разворачивать/закрывать, выключать/перезагружать компьютер и выполнять другие подобные действия. Ах да, ещё время можно посмотреть, большие такие, удобные часы.

3. Пишем свой HIP

Признаюсь, альтернативу HIP'у я не стал искать. Решил, что лучше сделаю её сам, именно такой, как мне хочется. В качестве языка я выбрал Python. Вторым кандидатом был Perl — оба языка я знаю весьма и весьма поверхностно, но так как решил попробовать изучить Python, выбор пал на него. Да и проще, пожалуй.

Для эмулирования нажатий клавиш (и прочих событий X сервера) была выбрана утилита xdotool. Для начала установим её (банальный sudo apt-get install xdotool в Ubuntu). К слову, lirc тоже предоставляет возможность эмулировать клавиши с помощью irxevent.

Итак, подключаемся к сокету /dev/lircd и в бесконечном цикле читаем данные:

lircd = socket.socket(socket.AF_UNIX)
lircd.connect('/dev/lircd')
while 1:
  comm = lircd.recv(128)

lircd посылает строки вида «сканкод количество_нажатий название_кнопки название_пульта», например, «000400040000001c 00 Radio Chronos». Разбираем её регулярным выражением (нам нужны название кнопки и пульта:

comm_parsed = re.search('([0-9A-Fa-f]+?) ([0-9]+?) (.+?) (.+)', comm)
rc_key, rc_name = comm_parsed.group(3, 4)

Теперь нужно определить, какое приложение активно. Логично было бы получить имя процесса, но у меня не получилось, поэтому будем использовать имя окна. Узнать его id можно командой xdotool getactivewindow. Узнать имя окна по id — xdotool getwindowname. Удобно, что xdotool позволяет использовать цепочки команд.
Вызывать xdotool будем с помощью модуля subprocess:

active_window_name = subprocess.check_output('xdotool getactivewindow getwindowname', shell=True)

Теперь надо найти в конфиге, задано ли действие для нажатой кнопки в этом приложении. Кстати, о конфиге — он ini-подобный, настройки разделены на секции [имя_приложения], в которых хранятся пары кнопка_пульта: действие. Имя_приложения — это неизменяемая часть заголовка окна, по которой будем идентифицировать приложение (например, для «video.avi — SMPlayer» это будет «SMPlayer»). Также есть секция для действий кнопок по умолчанию [Default] и настроек скрипта [Settings]. Конфиг называется .lip-config и хранится в папке пользователя. Прочитать его элементарно:

config = ConfigParser.ConfigParser()
config.read(os.path.expanduser('~')+'/.lip-config')

Итак, ищем программу в конфиге, если нашли — кнопку

for section in config.sections()[2:] # проходимся по всем секциям конфига (c 3-ей до конца, т.е. по вcем приложениям; 1 и 2 — это Settings и Default)
  if section in active_window_name: # если название приложения (например, VLC media player) - входит в имя окна (foo.mkv - VLC media player)
    if config.has_option(section, rc_key) :# если в конфиге есть это приложение и эта кнопка пульта - 
      subprocess.call('xdotool '+config.get(section, rc_key), shell=True) # выполняем действие
    break

Если не нашли — попробуем поискать кнопку в секции Default (конечно, если дефолтные кнопки включены в конфиге опцией use_default_keys:1). Код аналогичный:

if not key_found and use_default_keys and config.has_option('Default', rc_key):
  subprocess.call('xdotool '+config.get('Default', rc_key), shell=True)

Собственно, и всё. Скрипт получился намного проще, чем я думал. Это заслуга xdotool, которая умеет почти всё, что нужно, «из коробки». Например, чтобы «нажать» Ctrl+a, надо выполнить xdotool key ctrl+a, кликнуть правой кнопкой мыши — xdotool click 3. Другие примеры выполняемых ею действий можно посмотреть в моём .lip-config

4. Некоторые замечания, недостатки, планы

1. Нерационально использовать xdotool exec для запуска приложения. Можно запускать напрямую.

2. Некоторые приложения в разных режимах имеют разные имена окна. Например, Tano — в оконном режиме «{file} — Tano», в полноэкранном — «tano».

3. Простая проверка на вхождение подстроки для поиска приложения в конфиге может давать ложный результат. Например, при проигрывании файла «SMPlayer_example.avi» имя окна VLC будет «SMPlayer_example.avi — VLC media player». Если в конфиге секция [SMPlayer] будет раньше [VLC media player], обрабатываться будет она.

Возможное решение этого и предыдущего пункта — ввести в секции каждого приложения опцию windowname: имя_окна, в которой расширенно указывать возможные варианты названия окна (в виде регулярных выражений, например):
windowname:tano|Tano — имя может включать «tano» или «Tano» (решаем проблему 1)
windowname:SMPlayer$ — имя должно заканчиваться на «SMPlayer» (решаем проблему 2)
Названия секций при этом более не используются для поиска и служат лишь для разделения и наглядности (можно писать в них любой текст).

4. Можно реализовать расширенное управление мышью (вместо прописывания в конфиге простого mousemove_relative X Y) с ускорением в зависимости от продолжительности нажатия кнопки (её можно определить по строке, выдаваемой lirc'ом):

  • добавляем обработку конструкция вида {кнопка}:lipmouse up (down, left, up-left и т.п.)
  • добавляем в [Settings] настройки: начальный шаг курсора (в пикселях), шаг при включении ускорения, задержка перед включением ускорения.
  • при нажатии соответствующей клавиши выполняем xdotool mousemove_relative X Y, где X и Y будут зависеть от направления, шага и ускорения.

5. Не помешал бы режим, аналогичный Enhanced HIP Master Control. Хотя, его легко реализовать внешним лаунчером, через конфиг, без каких-либо изменений текущего скрипта (но лучше всё же вынести отдельной опцией).

Примечание 1: Не знаю, где лучше хранить код. Так что залил на pastebin: скрипт, конфиг.
Примечание 2: lip — по аналогии с HIP, где l — linux. Или lirc. Должно же быть название. Даже у такой поделки.

Автор: un_def

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js