Сколько разных значений вы можете ввести нажатием одной клавиши? Так, на клавиатуре 33 клавиши в буквенном блоке, 13 в цифровом ряду, получается 46. А двумя нажатиями? Выходит 46×46, и ещё 46 – те же клавиши с Shift. Всего чуть больше двух тысяч, и это просто случайные сочетания букв, цифр и пунктуации.
Что если правильные ответы – сотни для одного нажатия, и десятки тысяч для двух? Это далеко не предел. И эти значения – не случайные пары символов, а кнопка "мой рабочий емейл" или "текущая дата", символы осо́бой пунктуации, специфичные языковые символы, кнопка для перевода с транслита, исправления регистра, и даже "включить музыку через 20 минут" или "прогноз погоды". И для этого вам не нужно учить наизусть таблицу юникод или хитрые сочетания. Вы сами определяете, что и где будет находиться, никак не меняя базовую функциональность.
Нет, это не клавиатура с тысячью кнопок, и даже не прошиваемая механика. Самая обычная клавиатура, которая сейчас у вас под рукой. Как это возможно? Обо всём по порядку.
Предпосылки
В 2018 году в мире кастомных механических клавиатур (нет, статья совсем не о них) стала набирать популярность идея с забавным названием – Tap Dance.
В сообществе уже вполне устоявшейся была концепция мультинажатий, где у каждой клавиши может быть отдельное действие при двойном, тройном, и дальнейших нажатиях.
Также широко известна была и идея разделения обычных нажатий и нажатий с небольшим удержанием, с отдельным действием для каждого.
Объединив эти два подхода, получился Tap Dance, в котором комбинацией нажатий одной клавиши с опциональными удержаниями мы получаем огромное количество возможных назначений. Ведь всего на четырёх нажатиях с удержаниями мы можем разместить целую... азбуку Морзе? Кажется, идея не так нова, хотя стоит признать ценность её адаптации, особенно когда в массовом сознании имеется твёрдая убеждённость, что клавиша = символ, причём символ весьма конкретный.
Из-за того, что термин в себе уже объединяет две популярные концепции, и часто упоминается вместе с прочими соседями, вроде аккордного ввода, и не без метко заимствованного названия, впоследствии он также приобрёл значение просто общего стиля, подхода к использованию клавиатуры, включающий в себя весь этот "экзотический" функционал. Но так как реализован каноничный TapDance и его соседи на аппаратном уровне, подавляющему большинству он, увы, попросту недоступен. Был.
В общем, речь пойдёт об одном вольном программном переложении для самых обычных клавиатур всего описанного функционала, и даже чуть шире. Но не только на одной клавише, ведь, хоть эта идея звучит интересно, вряд ли вы всерьёз захотите вбивать морзянку ради одного назначения.
Добавим также аккордный ввод, кнопку перевода с транслита, переключение в эмодзи-режим (простите 😊), тот самый ввод азбукой Морзе, просто шутки ради, и всё, что ещё захотите. Если раньше вы не знали, как добавить новый символ на клавиатуру – через час не будете знать, чем же ещё можно занять это место.
Приступим.
Как работает – Структура дерева • Логика переходов
Что работает – GUI • Слои • Реализация
Структура
tl;dr
Расширяя базовую идею Tap Dance, разрешаем добавлять назначения к последовательностям любых клавиш, а не только одной и той же, сохраняя возможность tap/hold ветвления.
Для этого реализуем древовидную структуру, где каждое нажатие меняет активную таблицу назначений по обозначенному переходу. Когда доходим до листа или прерываемся на узле – возвращаемся к корню.
Также добавляем поддержку пользовательских модификаторов, нажатие которых добавляет 1<<значение_модификатора
к текущему узлу, не вызывая отдельный переход. Каждое назначение с модификатором добавляется в структуру парой – значение по нажатию, значение по удержанию, вроде {0, 1, 4, 5, 16, 17}
.0
и 1
– базовые значения без модификаторов.
Итоговый узел/рабочая таблица/активная таблица назначений:
На первом уровне хэш-таблица с ключами сканкодами, на втором – хэш-таблица со значениями модификаторов, третий уровень – набор [тип, значение, вложенная_таблица].
Тип на удержании может быть также может обозначать принадлежность к аккорду/комбо. Аккорды повторяют структуру таблицы назначений, только вместо ключей-сканкодов используют ключи-буферы_нажатий в hex представлении. Лежат в своей подтаблице под особым ключом на первом уровне.
У каждой раскладки собственное дерево, которые назначаются раздельно. К каждому дереву раскладки на старте приложения добавляются глобальные назначения. При смене раскладки мы переходим к новому корню.
В итоге доступны любые комбинации описанных действий для добавления новых назначений:q_base->w_hold->аккорд_123->модификатор3+e_hold->что-угодно->…
Чтобы не нарушать последовательность, оттолкнёмся от базовых составляющих концепции Tap Dance, но не привязывайтесь к идее одной клавиши, это лишь старт.
Каноничный Tap Dance
В простейшей реализации мультинажатий, как первой составляющей, мы по каждому нажатию запускаем ожидание следующего нажатия этой же клавиши, и в зависимости от того, произошло ли оно, отправляем или значение одиночного нажатия, или двойного. Для тройного повторяем ожидание ещё раз. И так далее, по необходимости. Хранить мы бы это стали в массиве. Угловато, но просто – клавиша+глубина=действие.
Ещё проще дело обстоит с tap/hold, здесь у нас всего-то два значения, один if.
Но для их объединения простого массива будет недостаточно, и уже нужно говорить о вложенности и переходах. То есть мы говорим о бинарном дереве с переходами к следующему узлу по нажатиям или удержаниям. Выполнение действия, в простейшем случае – ввод, происходит на листьях, или в случае прерывания на каком-то узле.
Расширяем дерево
Итак, у нас есть таблица клавиш, которая хранит бинарные деревья переходов. Почему бы не объединить эти уровни? Что если наши переходы будут вести не просто к выбору "нажал или удержал", а к новой таблице клавиш? Всего с одним уровнем вложенности и ветвлением tap/hold мы получим место под (количество_клавиш*2)^2 назначений. Уже в буквенной части поместятся все кандзи. На третьем уровне дважды поместятся все текущие назначения юникод. Заниматься мы этим, конечно, не будем, но простор открыт.
Теперь наша структура выглядит как хэш-таблица клавиш (сканкодов – идентификаторов физических клавиш), по которым хранится два массива – для нажатий и удержаний, каждый из которых содержит значение действия и вложенную таблицу клавиш для перехода. Так мы можем не контролировать уровень глубины перехода, не создавать отдельную логику для обработки корневого значения и прочие связанные вещи. Мы просто храним ссылку на корень и на текущий узел – "рабочую таблицу", каждый раз рассматривая только её, безотносительно её положения.
Модификаторы
Хотелось бы также реализовать и пользовательские модификаторы. Но создавать им такие же вложенные переходы не получится, ведь это отразится на комбинируемых модификаторах – нужно будет дублировать переход для каждого из задействованных модификаторов и дублировать их самих на прочих переходах.
Более оптимальный вариант – если модификаторы не будут иметь собственного перехода, и все будут работать в рамках одной таблицы, просто изменяя общую переменную текущих модификаторов. Для этого каждому модификатору добавим числовое значение, на которое и будет увеличиваться/уменьшаться наше состояние_модификаторов.
По сканкодам у нас уже хранятся базовое значение и значение при удержании, как элементы 0 и 1, без модификаторов. 0 без модификаторов. Напрашивается, что 2, 3, и далее это и будут наши модификаторы. С двумя допущениями – это также нужно будет из массива перевести в хэш-таблицу, чтобы не создавать ограничение на последовательное добавление модификаторов; а также, чтобы было меньше пересечений на комбинируемых модификаторах, будем использовать не чистое значение модификатора, а 1 << значение
. Так у нас не получится, что модификаторы 2 и 3 вместе приведут к назначениям модификатора 5. К тому же, так мы сможем и для переменной текущих модификаторов просто устанавливать/отключать биты, а не добавлять/убавлять, что избавит нас от потенциальных ошибок лишнего добавленного значения модификатора.
Модификаторы не отменяют tap/hold, а дополняют его. Так значения 2 и 3 – это те же tap/hold, только с добавленным модификатором 1. За счёт того, что мы используем не чистое значение модификатора, у нас теперь всегда (модификатор 0 не рассматриваем), значение нажатия это текущий_модификатор
, а удержания – текущий_модификатор+1
. Альтернативным вариантом было бы просто располагать их парами в массиве по каждому модификатору, но так пришлось бы добавить ещё один уровень к структуре.
Типы значений
Добавленные модификаторы подсказывают, что теперь нам нужно добавить что-то, что поможет нам отличать их от обычных удержаний. Хранить их отдельно не имеет смысла, если они отлично вписываются в существующую структуру – они не могут работать вместе с обычным действием при удержании, и логически ближе к ним. При этом, они не мешают базовым нажатиям.
Значит мы можем просто описывать их вместо действия при удержании, но добавив подсказку типа. Так как типы нам всё равно понадобятся, это ни капли не усложняет структуру.
Почти финальный вид узла:
{
<клавиша>: {
<модификатор>: [тип, действие/значение, {следующий_узел}]
}
}
Где все чётные "модификаторы" – базовые значения, нечётные – при удержании.
Аккорды
Ещё одна функциональность, которую хотелось бы реализовать и сделать частью нашей структуры – аккорды (также часто применяется термин "combo", но есть разные трактовки). Это также триггер действия, который срабатывает при одновременном нажатии заданного сочетания клавиш.
Казалось бы, что аккорды совсем не вписываются в структуру, ведь мы работаем с каждой отдельной клавишей, по отдельному модификатору, и нам понадобится собирать значения аккордовых клавиш. Но замените в нашей рабочей таблице слово <клавиша>
на <аккорд>
, и всё сойдётся. Конечно, просто заменять клавиши аккордами мы не будем, а вот повторить эту структуру по особому ключу, почему бы и нет:
{
<клавиша>: {
<модификатор>: [тип, действие/значение, {следующий_узел}]
},
"аккорды": {
<сочетание>: {
<модификатор>: [тип, действие/значение, {следующий_узел}]
}
}
}
Минимальные отличия в логике, абсолютно та же структура, лишь с другим типом ключа. Те же поддержки модификаторов, те же переходы к следующим узлам.
Проверка сочетаний – не самое дешёвое дело, потому не стоит выполнять её при каждом нажатии. Лучше бы указать где-то тип часть_аккорда
задействованным клавишам, чтобы выполнять проверку только когда одна из клавиш группы срабатывает. И указать это можно, опять же, в типе по удержанию, ведь аккордовая клавиша логически не может выполнять какое-либо действие по удержанию или быть модификатором.
Языковые зависимости
И последнее дополнение, прежде чем мы закроем этот блок:
До сих пор мы говорили о единственном дереве, и оно вполне актуально, как дерево глобальных назначений, которые будут активны всегда. Но у нас может возникнуть потребность в зависимых от языка/раскладки назначениях. Тогда стоило бы объявить отдельные деревья для каждой из раскладок, и при смене раскладки менять ссылку актуального корня.
А чтобы не выполнять одновременно и глобальные и языковые переходы, с дублированием проверок в каждом из них, это было бы странно, на старте приложения просто будем дополнять каждое языковое дерево глобальными назначениями, и использовать только корни от текущей раскладки.
Основная логика
Так как базовых событий, от которых мы можем оттолкнуться всего два (key down и key up), всю логику и взаимодействия нам нужно обвязывать вокруг них. При этом нагрузка на key up минимальная – он служит лишь для сброса состояния.
В процессе разбора нажатий нам часто придётся откладывать ввод в ожидании следующего нажатия – для этого нам понадобятся отдельные глобальные переменные для ожидающего значения цепочки и ожидающего значения перед удержанием.
Итого между нажатиями у нас будут переходить:
уже обсуждённая активная рабочая таблица / узел
константная таблица корней от раскладок
текущее значение модификатора
последний массив цепочки нажатий [тип, значение, переход]
массив базового нажатия [тип, значение, переход], пока ожидаем удержание
а также два битовых буфера – для нажатых модификаторов, и для всех остальных нажатий
Центральная логика при нажатии максимально проста: мы смотрим, есть ли в нашей активной таблице переходов пришедший сканкод и есть ли в нём ключ текущего модификатора – working_table[scancode][current_mod]
. Если чего-то нет, необходимо передать/пропустить нативное нажатие, то есть не вмешиваться в процесс ввода.
Если значение есть, проверяем тип удержания – working_table[scancode][current_mod + 1][0]
, если его нет – выходим в первую и основную ветку:
Базовая обработка без назначенного действия удержания
отправляем на обработку массив обычного нажатия
в
обработке {
проверяем, есть ли значения в таблице переходов пришедшего массиваесли переходы есть
• сохраняем массив как "последнее значение цепочки"
• меняем ссылку активной таблицы переходов на вложенную
• запускаем таймер передачи последнего значения/действия, который мы сбрасываем при каждом новом нажатии
• когда/если таймер срабатывает – выполняем последнее действие (допустим, ввод символа), сбрасываем переменную, и возвращаем ссылку текущей активной таблицы к корню по текущей раскладкеесли переходов при обработке не нашлось – сразу выполняем действие и сбрасываем таблицу к корню.
}
Таким образом, при каждом нажатии мы сбрасываем предыдущий таймер, переходим на следующий уровень цепочки, и так пока не дойдём до листа, или пока не прервёмся таймером, что в любом случае выполнит последнее действие и сбросит таблицу к корню.
Действие при удержании
Вторая ветка, когда у нас есть тип действие
при удержании, не особо сложнее. Мы добавляем проверку удержания, и в зависимости от результата отправляем на обработку
либо массив от базового нажатия, либо от удержания. Но с двумя дополнениями:
чтобы у нас не начиналось новое
key_down
событие от удержания, включим бит по текущему сканкоду в буфере, а в началоkey_down
обработчика добавим проверку, что бит от пришедшего нажатия мы ещё не трогали. Снимаем бит только наkey_up
событии.если во время ожидания у нас произойдёт нажатие другой клавиши, нам нужно завершить предыдущую обработку. Для этого перед началом проверки удержания сохраним текущее значение от нажатия в глобальную переменную
waiting
. Если мы выполнили чистую проверку удержания, и отправили нужный массив на обработку, в конце мы сбросим данную переменную. И добавим новый блок в начало key_down обработчика: если у нас есть не пустойwaiting
, то есть мы в ожидании удержания, но уже делаем следующее нажатие, – форсируемобработку
по базовому значению изwaiting
, и сбрасываем его, вместе с проверкой ожидания удержания. Проверка клавиши, нажатие которой вызвало этот блок, продолжается без изменений, но уже с обновлённой ссылкой на текущий узел.
Модификатор
Проверку типа модификатор
мы выполняем чуть иначе – без влияния прочих, уже активных модификаторов. Это нужно для комбинируемых модификаторов, иначе их придётся перекрёстно назначать в каждом сочетании.
Тем не менее, если тип удержания – модификатор
, мы включаем бит в соответствующем буфере, и увеличиваем глобальную переменную текущего значения модификаторов. А вkey_up
добавляем проверку бита по сканкоду, и если срабатывает, выключаем его и уменьшаем переменную значения модификаторов.
Но до этого мы также запоминаем "последнее значение цепочки" от обычного нажатия с уже активными прочими модификаторами, и добавляем таймер сброса этого значения. Также мы добавляем в key_up
маленький блок: если есть последнее значение – отдай его на обработку
и отмени таймер сброса.
Так наш модификатор не теряет функции обычного нажатия – если мы просто нажали модификатор, то отправится назначение от обычного нажатия. Если мы удержали модификатор, подумали, нужен ли он нам, и отпустили – просто ничего не произойдёт. А если через модификатор нажали хоть одну клавишу, "последнее значение" будет перезаписано и модификатор не отправит своё базовое значение, как бы быстро вы его не отжали.
Аккорд
И последняя ветка – если мы видим тип удержания часть_аккорда
.
Включаем бит нажатия клавиши, проверяем вхождение всего буфера нажатий (модификаторы тут не учитываются, у них собственный буфер) в таблице по нашему "особому ключу". Если значение найдено – передаём в обработку
. Ничего необычного. Но буфер для хранения и проверок приходится переводить в hex-представление.
Так же, как и с модификаторами, мы сохраняем последнее_значение
и запускаем таймер его сброса. Уже добавленный блок в key_up
без изменений справится с передачей действия при базовом нажатии, если последнее_значение
не будет перезаписано.
Прочее
И из того, что не пришлось к слову в предыдущих пунктах, но участвует в процессе:
Клавиши системных модификаторов стараемся пропускать максимально нативно, не вмешиваясь в процесс, чтобы не ломать системные/программные хоткеи. Они будут запрещены к переназначению по нажатию, но им можно будет дополнительно добавить значение пользовательского модификатора на удержании. Хотя это не рекомендуется.
В целом это работает как и ранее: если назначения есть – отправляются они, если нет – нативное нажатие. Например, если вы определите ctrl
модификатором, и на s
назначите действие из-под его значения модификатора, данный хоткей сработает только как назначенное действие. Если ctrl
не назначен модификатором, или назначен, но на "модифицированный" s
нет действия – сработает системный хоткей.
Но это всё равно остаётся потенциально очень ошибковозможным местом, и требует тщательного тестирования, особенно в различных сочетаниях нажатий.
При нажатиях также сверяем текущую активную раскладку с последней, которую мы видели. Если они отличаются, форсируем передачу последнего значения, и возвращаем активную таблицу к корню по новой раскладке, прежде чем перейти к проверкам.
Если начальная проверка сканкода и модификатора в активной таблице неудачна – возвращаемся к корню, и выполняем проверку ещё раз, прежде чем уйти в ветку "нет назначений".
В обработке
не сбрасываем активную таблицу к корню, если есть активные модификаторы. Сброс к корню в этом случае происходит при отпускании всех модификаторов, а пока любой из них активен – можно продолжать ввод на этом уровне переходов.
Демонстрация
Мы рассмотрели идею и принципы работы основной логической части проекта, теперь пора посмотреть на реализацию.
Но для начала, буквально 30 секунд, если позволите:
Tap Dance for Windows – это открытый проект для Windows (пока что), позволяющий наделять клавиши клавиатуры дополнительными функциями и комбинациями, подобно возможностям прошивок вроде QMK, но на программном уровне.
Проект представляет собой интерактивную среду для переназначений и поддерживает такие ключевые возможности, как:
• разделение поведения tap/hold
• классические мультинажатия
• последовательности нажатий с неограниченной глубиной
• аккорды/комбо
• назначение пользовательских модификаторов
… и всё это одновременно, в одном сценарии использования, если понадобится. Вы сами выбираете, что из возможностей, и в каких комбинациях использовать.Всё это настраивается через GUI в реальном времени. Назначения могут быть привязаны к конкретным раскладкам, и объединены в слои.
Добавляемые назначения поддерживают не только ввод текста и симуляцию клавиш, но и вызов ряда дополнительных функций, список которых вы легко можете дополнить сами, для любого нужного вам функционала.
Спасибо, это надо было сделать.
Обзор GUI
Для большей интерактивности можете клонировать репозиторий, и двигаться параллельно с дальнейшим описанием.
Основное окно приложения отображает клавиши, повторяющие клавиши обычной клавиатуры в вашей текущей раскладке, в правом списке отображены аккорды текущего уровня, в левом – список слоёв. Кроме этого, над клавишами-стрелками расположено выпадающее меню текущих языковых раскладок, для переключения и раздельного назначения на каждой из них, или в глобальном режиме, для общих назначений.

В настройках (🔧) вы можете изменить режим отображения – квадратный/широкий, масштаб интерфейса и шрифта, а также формат раскладки вашей клавиатуры (ANSI/ISO). Последняя настройка не только косметическая, она влияет на некоторые назначения, так как и определённые сканкоды зависят от формата. В том же окне задайте желаемое время для распознавания удержаний и для ожидания следующего нажатия перехода.

Все назначения добавляются на определённый слой (да, это не совсем те слои), которые вы можете раздельно переключать, редактировать, настраивать их относительный приоритет, а также контролировать собственными назначениями. Более подробно рассмотрим их позже, а пока добавим новый слой, на котором немного поиграемся с назначениями. Отметим галочкой как активный, и перейдём к его редактированию двойным нажатием.
Добавим первые назначения. Для этого кликом или нажатием физической кнопки клавиатуры перейдём к нужной клавише, и появившимися в верхней правой части кнопками зададим действия. Например, на базовое нажатие тильды добавим текст, а на удержание добавим модификатор 1
, выбрав соответствующий тип. В этом же окне ещё раз нажмём по тильде, и добавим назначения для базового нажатия – {Media_Play_Pause}
с типом Key simulation
и для удержания – SendCurrentDate
с типом Function
.
У нас готовы первые каноничные TapDance назначения с разными типами действий. Можете сразу опробовать. Одиночное нажатие тильды введёт заданный текст, двойное – переключит текущее воспроизведение медиа, а нажатие+нажатие с удержанием вызовет функцию, в данном случае возвращающую на ввод текущую дату. Sunday, 20 April 2025.
В отличии от классической идеи, мы не ограничены тильдой, и второе нажатие можем задавать не только на той же клавише, а на любых других, в любых количествах, с любой вложенностью.
Назначение модификатора под одиночным удержанием пока ничего не даёт, мы не задали назначения из-под этого модификатора. В верхнем меню пути нажмите на первый элемент, с названием слоя, который вернёт вас к первому уровню переходов. Вид тильды изменился, теперь она отображает назначенный текст по базовому нажатию, значение модификатора при удержании и синей обводкой показывает, что выполняет роль модификатора на удержании.

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

При каждом назначении пока что нужно возвращаться к предыдущему уровню со сбросом активных модификаторов. (Поправим)
Протестируем глубину назначений. Назначим действие на 20 нажатий q
. Хотя это, опять же, могут быть абсолютно любые клавиши, совершенно не обязательно одна и та же.

Проверим. It works. Хотя вызвать значение при удержании тут не очень просто.
Если снова вернёмся к первому уровню переходов, мы увидим подчёркнутый q, что говорит нам о том, что у него есть дочерние переходы, даже при том, что под самим q никаких назначений нет. Если пройдём по подчёркнутому пути и сбросим назначенные действия, всё вернётся к прежнему состоянию.
И проверим аккорды – нажмём в new
под правым списком, нажатиями мышью или на клавиатуре выберем нужное сочетание, и подтвердим аккорд кнопкой save
, назначив нужное действие. Клавиши аккордов обозначились жёлтым, а в правом списке появился наш аккорд, который мы так же можем сразу проверить.

Аккорды точно так же можно назначать из-под модификаторов, с других уровней, и задавать им собственные дочерние переходы. Для последнего – "зайдите" в аккорд двойным нажатием по его строке в списке, и определите вложенные назначения.
Верхнее меню пути показывает, какой тип перехода вы выполнили, от обычного нажатия ➤
, от удержания ▲
или по аккорду ▼
. Цифра перед стрелкой показывает значение модификатора перехода, если оно было.

Слои и раскладки
Слоями в данном приложении называются отдельные .json файлы с собственными деревьями назначений, которые генерируются и редактируются нашим приложением, и рекурсивно собираются в единое дерево, используемое логической частью приложения, в соответствии со своим приоритетом.
У совпадающих типов/значений дочерние назначения объединяются: если вы назначите e➤e➤=1 на одном слое, и e➤i➤=2 на другом, они соберутся в единый e➤(e➤=1, i➤=2), но если вы при этом зададите на первом слое базовое значение e➤=3, объединение не произойдёт и останется только e➤=3, e➤e➤=1. В случае таких конфликтов значение имеет приоритет слоёв, отображаемый рядом с каждым активным слоем.
Для большего контроля, все слои в списке, вне зависимости от текущей активности, отображают собственные назначения по данному пути и количество дочерних переходов, раздельно для tap и hold:

Да, сами .json'ы выглядят чуть монструозно, но обращаться к ним вам не понадобится – вся настройка проходит через GUI.
Раскладки отображены выпадающим списком над клавишами-стрелками. Переключаясь между ними вы переходите в редактирование дерева конкретной раскладки.
Каждый слой имеет собственный список раскладок, которые добавляются к общему списку в GUI при запуске. Даже если у вас не установлена определённая раскладка, но она используется каким-то слоем, даже неактивным, – вы сможете перейти в её дерево назначений через выпадающий список. Хотя использовать не сможете, пока не установите её, конечно.
Глобальное дерево назначений каждого слоя объединяется с каждой отдельной раскладкой, по тому же принципу, что и общее объединение слоёв. Приоритет при совмещении глобального и частных деревьев за конкретными раскладками.
В репозитории размещён ряд слоёв, которые вы можете опробовать, прежде чем определите собственные. Бо́льшая их часть это реальные назначения, которыми я сам пользуюсь уже более 7 лет, хотя особо рекомендовать их в данный момент не могу – они используют дополнительные назначения на клавишах системных модификаторов, что, как уже отмечалось ранее, может вызывать нехорошие баги. Тем не менее, для ознакомления или, может, шаблона, отлично подходят.
Предустановленные слои
Default: слой с назначениями 83 пунктуационных и вспомогательных символов в буквенной части клавиатуры. Расположены под обычным удержанием, с alt, и alt с удержанием.


Controlling Keys: слой с переназначениями управляющих клавиш, hjkl-стрелки, управление медиа на разные сочетания с BS, и всякое прочее. Задействует, как видно, всё "нежелательное".


Extra Langs: слой с назначениями для конкретных раскладок. Цифровой ряд через Sh и Sh с удержанием вводит специфичные языковые символы в двух регистрах. Версии для кириллических и латинских письменностей привязаны к стандартным йцукен и qwerty. Символы подобраны по максимальной ширине охвата.


Leader: поле для назначений пользовательских функций (о них ниже) через \➤
(дополнительная клавиша над Enter для ANSI, слева от Enter для ISO). И как модификатор, для поля эмодзи 😇.


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


Реализация и дополнительный функционал
Текущая реализация написана на ahk v2, что хоть и довольно удобно, но налагает ряд ограничений.
Например, в ахк мы должны заранее обозначить, будет ли заблокировано нативное нажатие, или нет. Потому в случаях, когда мы в key_down
обошли всю логику, не нашли никаких назначений и ожиданий, и хотим передать нажатие дальше – мы должны сами симулировать это нажатие. В подавляющем большинстве случаев этого более чем достаточно, но может некорректно работать в некоторых приложениях, в основном игровых.
Это же сказывается на системных модификаторах – лучшим найденным решением, в рамках ахк, на текущий момент, оказалось объявление на каждом новом (только новом) переходе отдельного хоткея на каждое переопределённое системно-модификаторное сочетание, с передачей отдельного аргумента, как значения переопределённого модификатора. Это вполне работает, и с кэшированием даёт малый оверхед, но всё же костыль.
Из-за этого, и для общего повышения быстродействия, основная логика была переписана на C. Пока это лишь экспериментальное переложение без кастомизации, которое использует уже сгенерированное через основной скрипт дерево, но большая часть логики уже отлично работает. Начало положено.
А теперь о плю́сах.
Использование ахк позволяет нам использовать его родной синтаксис для переназначенной симуляции клавиш. Пункт Key Simulation
, в любом назначении, это он и есть. ^z
для undo
, {Volume_up}
и все прочие назначения, которые и понятно выглядят в gui, и удобно назначаются.
Кастомные функции
И ещё один очень приятный аспект – мы можем задавать назначениям не просто передачу текста или симуляцию клавиши, но и исполнение любой функции. И она совершенно не обязательно должна быть связана с вводом или передавать значение в текстовое поле, вроде текущей даты или инкрементации числа под курсором. Любая функция.
Обратитесь к внешнему api, чтобы получить курс валют или прогноз погоды; поставьте напоминание, которое сработает через 20 минут; задайте таймер включения/выключения музыки; нормализуйте текст со сбитым регистром или переведите выделенный текст с translita.
Или используйте назначенные функции чтобы менять активность слоёв приложения – переключать конкретные, или устанавливать определённый активный набор по одному нажатию.
Это то, что уже добавлено для демонстрации функций, но вы всегда можете добавить ту, что нужна именно вам. Всё, что придумаете.
Заключение
Здесь ещё должен был быть раздел про применимость, где я думал рассказать о переложении типографских раскладок, использовании аккордного ввода, и всём прочем, но этот проект – инструмент, и как его использовать, решать вам. Для одной кнопки ввода "work_email@gmail.com", или для переназначения всего и вся, для своей вариации полнословной клавиатуры, или для вызова десятков самописных функций? В любом случае, всегда буду рад почитать об интересных сценариях использования.
Спасибо за внимание.
Проект живёт на github. Все предложения, дополнения и замечания приветствуются.