Рейтинг@Mail.ru
[Войти] [Зарегистрироваться]

Наши друзья и партнеры

UnixForum
Беспроводные выключатели nooLite

Lines Club

Ищем достойных соперников.




Книги по Linux (с отзывами читателей)

Библиотека сайта или "Мой Linux Documentation Project"


Previos Contents Next

"Вариации на тему" переключатели "рус/лат" (и еще раз - "рус").

Предположим, что вы выполнили предыдущий пример или еще откуда-нибудь взяли (сделали) раскладку клавиатуры с тремя группами.

И вам не нравится способ переключения между группами - три положения у одного переключателя "лат/рус/рус".

Что можно сделать?

Ну, во-первых, давайте вообще забудем пока про символ 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] };
Вот теперь можно пробовать - что получилось.

Конечно, это не самое лучшее решение.

  • Во-первых, из-за того, что мы заняли реальный модификатор Mod5 (кстати, и отмена ScrollLock не во всех случаях спасает).
  • Во-вторых, наш дополнительный переключатель сам группы не меняет, а только "машет флагом". Поэтому, если переключение "из лат. в рус" работает всегда, то для переключения "из одного рус. в другой рус." надо не только нажать дополнительный преключатель, но и "щелкнуть" основным.
  • Ну и, наконец, если мы захотим задействовать все четыре группы, то решение будет еще сложнее (понадобится как минимум два виртуальных модификатора, с которыми и так "напряженка").
Поэтому рассмотрим еще один способ -

Третий способ (через дополнительную переменную номера группы).

Вспомним еще раз - в чем проблема. В том, что основной переключатель должен переходит из одного состояния (текущая группа - 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).
Тогда у нас

  • первая группа ("лат") - 0
  • вторая группа ("старый рус.") - 1
  • третья группа ("новый рус.") - 2
  • максимальный номер группы - 2
При этом
  • если к 1 добавить 2 - получится снова 0 (3 на единицу больше, чем "максимальный номер группы")
  • аналогично, если к 2 добавить 1 - снова вернемся к группе 0.

Тогда алгоритм работы переключателей мог бы быть таким -

  • в дополнительной переменной (base group) запоминается номер одной из русских раскладок - то есть, 1 или 2;
  • для того, чтобы перейти из состояния, когда текущая группа "лат", в выбранную "рус", надо просто в locked group записать 0;
  • а для того, чтобы вернуться в "лат" из состояния "рус." надо в переменную base group записать -
    • 2, если текущая группа 1;
    • 1, если текущая группа 2.

Однако, не все так просто. Если мы попытаемся воплотить это в конфигурацию клавиши, то нарвемся на очередную "засаду".

Дело в том, что значения номера группы "запоминаются" только в locked group. В других переменных (base и latched) они "держаться" только пока соответствующая клавиша нажата и "испаряются" оттуда, если клавишу отпустить.

Правда, у нас есть одна "лазейка". Мы можем изменить поведение клавиши (той, что будет менять base group) и сделать ее "залипающей". Тогда XKB при первом нажатии/отжатии клавиши выполнит только ту задачу, которая выполняется при нажатии клавиши, а при повторном нажатии/отжатии наоборот - только то, что выполняется при отжатии клавиши.

Только обратите внимание, что когда мы ее все-таки "отожмем" там получится 0. То есть, мы не сможем держать там "2 или 3", а только "2 или 0", "1 или 0" и т.п.

Ну и ладно. Придется в ней держать не "номер алтернативной раскладки", а только "добавку" к номеру. То есть - не "2 или 3", а "0 или 1".

Тoгда нам придется слегка подправить наш алгоритм -

  • в дополнительной переменной (base group) запоминается "смещение" от первой "русской" раскладки - 0 или 1;
  • для того, чтобы перейти из состояния "лат", в нужную "рус", надо в locked group записать 1;
  • для того, чтобы вернуться в "лат" из состояния "рус." надо в переменную base group записать -
    • если текущая группа 1 (в base 0) - 0;
    • если текущая группа 2 (в base 1) - 2.

Теперь, переходя к обозначениям из "конфигов" (group1, group2 и т.д.), получим -

  • дополнительный переключатель:
    • ну, 0 или Group1 там получится автоматически при "отжатии" клавиши
    • "запоминать" в base group надо Group2,
  • основной переключатель:
    • "действие" для Group1 - записать в locked group значение Group2
    • для Group2 - записать Group1
    • для Group3 - записать Group3

Сочиняем описание клавиш (пусть это будут те же <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.

Давайте считать, что для нашего случая

  • First_Group - group=1
  • Next_Group - group=2
  • Last_Group - group=3

Тогда логично выбрать

  • для LockGroup(group=1) - ISO_First_Group_Lock
  • для LockGroup(group=2) - ISO_Next_Group_Lock
  • для LockGroup(group=3) - ISO_Last_Group_Lock
  • для SetGroup(group=2) - ISO_Group_Lock

Составим соответствующие "интерпретации". Напомню, что это нужно делать в файле типа 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 ] };

Можно пробовать.

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

Еще несколько "переключателей".


Иван Паскаль pascal@tsu.ru


Previos Contents Next


Эта статья еще не оценивалась
Вы сможете оценить статью и оставить комментарий, если войдете или зарегистрируетесь.
Только зарегистрированные пользователи могут оценивать и комментировать статьи.

Комментарии отсутствуют