Предположим, что вы выполнили предыдущий пример или еще откуда-нибудь взяли (сделали) раскладку клавиатуры с тремя группами.
И вам не нравится способ переключения между группами - три положения у одного переключателя "лат/рус/рус".
Ну, во-первых, давайте вообще забудем пока про символ ISO_Next_Group, к которому "прицеплено" такое неудобное "действие". (Вообще-то, семантику ISO_Next_Group можно и переделать, но пока отложим этот вопрос).
Для простоты рассмотрения будем считать, что переключение у вас делается одной клавишей (обычно - это CapsLock),а не комбинацией клавиш (типа - Shift+Shift или Alt+Shift).
Напомню, что непосредственно в описании клавиши можно указать "действия", причем, разные для разных групп.
Кстати. Напомню, что, во-первых, большинство изменений мы будем делать в файле типа xkb_symbols. А, во-вторых, поскольку эти изменения не просто дополняют существующие определения клавиш, а изменяют их радикально, то лучше всего добавлять их в "общую конфигурацию" не "приплюсовыванием", а отдельной инструкцией replace, например -
xkb_symbols { include "en_US(pc104)" replace "new.symbols" };(Вообще-то, можно "способ добавления" replace указывать в файле перед каждой инструкцией. Но это, почему-то, не всегда срабатывает.)
Самый тривиальный способ - сделать две клавиши-переключатели, каждая с двумя состояниями. Одна клавиша переключает "лат./старый рус.", другая - "лат/новый рус.". Или в терминах "номер группы", первая клавиша переключает "group1/group2", другая - "group1/group3".
Выберем два скан-кода и "подвесим" на них "действия" -
key <...> { actions[Group1]= [ LockGroup(group=2) ], actions[Group2]= [ LockGroup(group=1) ] }; key <...> { actions[Group1]= [ LockGroup(group=3) ], actions[Group3]= [ LockGroup(group=1) ] };
Конечно, это - неполное описание. Надо добавить к первой клавише ее "поведение", когда включена группа 3, на тот случай, если мы нажмем ее в ситуации, когда с помощью второго переключателя уже выбрана третья группа. И, соответственно, "поведение" второй клавиши в состоянии "группа 2".
Кроме того, в описании должна быть кроме таблицы "действий" еще и таблица символов. В нашем случае можно использовать специальный "псевдосимвол" - NoSymbol.
Тогда полное описание будет выглядеть как
key <...> { [NoSymbol],[NoSymbol],[NoSymbol], actions[Group1]= [ LockGroup(group=2) ], actions[Group2]= [ LockGroup(group=1) ], actions[Group3]= [ LockGroup(group=1) ] }; key <...> { [NoSymbol],[NoSymbol],[NoSymbol], actions[Group1]= [ LockGroup(group=3) ], actions[Group2]= [ LockGroup(group=1) ], actions[Group3]= [ LockGroup(group=1) ] };Но, на мой взгляд, такой способ переключения еще более неудобный. (Я же не обещал "хороших" решений :-).
Более удобным мне представляется способ, когда наш привычный перключатель (пусть это будет CapsLock) продолжает переключать "лат/рус". А вот - какой именно "рус" (вторую или третью группу) он выберет - будет определяться другой клавишей.
Естественно, при этом каждый из переключателей имеет два состояния. Основной переключатель - "лат/рус", дополнительный - "группа2/группа3".
Другими словами, "поведение" основного переключателя должно меняться в зависимости от того, нажимали ли мы дополнительный переключатель (и сколько раз - четное или нечетное количество).
Собственно эти два "поведения" я уже написал в предыдущем примере. Только там были две разные клавиши, а нам надо соместить это на одной. Кстати, можно заметить, что эти два описания отличаются только в одной строчке - "переход из состояния Group1", остальные части описания совпадают.
Итак. Подзадача - как сделать так, чтобы "поведение" клавиши менялось в зависмости от состяния какой-нибудь другой.
Первое, что приходит в голову - с помощью какого-нибудь модификатора. Напомню, что каждая группа может делиться на уровни (shift level), а выбор конкретного уровня зависит от состояния модификаторов. Причем, эту зависимость мы можем как угодно менять, сочиняя новые "типы" клавиш.
Все, что нам надо - взять какой-нибудь модификатор (виртуальный), сочинить "тип", в котором переход на второй уровень зависит только от данного модификатора (чтобы наш основной перключатель реагировал только на этот модификатор) и, наконец, разделить у нашего переключателя (основного) первую группу на два уровня и "присвоить" им разные действия.
Естественно, "установку/сброс" модификатора "повесить" (с помощью соответствующих "действий") на второй переключатель.
Итак. Описание основного переключателя теперь будет иметь вид
key <...> { type[Group1]=".....", [NoSymbol, NoSymbol], [NoSymbol], [NoSymbol], actions[Group1]= [ LockGroup(group=2), LockGroup(group=3)], actions[Group2]= [ LockGroup(group=1) ], actions[Group3]= [ LockGroup(group=1) ] };("тип" придумаем немного позже).
Теперь надо "сочинить" модификатор и новый тип. И здесь нас ждет "засада". Дело в том, что xkbcomp (по крайней мере в нынешнем состоянии) не позволяет объявить новый виртуальный модификатор. Можно использовать только те, которые в нем уже предопределены.
К счастью, среди его модификаторов уже есть подходящий - LevelThree. А подходящий он потому, что
Таким образом, берем модификатор LevelThree и тип "THREE_LEVEL" -
type "THREE_LEVEL" { modifiers = Shift+LevelThree; map[None] = Level1; map[Shift] = Level2; map[LevelThree] = Level3; map[Shift+LevelThree] = Level3; level_name[Level1] = "Base'; level_name[Level2] = "Shift"; level_name[Level3] = "Level3"; };
Напомню, что он уже должен быть в нашей "полной конфигурации" по умолчанию. Только обратите внимание, что он определяет не два, а три уровня. Причем, "наш" модификатор "посылает" на третий уровень. Но ничего страшного в этом нет. Мы просто сдвинем второе "действие" на третий уровень, а второй можно заполнить "заглушкой" - NoAction() или сделать его таким же как и первый уровнь (обратите внимание - второй уровень, это - когда прижат Shift).
Таким образом, наш основной переключатель (давайте уже определимся, что это будет <CAPS>), приобретает вид
key <CAPS> { type[Group1]="THREE_LEVEL", [NoSymbol, NoSymbol, NoSymbol], [NoSymbol], [NoSymbol], actions[Group1]= [ LockGroup(group=2), NoAction(), LockGroup(group=3)], actions[Group2]= [ LockGroup(group=1)], actions[Group3]= [ LockGroup(group=1)] };
Теперь можно заняться второй клавишей, которая будет "поднимать/опускать" модификатор LevelThree. Конечно, это может быть не одиночная клавиша, а какая-нибудь комбинация клавиш. Но для простоты, сначала возьмем отдельную клавишу. Я на своей клавиатуре ("Микрософтовской") использовал для этого клавишу "Menu" (скан-код <MENU>). Но, если у вас такой клавиши нет, то можно задействовать одну из парных клавиш (Shift, Control, Alt).
Итак. Сочиняем "поведение" клавиши <MENU>.
Прежде всего - я думаю, что вам не хочется держать ее все время нажатой,
пока это нужно. То есть, нам надо, чтобы по первому нажатию она "подняла"
модификатор, а по второму - опустила.
Как раз это делает "действие" LockMods (в переменной locked modifiers). Напомню, что SetMods и LatchMods "держат" модификаторы только пока нажата клавиша.
Итак, используем "действие" LockMods -
key <MENU> { [ NoSymbol ], actions[Group1]=[ LockMods(modifiers=LevelThree) ]};(Описывать все три группы не обязательно. Если номер группы больше, чем допустимо для клавиши, XKB будет его "выравнивать", пока не попадет в допустимый диапазон.)
Теперь остается одна тонкость. Дело в том, что сам по себе виртуальный модификатор работать не будет, если он не связан с каким-нибудь реальным модификатором. Напомню, что при общении модуля XKB с процедурами Xlib, передается только "эмулируемый набор модификаторов" (то есть - реальные модификаторы). И, хотя выполнение "действий" происходит внутри модуля XKB, а не в Xlib, чтобы не возникало "разногласий" между XKB и Xlib по поводу "состояния", необходимо "связать" виртуальный модификатор с каким-нибудь реальным.
Поэтому, последним шагом будет - связать с клавишей какой-нибудь реальный модификатор (с помошью объявления modifier_map). И вот здесь нас ждет вторая "засада". В полной конфигурации все модификаторы (даже "безымянные" - Mod1-Mod5) уже расписаны. То есть, каждый уже соединен с какой-нибудь клавишей и, соответственно, каким-нибудь символом.
Самое неприятное то, что клиентские приложения могут реагировать на эти модификаторы и менять свое поведение. Поэтому, если мы свяжем наш LevelTree, например, с модификатором Mod5, который соответствует символу Scroll_Lock, то, при "поднятии" модификатора LevelThree, приложения будут считать, что нажата кнопка ScrollLock. А вот как они будут вести себя при этом - кто как.
Здесь надо немного пояснить - как реагируют программы на "безымянные" модификаторы. Поскольку этими модификаторам явно никакая функция не соответствует, программы ориентируются на символы, которые с ними связаны.
То есть, если программа обнаружит, что в "состоянии" модификаторов взведен модификатор Mod1, она попытается найти соответствующий ему символ. Обычно это - Alt_L и Alt_R, то есть любая из клавиш Alt. Соответственно, программа считает, что нажата клавиша Alt и ведет себя в соответствии со своим пониманием этой клавиши.
Поэтому, чтобы избежать всяких "побочных" эффектов, нам надо не только связать модификатор с нашей клавишей <MENU>, но и "отцепить" этот реальный модификатор от других. Как я уже говорил в примере "добавление новой группы", "отцепить" модификатор, который был объявлен где-то "в глубинах" includ'ов, очень трудно. но, к счастью, они там привязываются не к скан-кодам, а к символам. Поэтому, если мы в своей конфигурации просто уберем эти символы из описаний скан-кодов, то связка "модификатор-символ" просто "повиснет в воздухе" и таким образом нам удасться "отцепить" модификатор.
Итак. Давайте возьмем реальный модификатор Mod5. Он используется только для символа Scroll_Lock, а этот символ, в свою очередь, "подвешен" только на клавишу ScrollLock (скан-код <SCLK>). Да и клавиша эта не самая "часто используемая".
Теперь нам надо добавить в описание клавиши <MENU> виртуальный модификатор LevelThree. Добавить инструкцию modifier_map, который свяжет Mod5 с нашей клавишей. И, наконец, переопределить клавишу <SCLK>, чтобы в ней не было упоминания и символе Scroll_Lock.
key <MENU> { virtualMods = LevelThree, [ NoSymbol], actions[Group1]=[ LockMods(modifiers=LevelThree) ]}; modifier_map Mod5 { <MENU> }; key SCLK { [NoSymbol] };Вот теперь можно пробовать - что получилось.
Конечно, это не самое лучшее решение.
Вспомним еще раз - в чем проблема. В том, что основной переключатель должен переходит из одного состояния (текущая группа - Group1), в два разных, в зависимости от состояния какой-то другой клавиши.
Но ведь можно "запомнить" состояние дополнительного переключателя нет только с помощью модификатора.
Напомню, что номер группы может храниться в трех внутренних переменных XKB - locked, latched и base group, значение которых можно менять независимо. Причем для выбора символа у клавиши используется суммарное значение этих переменных - effective group.
Напомню также, что благодаря "методу выравнивания номера группы" (см. "Внутренности":Метод выравнивания..."), если суммарное значение окажется больше, чем количество существующих групп на единицу, то опять получится первая группа и т.д.
Таким образом, можно заставить дополнительный переключатель "запоминать" номер "альтернативной" группы в дополнительной "групповой" переменной, например - base group. А основной переключатель пусть манипулирует значением в другой переменной, например - locked group.
Итак. Пусть дополнительный переключатель запоминает - какая из "русских" групп нам требуется (2 или 3), например, в переменной base group.
Тогда основной переключатель, чтобы перейти из "лат" в из "рус" раскладок должен просто обнулить locked group. А для того, чтобы вернуться обратно в "лат", надо в locked group записать "добавку", достаточно большую, чтобы "метод выравнивания" "завернул" значение обратно к "лат" раскладке.
Таким образом мы нашу проблему (переход из Group1 в два разных состояния) решаем. Правда теперь у на появляется проблема - как вернуться обратно из разных состояний в одно и ту же группу. Но она то, решается очень просто. Поскольку клавиатура при этом находится в разных состояниях (Group2 и Group3), то мы без проблем "подвешиваем" на клавишу два разных "действия", каждое со своим значением "добавки".
Прежде чем переходить написанию конфигурации клавишь, давайте проясним вопрос: что значит "обнулить" переменную с номером группы, и - какие "добавки" нам понадобятся.
Надо сказать об одной тонкости - хотя в "конфигах" группы нумеруются -
1,2,3..., внутри XKB это означает - 0,1,2...
То есть, group1 - на самом деле 0, group2 - 1 и т.д.
Итак. Давайте сначала рассмотрим задачу во внутренних значениях XKB (0,1,2).
Тогда у нас
Тогда алгоритм работы переключателей мог бы быть таким -
Однако, не все так просто. Если мы попытаемся воплотить это в конфигурацию клавиши, то нарвемся на очередную "засаду".
Дело в том, что значения номера группы "запоминаются" только в locked group. В других переменных (base и latched) они "держаться" только пока соответствующая клавиша нажата и "испаряются" оттуда, если клавишу отпустить.
Правда, у нас есть одна "лазейка". Мы можем изменить поведение клавиши (той, что будет менять base group) и сделать ее "залипающей". Тогда XKB при первом нажатии/отжатии клавиши выполнит только ту задачу, которая выполняется при нажатии клавиши, а при повторном нажатии/отжатии наоборот - только то, что выполняется при отжатии клавиши.
Только обратите внимание, что когда мы ее все-таки "отожмем" там получится 0. То есть, мы не сможем держать там "2 или 3", а только "2 или 0", "1 или 0" и т.п.
Ну и ладно. Придется в ней держать не "номер алтернативной раскладки", а только "добавку" к номеру. То есть - не "2 или 3", а "0 или 1".
Тoгда нам придется слегка подправить наш алгоритм -
Теперь, переходя к обозначениям из "конфигов" (group1, group2 и т.д.), получим -
Сочиняем описание клавиш (пусть это будут те же <CAPS> и <MENU>)
key <CAPS> { [NoSymbol],[NoSymbol],[NoSymbol], actions[Group1]= [ LockGroup(group=2) ], actions[Group2]= [ LockGroup(group=1) ], actions[Group3]= [ LockGroup(group=3) ] }; key <MENU> { [ NoSymbol ], locks= yes actions[Group1]=[ SetGroup(group=2) ]};(обратите внимание - мы для дополнительного переключателя опять описываем только одну группу. Все равно он может "перещелкивать" только два состояния, и делать его "чувствительным" к текущей группе нет смысла.)
Можно пробовать.
Правда, надо признаться, что этот метод тоже не лишен недостатков. Если вы начнете менять альтернативную раскладку, когда основной переключатель стоит в положении "какой-нибудь рус." - все будет нормально.
Но вот если "пощелкать" им, когда основной переключатель стоит в состоянии "лат", то результаты будут несколько "странные" (вы можете сами их "просчитать"), поскольку "состояние лат." на самом деле - "хитро" подобранная сумма двух переменных (base и locked).
С другой стороны, с этим можно примириться. Скорее всего в реальной работе вы будете "подгонять" русскую раскладку, когда основной переключатель стоит в сотоянии "рус", а не готовить ее заранее (когда основной переключатель в состоянии "лат").
Замечу также, что это не единственный вариант переключения "с помощью двух переменных group". Можно, например, сделать так, чтобы дополнительный переключатель работал с locked group, а основной - с base group (если посмотреть исходный алгоритм, то там у основного переключателя есть состояние когда он "держит" в "своей" переменной 0). Но этот вариант не лучше в смысле "побочных эффектов", а в описании, пожалуй, сложнее.
Поэтому, пока не этом и остановимся.
Тем более, что у нас остался еще -
Как я уже говорил - не очень хорошо, когда "действия" назначаются прямо в описании клавиши (в файлах xkb_symbols).
Во всяком случае, при этом трудно распространить наше решение на случай, когда роль основного или дополнительного переключателей (или обоих) выполняет не одиночная клавиша, а комбинация клавиш.
Обычно "хорошим тоном" является - привязать "действия" к каким-нибудь сиволам (через "интерпретации"), а в описании клавиш использовать только символы.
Давайте проделаем это для любого из примеров, например, последнего.
Единственная проблема - надо подобрать подходящии символы. Дело в том, что брать коды обычных символов (даже "экзотических" - типа "умлаутов") не очень хорошо. Потому, что при нажатии клавиши XKB будет не только выполнять соответствующие "действия", но и выдавать в прикладную программу эти символы. А программа, соответственно, будет пытаться их напечатать.
К счастью, в наборе символов есть группа кодов, которые должны использоваться только для внутренних нужд XKB и игнорироваться прикладными программами. Вы можете найти их в файле {XROOT}/include/X11/keysymdef.h их названия начинаются на XK_ISO_. Например,
XK_ISO_First_Group XK_ISO_First_Group_Lock XK_ISO_Last_Group XK_ISO_Prev_Group_Lockи т.п.
Конечно, там нет символов из названия которых следует, что по этому символу "записать в locked group число 3". Но мы можем изменить семантику любого из символов (тем более, что и не для каждого из этих символов задана семантика в "конфигах" XKB).
Давайте сначала выясним - сколько нам надо символов. Для этого посчитаем - сколько у нас различных действий в описании обоих переключателей.
LockGroup(group=1) LockGroup(group=2) LockGroup(group=3) SetGroup(group=2)Итого - 4 штуки.
Давайте подберем им символы. Конечно, можно взять любые из тех о которых говорилось выше. Но, чтобы лучше в них ориентироваться, подберем такие, чтобы их название хоть немного походили на соответствующие действия.
Обратите внимание, чтов названиях присутствуют - First_Group, Last_Group, Next_Group и Prev_Group.
Давайте считать, что для нашего случая
Тогда логично выбрать
Составим соответствующие "интерпретации". Напомню, что это нужно делать в файле типа xkb_compat и, соответственно, "приплюсовать" это файл к строчке в "полной" конфигурации, которая описывает именно xkb_compat (а не xkb_symbols).
Итак, наша "добавка" к xkb_compat (не забудьте ее "приплюсовать" куда надо) -
xkb_compat { interpret ISO_First_Group_Lock { action=LockGroup(group=1); }; interpret ISO_Next_Group_Lock { action=LockGroup(group=2); }; interpret ISO_Last_Group_Lock { action=LockGroup(group=3); }; interpret ISO_Group_Lock { action=SetGroup(group=2); locking = True;}; group 2 = AltGr; group 3 = AltGr; };(Последнии две инструкции, "по идее" не нужны, поскольку уже должны быть в "общей конфигурации". Но почему-то они иногда "теряются".)
А описание символов (в xkb_symbols) теперь будут выглядеть как
key <CAPS> { [ ISO_Next_Group_Lock ], [ ISO_First_Group_Lock ], [ ISO_Last_Group_Lock ] }; key <MENU> { [ ISO_Group_Lock ] };
Можно пробовать.
На этом наши эксперименты на тему переключателей не заканчиваются.
Дальше мы рассмотрим еще более сложный случай (и другие механизмы) -
Иван Паскаль [email protected]