В этой статье мы рассмотрим:
- почему вообще может потребоваться ремаппинг;
- как ремаппят другие;
- как раскладка реализована у меня;
- как сделать собственную раскладку’.
Кроме обычного ремаппинга, мы также коснёмся темы создания слоёв, их индикации, а также рассмотрим создание аккордов (chords). Всё это на уровне системы.
Посмотрим на стандартную раскладку клавиш
Раскладка QWERTY далеко не всегда наиболее эффективная и удобная. Это можно показать на примере 80% клавиатуры. Не будем смотреть на саму QWERTY-раскладку, т.к. на этот счёт есть отдельные сравнения именно расположения буквенных клавиш с тем же Dvorak’ом или Colemak’ом (1, 2, 3). Обратим внимание именно на контрольные клавиши. Посмотрим на левую половину клавиатуры:
- Бесполезный для многих Caps Lock, на месте которого могла находиться более полезная клавиша. Тем более, что место капслока довольно удобное для нажатия левой рукой.
- Control или Ctrl является одной из часто используемых клавиш, но находится он в крайне труднодоступном для левой руки месте, что может вызывать боли в руке.
- Клавиша Win почти не играет никакой значимой роли в большинстве DE и WM, но находится на удобном для нажатия левой рукой месте, на котором могла располагаться более полезная клавиша.
- Пробел можно было бы безболезненно уменьшить на одну клавишу с двух сторон. Бесполезная трата свободного места.
- В некоторых случаях Esc используется чаще, чем Tab. Но на клавиатурах он обычно расположен очень далеко от домашнего ряда (home row).
- Скорее субъективный момент, но, в зависимости от размера руки, дотягиваться до цифровых клавиш может быть сложно.
На правой половине клавиатуры важных управляющих клавиш меньше, но всё же можно отметить пару вещей:
- Клавиши-стрелки находятся очень далеко от домашнего ряда, из-за чего приходится отрывать руки от него при нажатии на них.
- Бэкспейс может для кого-то быть труднодоступным.
Как с этим разбираются другие
Некоторые люди на капслок назначают хоткей переключения языков. Другие меняют местами капслок и контрол, либо просто помещают контрол на место капса. Всё это делается через инструментарий X11. Но всё же подобное — даже не половина решения.
Есть также попытка решения проблемы через саму клавиатуру. Например, клавиатуры Leopold поставляются с обычной раскладкой, но у них также есть переключатели, позволяющие перемещать некоторые клавиши местами. Уже лучше чем первый пример по полноте, но всё ещё недостаточно.
QMK/ZMK-клавиатуры, позволяющие менять назначение любой клавиши на уровне клавиатуры благодаря прошивке. Прямых недостатков у этого варианта нет, если работа ведётся лишь на одной машине. К косвенным недостаткам можно отнести то, что таких клавиатур не так много и стоят они в среднем дороже. И идёт привязка к конкретной клавиатуре. Т.е. если мы работаем на несколько машин, то это начинает хорошо проявляться, особенно, если одна из машин это ноутбук, у которого будет стандартная клавиатура.
Есть также демон для ремаппинга клавиш keyd, который также умеет в слои. Пользоваться им не приходилось, но есть вероятность меньшей гибкости, чем мы можем получить теоретически, если будем работать на более низком уровне.
Более низкого уровня мы коснёмся дальше.
Как у меня работает раскладка
На самом деле, раскладка — это не что-то абсолютное и удобство использования клавиш зависит от конкретной клавиатуры. Для примера возьму клавиатуру ноутбука ThinkPad X220.
По расположению. Ctrl находится на месте Windows-клавиши. Эскейп на месте таба и наоборот. На месте капса клавиша XF86MyComputer — рандомная клавиша для назначения управляющей в StumpWM (что-то наподобии M-x для Emacs’а), но про пользовательский опыт этого оконного менеджера можно делать отдельную статью.
Дальше можно пойти по слоям. На старом месте Ctrl теперь включается слой, которые меняет ESDF клавиши на стрелки. По Shift+пробел включается нампад-слой, котрый меняет QWER, ASDF, ZXCV клавиши на клавиши цифрового ряда.
Перемещённый Ctrl работает как аккорд, т.е. для отправки хоткея можно его не удерживать, а просто нажать, отпустить и нажать нужную конечную клавишу — в систему отправится полноценный Ctrl-хоткей.
Переключение на слой сигналится ThinkLight’ом (лампочка над монитором): если произошло переключение, он включается, если обратно, то выключается.
Выход из слоя идёт по нажатию эскейпа.
Также есть таймер, который можно настроить на автоматический выход из слоя по истечению определённого времени.
Как это реализовано софтварно
Упоминалось, что обычные демоны-переключатели не подходят из-за возможной меньшей гибкости, т.е. нам нужен более низкий уровень. Под более низким уровнем имеется в виду чтение evdev-событий, отлов нужных и отправка своих через uinput. У питона есть готовые библиотеки, позволяющие нам заняться реализацией поставленной задачи.
Также у нас есть готовый скелет, подготовленный пользователем t184256: https://gist.github.com/t184256/f4994037a2a204774ef3b9a2b38736dc
В целом, там же объяснена база ремаппинга на питоне и поиска названий нужных клавиш через evtest.
Но стоит дать несколько специфичных уточнений.
Аккорды реализованы через глобальные переменные и проверку их значений. Т.е. перед циклом мы можем прописать объявление переменной:
pressed_ctrl = False
Дальше в цикле нужно включать/выключать аккорд по нажатию определённой клавиши:
if ev.code == evdev.ecodes.KEY_LEFTMETA and ev.value == 1:
if pressed_ctrl == False:
pressed_ctrl = True
else:
pressed_ctrl = False
И в цикле проверять то, включён ли аккорд:
if (pressed_ctrl and ev.value == 1 and ev.code != evdev.ecodes.KEY_LEFTMETA):
ui.write(evdev.ecodes.EV_KEY, evdev.ecodes.KEY_LEFTCTRL, 1)
Если включён, то вместе с изначальной клавишей мы отправляем событие нажатия Ctrl.
После отправки закрываем аккорд:
if (pressed_ctrl and ev.value == 0 and ev.code != evdev.ecodes.KEY_LEFTMETA):
ui.write(evdev.ecodes.EV_KEY, evdev.ecodes.KEY_LEFTCTRL, 0)
pressed_ctrl = False
Переход на слой можно рассмотреть через пример нампад-слоя.
Перед циклом добавляем глобальные переменные:
pressed_shift = False
spc_layout = False
Нампад-слой чуть более замороченный, т.к. он включается через хоткей шифт+пробел, поэтому добавляем также переменную шифта.
В цикле смотрим, удерживается ли шифт:
if ev.code == evdev.ecodes.KEY_LEFTSHIFT:
if (ev.value > 0):
pressed_shift = True
else:
pressed_shift = False
И в цикле включаем слой, если нажимается пробел при нажатом шифте:
elif ev.code == evdev.ecodes.KEY_SPACE and pressed_shift:
if (ev.value > 0):
if spc_layout == True:
spc_layout = False
else:
spc_layout = True
pressed_shift = False
meta_layout = False
При включении слоя мы выключаем глобальную переменную шифта и выключаем остальные слои.
Также у меня для каждого слоя отдельный словарь, который соотносит изначальную клавишу с целевой, по аналогии с REMAP_TABLE
. И по этому словарю в цикле идёт проверка клавиши:
elif (spc_layout and ev.code in SPACE_KEYS):
ui.write(evdev.ecodes.EV_KEY, SPACE_KEYS[ev.code], ev.value)
Также можно рассмотреть индикацию перехода на слой. В моём случае она происходит через ThinkLight (включается/выключается лампочка). Можно написать такую функцию:
def send_layout_change_signal(signal):
f = open("/sys/class/leds/tpacpi::thinklight/brightness", "w")
if signal == "on":
f.write("1")
else:
f.write("0")
f.close()
В файл /sys/class/leds/tpacpi::thinklight/brightness
отправляется значение 1
или 0
, в зависимости от сигнала.
И таким образом мы можем дополнить включение/выключение слоя:
elif ev.code == evdev.ecodes.KEY_SPACE and pressed_shift:
if (ev.value > 0):
if spc_layout == True:
spc_layout = False
send_layout_change_signal("off")
else:
spc_layout = True
pressed_shift = False
meta_layout = False
send_layout_change_signal("on")
Но это если мы имеем дело со ThinkPad’ом. На десктопном ПК мне не удалось придумать индикацию переключения слоя, чтобы оно не зависело от Wayland’а/X11/терминала. Разве что через умные лампочки.
Также если нам не нужны постоянно включённые слои, можно сделать выключение слоя через определённое время. Для этого перед циклом можем определить глобальную переменную, определяющую время перехода на нампад-слой:
space_key_timestamp = datetime.datetime.now()
И переменную, определяющую таймаут в секундах, при истечении которого слой будет отключён:
space_key_timeout = 3
И при включении того же нампад-слоя вводим в переменную текущее время:
elif ev.code == evdev.ecodes.KEY_SPACE and pressed_shift:
if (ev.value > 0):
if spc_layout == True:
spc_layout = False
send_layout_change_signal("off")
else:
spc_layout = True
pressed_shift = False
meta_layout = False
send_layout_change_signal("on")
space_key_timestamp = datetime.datetime.now()
А при вводе клавиши с нампад-слоя мы теперь можем проверять, не прошло ли время слоя, вычитая от текущего времени время перехода на слой:
elif (spc_layout and ev.code in SPACE_KEYS):
time_difference = datetime.datetime.now() - space_key_timestamp
# if time difference between now and entering space layout is less needed timeout
if (time_difference.total_seconds() < space_key_timeout):
# make space hotkey
ui.write(evdev.ecodes.EV_KEY, SPACE_KEYS[ev.code], ev.value)
# update timer
space_key_timestamp = datetime.datetime.now()
send_layout_change_signal("on")
else:
# else disable space layout and send default key
ui.write(ev.type, ev.code, ev.value)
spc_layout = False
send_layout_change_signal("off")
По итогу
По итогу мы получаем очень большой по возможностям инструмент, который позволяет реализовать очень много идей, на гораздо меньшем количестве клавиш. И возможность реализовать это на любой клавиатуре.
Сама статья представляет лишь пример того, как это можно использовать. Она призвана лишь обратить на этот подход внимание. Код сырой и его можно сделать лучше.