Добавить в избранное | Сделать стартовой страницей

Большая Linux библиотека для пользователей OS Linux и ПО для нее
Есть что сказать? Нужен совет? Посети наш форум.


При поддержке
Продвижение сайта
Создание сайта
Администрирование сервера
настройка сервера
Администрирование сервера
настройка сервера
Администрирование сервера
аренда сервера


X Keyboard Extension. часть 2

Автор : Иван Паскаль

Описание "действий".

Описание "действий" (actions) используются в файлах типа xkb_symbols, где они "привязываются" к скан-кодам клавиш, и в файлах типа xkb_compat, где они "привязываются" к управляющим символам. (Напомню, что в xkb_compat, описываются "интерпретации" - таблички, которые помогают менять привязку "действия" к скан-кодам, когда прикладные программы меняют привязку соответствующих "управляющих символов").

Описание действия похоже на объявление функции в языке C, то есть

имя_действия '(' список_аргументов ');'
Но отличие в том, что в списке аргументов указываются не просто значения, а пары имя-значение:
имя_аргумента '=' значение_аргумента
Например
 MovePointer(x=10, y=10, repeat=False);

Кстати программа xkbcomp, которая компилирует файлы конфигурации, во многих случаях понимает разные названия для одного и того же действия и разные имена для одного и того же аргумента.

Внутри XKB действия представляют собой некую структуру которая содержит

  • код (номер) действия;
  • поле битовых флагов, которые как-то модифицируют выполняемое действие;
  • поля содержащие аргументы действия (их может быть несколько или вообще ни одного)

Понятно, что количество и смысл аргументов зависит от конкретного действия. А вот флаги у многих действий совпадают по названию и по смыслу. При этом надо заметить, что в файлах конфигурации не все флаги можно указать явно.

Например некоторые действия имеют аргументы, которые могут быть как абсолютными значениями, так и приращениями к текущим значениям. Это могут быть координаты курсора на экране, номер группы XKB и т.п. Во внутренней структуре действия для различения этих двух типов аргументов предусмотрен специальный флаг Absolute. Но в описаниях функций он явно не указывается. О том какие значения вы имели ввиду (абсолютные или относительные) XKB догадывается по наличию знака "+" или "-" перед значением аргумента.
То есть

SomeAction(x=10, y=10)
означает абсолютные значения для x и y, а
SomeAction(x=+10, y=+10)
означает положительные приращения для тех же переменных.

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

Эти флаги в описаниях действий или вообще нельзя указать, или это указание делается через некий "псевдоаргумент" (в том смысле, что он не соответствует никакому полю во внутренней структуре XKB описывающей действие).
(У вас может возникнуть вопрос - зачем вообще существуют флаги, которые никак нельзя задать в описании? Дело в том, что в XKB протоколе существуют специальные запросы к серверу, которые позволяют загрузить или модифицировать любое действие из прикладной программы. Вот они то не накладывают никаких ограничений и позволяют задать любые флаги, имеющие смысл для данного действия.)

И еще несколько слов о том, как в описании действия указываются флаги. Хотя они предствляют собой отдельные биты в одном и том же поле внутренней структуры XKB, но в описании указываются как отдельные переменные типа boolean (то есть, их значением может быть только "да" или "нет"). При этом xkbcomp понимает в качестве значений логической переменной и другие слова - в качестве "да" вы можете писать yes, on, true, а качестве "нет" - no, off, false.

Но и это еще не все. Специально для логических переменных предусмотрена еще более простая запись. Вы можете написать просто имя переменной (флага) безо всякого значения и знака присваивания, это будет означать, что соответствующая переменная имеет значение "да". А вот если перед именем переменной поставить знак "!" или "~", это будет означать, что переменная имеет значение "нет".

Например следующие строчки полностью эквивалентны

SomeAction(..., SomeFlag=yes,  ...);
SomeAction(..., SomeFlag=on,   ...);
SomeAction(..., SomeFlag=true, ...);
SomeAction(..., SomeFlag,      ...);
и следующие строчки тоже эквивалентны
SomeAction(..., SomeFlag=no,    ...);
SomeAction(..., SomeFlag=off,   ...);
SomeAction(..., SomeFlag=false, ...);
SomeAction(..., !SomeFlag,      ...);
SomeAction(..., ~SomeFlag,      ...);

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

Действия, изменяющие состояние XKB.

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

Изменение набора модификаторов (modifiers).

Как я уже сказал, набор модификаторов распределен по трем переменным - base modifiers, latched modifiers и locked modifiers.
Соответственно действия для их изменения:

  • SetMods - меняет base modifiers,
  • LatchMods - меняет latched modifiers,
  • LockMods - меняет locked modifiers.

Основной аргумент у всех трех действий - modifiers (другое имя - mods). А его значение - название виртуального или реального модификатора. Если одно действие меняет сразу несколько модификаторов, их можно перечислить через знак "+". Например

SetMods(mods=Shift+Control);

Вместо названия модификатора можно указать специальное значение UseModMapMods (или просто ModMapMods). Это будет означать, что сами модификаторы надо взять из списка виртуальных модификаторов, связаных с этой клавишей (modmap и vmodmap).

Надо также заметить, что эти три действия отличаются не только тем, какую переменную они меняют. Они по разному работают в момент нажатия и в момент отпускания клавиши. Вспомните как отличается работа клавиш Shift и CapsLock. Первая должна действовать только пока ее удерживают в нажатом состоянии, то есть при ее нажатии модификатор Shift должен появиться, а при отпускании - автоматически исчезнуть. А вот CapsLock должна действовать долговременно - при первом нажатии ее модификатор должен стать активным и оставаться в таком состоянии даже после того как вы отпустите клавишу. А вот по повторному нажатию/отпусканию - убраться.

Так вот. Первые два действия предназначены для модификаторов типа Shift. То есть когда клавиша с таким действием нажимается, модификатор, указанный в аргументе добавляется в соответсвующую переменную (base или latched), а при отпускании клавиши то же действие выполняет обратную операцию - убирает модификатор.

А вот действие LockMods при первом исполнении только добавляет модификатор в locked modifiers, но не удаляет его при отпускании клавиши, а вот если модификатор уже установлен (то есть это уже повторное нажатие то же клавши), то при нажатии клавиши модификатор наоборот - убирается из locked modifiers.

Обратите внимание, что на самом деле совсем необязательно, чтобы вы использовали для модификатора Shift действие типа SetMods, а для модификатора Lock - LockMods. Вы можете "залокировать" Shift или наоборот - делать Lock активным только на время удержания клавши. Но это уже зависит от того, что вы собственно хотите этим добится. :-)

Также поведение первых двух действий могут слегка изменяться с помощью двух флагов - clearLocks и latchToLock.

Поэтому полное описание всех деталей этих действий выглядит так

Действие При нажатии При отпускании
SetMods Добавляет модификаторы в base modifiers
  • Убирает свои модификаторы из base modifiers
  • если clearLocks=yes и между нажатием и отпусканием этой клавиши вы не нажимали другие клавиши, то эти же модификаторы вычищаются и из locked modifiers
LatchMods Добавляет модификаторы в latched modifiers
  • Убирает свои модификаторы из latched modifiers
  • если clearLocks=yes и между нажатием и отпусканием этой клавиши вы не нажимали другие клавиши, то эти же модификаторы вычищаются и из locked modifiers
  • если latchToLock=yes, то те же модификаторы запоминаются в locked modifiers
LockMods
  • Добавляет модификаторы в base modifiers
  • если этих модификаторов нет в locked modifiers, то добавляет их туда, в противном случае наоборот - убирает
  • Убирает свои модификаторы из base modifiers
  • locked modifiers не меняется.

Изменение номер группы.

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

Примеры изменения конфигурации XKB.

Прежде всего, хочу заметить, что все решения, рассмотренные в примерах, не претендуют на "правильность".

Более того, многие из них я сам считаю или излишне "корявыми" (громоздкими), или "идеологически неправильными".

Но, с другой стороны, я не предлагаю готовые решения для всех проблем, а только хочу показать - чего можно добиться простым изменением текстовых "конфигов" XKB.

Во-первых, давайте решим вопрос -

Где будем экспериментировать?

Конечно, все изменения конфигурации можно сделать непосредственно в соответствующих файлах настройки XKB. Но это очень неудобно по нескольким причинам

  • всегда хочется иметь возможность "откатиться" к стандартной конфигурации, поэтому придется сохранять отдельно оригинальные файлы;
  • если в следующих версиях "иксов" эти файлы изменятся, то вам придется либо переносить свои изменения вручную, либо продолжать пользоваться старыми файлами;
  • ну и, наконец, если мы вносим изменения только в одно-два определения, зачем нам искать их каждый раз в "многоэтажном" тексте.

При этом xkbcomp позволяет легко "нанизывать" несколько файлов при описании одного компонента настройки XKB. Например, файл (блок) описания компонента xkb_types может выглядеть как

xkb_types {
  include "basic+pc+мои_типы+еще_один_полезный_тип";
};

Что означает -

  • взять описание из файла basic;
  • добавить к нему описание из файла pc;
  • добавить описание из файла мои_типы;
  • и, наконец, добавить еще описание из файла еще_один_полезный_тип.

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

Итак. Давайте все изменения/исправления/дополнения помещать в отдельных файлах и просто "приплюсовывать" эти файла к уже имеющимся.

Хотя, в некоторых случаях (особенно это касается добавлений в xkb_symbols), боле эффективным может оказаться не "приплюсовывание", а объявление добавляемого файла отдельной инструкцией - replace.

Напомню, что "плюсик" в инструкции include означает, что инструкции из файла будут добавляться в режиме override (см "Способ добавления".). А при переопределении клавиш часто требуется способ replace.
Поэтому, при добавлении в xkb_symbols, вместо одного длинного include лучше использовать конструкцию типа

xkb_symbols { include "en_US(pc104)"
                replace "my.symbols"
                replace "one_another_symbol" };

Теперь осталось решить вопрос - куда "приплюсовывать"?
Во-первых, напомню, что программа xkbcomp может "на ходу" поменять настройки XKB прямо в работающем X-сервере. Для этого вторым ее аргументом ("куда") нужно указать "X дисплей". Если вы работаете на той же машине, где и запущен X-сервер, то это выглядит как

xkbcomp ... :0.0

(можно еще проще - ":0")

А вот первым аргументом должен быть файл с описанием одного или нескольких компонентов настройки. Для того, чтобы одной командой загружать все необходимые компоненты настройки, давайте сначала составим файл с полным описанием всех компонентов, соответствующий вашей текущей конфигурации.

Это совсем не сложно, но зависит от того - какой способ задания полной конфигурации используется у вас в XF86Config (см. "Настройка XKB")
(Надеюсь, что у вас используется один из способов "в чистом виде", а не "каша" из всех возможных инструкций).

"Первый способ"

Если у вас используется первый способ - перечислением необходимых компонентов (keycodes, types, compat, symbols, geometry).

Просто скопируем из файла XF86Config все инструкции типа Xkb**** из секции "Keyboard" в наш файл. И слегка подправим.
Например, у вас там написано

  XkbKeycodes    "xfree86"
  XkbTypes      "default"
  XkbCompat     "default"
  XkbSymbols    "us(pc104)+ru"
  XkbGeometry   "pc(pc104)"

Надо -

  • во всех строчках заменить префикс Xkb на Xkb_ ("case" букв менять не обязательно),
  • вставить в каждую строчку инструкцию include,
  • получившиеся инструкции include "..." взять в фигурные скобки и закончить знаком ";",
  • и, наконец, добавить "обрамление" - xkb_keymap { .... }; .

Должно получится

xkb_keymap {
  Xkb_Keycodes  { include "xfree86" };
  Xkb_Types     { include "default" };
  Xkb_Compat    { include "default" };
  Xkb_Symbols   { include "us(pc104)+ru" };
  Xkb_Geometry  { include "pc(pc104)" };
};

Это и есть полное описание настройки XKB. Которое можно загружать в X-сервер, программой xkbcomp.

Все наши добавки мы можем "приплюсовывать" в соответствующие строчки этого описания.

"Второй способ"

Если у вас используется второй способ - указание полной keymap. В этом случае надо просто найти конкретную keymap и скопировать в наш файл.

Например, у вас в XGF86Config есть только строчка

XkbKeymap  "xfree86(ru)"

Она указывает на то, что полное описание лежит в файле {XKBROOT}/keymap/xfree86, в блоке "ru".

Находим этот файл. Находим в нем блок

xkb_keymap "ru" {
 ....
};

И "выкусываем" его оттуда. (Поскольку в нашем файле только один блок, название блока можно убрать).

Больше никаких исправлений не требуется.

"Третий способ".

Если у вас используется третий способ - через задание "правил", "модели", "схемы".

В этом случае все немного сложнее, поскольку непосредственно xkbcomp не понимает этот способ.

Однако, в этом случае можно "вручную" выполнить преобразование правил/модели/схемы в компоненты настройки (keycodes,symbols и т.п.).
Например, у вас в файле конфигурации написано

XkbRules "xfree86"
XkbModel        "pc104"
XkbLayout       "ru"
XkbOptions      "grp:shift_toggle"

Сначала надо найти файл "правил" (rules). Это будет файл {XKBROOT}/rules/xfree86.

В первой секции, которая после "шаблона"

! model = keycodes geometry

по вашей модели - "pc104" находим название файлов (блоков) для xkb_keycodes и xkb_geometry. Скорее всего это будет

xkb_keycodes - "xfree86"
xkb_geometry - "pc(104)"

Теперь, во второй секции, после "шаблона"

! model   layout  =       symbols

найдем по "модели" - "pc104" и "схеме" - "ru" подходящий файл для xkb_symbols.

Скорее всего, схема "ru" там не упомянута. Но зато есть правило

pc104     *       = en_US(pc104)+%l%(v)

где %l надо "заместить" названием "схемы" (layout), а %(v) - названием "варианта".
Поскольку "вариант" у вас не задан, то это правило "развернется" в

xkb_symbols - "en_US(pc104)+ru"

Следующая секция, после "шаблона"

! model   layout  =       compat  types

вообще очень простая

*        *       =       complete        complete

То есть, независимо от конкретных значений model и layout, и xkb_compat, и xkb_types надо брать из файлов "complete".
Таким образом, для нашего файла полной конфигурации значения

xkb_types  - "complete"
xkb_compat - "complete"

И, наконец, последняя секция, после "шаблона"

! option      =       symbols

указывает, что для нашей "опции" - grp:shift_toggle, к уже выбранному файлу для xkb_symbols надо "приплюсовать" еще и блок "group(shift_toggle)"

Теперь не забудьте добавить слова include, скобки в нужном месте и "обрамление" xkb_keymap { ... };

Должно получится

xkb_keymap {
  xkb_keycodes  { include "xfree86" };
  xkb_types     { include "complete" };
  xkb_compat    { include "complete" };
  xkb_symbols   { include "us(pc104)+ru+group(shift_toggle)" };
  xkb_geometry  { include "pc(pc104)" };
};

Это и есть наша рабочая "полная конфигурация", к которой можно писать "добавки" - исправления/дополнения.

Наконец, надо заметить, что делать все это (и полное описание и фалы-добавки) вы можете в отдельной директории, поскольку xkbcomp при "разборке" include сначала ищет файла в текущей директории, а только потом в "стандартном" месте - {XROOT}/lib/X11/xkb. Естественно, подразумевается, что мы при экспериментах запускаем xkbcomp, находясь в этой директории.

А вот потом, если вы решите, что "это хорошо", можно будет разложить файлы с исправлениями в соответствующие поддиректории (keycodes, types, symbols и т.д ) "домашней директории" XKB - {XROOT}/lib/X11/xkb. И подправить файл конфигурации X-сервера так, чтобы он при старте загрузил вашу конфигурацию.

Итак. Примеры изменения конфигурации XKB.

Новый тип для клавиши Enter.

Рассмотрим пример - для чего может понадобится новый тип. И что нужно сделать.

Итак. Проблема...
"По умолчанию" клавиша Enter относится к типу ONE_LEVEL, то есть выдает всегда один и тот же код (CR) при любых модификаторах.
Я же привык, что в "консольном режиме" эта же клавиша, нажатая вместе с Control меняет свое значение на LF (во всяком случае, так во FreeBSD).

Для того, чтобы и в XKB она вела себя так же, можно описать новый тип (сделаем для этого файл my.types и не забудем "приплюсовать" его в строчку, указывающую на файлы для xkb_types).

type "CTRL_DEPEND" {
   modifiers = Control;
   map[None] = Level1;
   map[Control] = Level2;
   level_name[level1] = "Base";
   level_name[level2] = "Control";
};

(В общем-то, имена уровней - level_name для работы XKB не нужны, но X-сервер очень не любит "неполные" определения.)

Cоответственно, в описании xkb_symbols надо изменить определение для клавиши Enter (это скан-код ).

Сделаем файлик my.symbols (не забудьте "приплюсовать" его куда надо) и поместим туда наше новое определение.

"Классическое" определение для (его можно найти найти, например, в файле symbols/us) выглядит как

key          { [Return] };

Нам надо указать, что эта клавиша имеет тип "CTRL_DEPEND", и добавить значение для появившегося второго "уровня" - Linefeed .

key       { type="CTRL_DEPEND", [ Return, Linefeed ] };

Осталось перегрузить конфигурацию командой xkbcomp. И убедиться, что все работает.

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

А теперь настало время сказать, что для данной задачи, новый тип не требуется.
( Я же предупреждал, что мои примеры не являются "хорошим" или "правильным" решением :-).

Обычно в загружаемых "конфигах" уже есть подходящий тип - "PC_BREAK". Он используется только для клавиши Break, но ничто не мешает "приписать" его и клавише Enter.

Поэтому достаточно составить только новое определение для скан-кода .

key         { type="PC_BREAK", [ Return, Linefeed ] };

Добавляем новую "старую" раскладку клавиатуры.

Рассмотрим - как добавить еще одну группу с другой расскладкой клавиатуры. 
Зачем это может понадобится? 
Ну, например, проблема (описанная в "Почему руссификация не работает?") - у вас есть программы, в "бинарниках", статически слинкованные, которые напрочь отказываются понимать коды типа Cyrillic_*. Можно специально для них изготовить раскладку, в которой будут не двубайтные коды русских букв, а однобайтные коды KOI8-R. 
Возможно, вам захочется добавить раскладку в кодировке cp1251 или еще какой-нибудь, которая отличается от стандартной расположением русских букв. 
(Надо заметить, что этот путь (добавление новой кодировки русских букв с помощью дополнительной группы) - в общем-то, плохое решение. Тем более, если вы не используете новую locale. Правильно было бы - добавить соответствующую таблицу перекодировки в Xlib и изготовить подходящую "иксовую" locale. 
Но, как я уже сказал, я не предлагаю правильные решения :-), а только привожу примеры - как это можно сделать.)
Итак, давайте в этом примере сосредоточимся на задаче - добавление "однобайтной koi8-r кодировки" для "старых" или "тупых" клиентских программ. 
Прежде всего, надо заметить, что у вас должны быть задействованы уже две группы. Первая - "латиница", вторая - "кириллица" с "правильными" кодами для русских букв (Cyrillic_*). 
Новую группу надо добавлять не "в конец" (как третью), а "в середину" - так чтобы она была второй, а "правильная" кириллица - третьей. 
Почему? Потому, что "традиционные" программы (со старой Xlib) понимают только первую и вторую группу. Причем, вторую выбирают тогда, когда установлен модификатор, соответствующий символу Mode_switch. Ну, об установке модификатора позаботится "таблица" совместимости XKB. Если она у вас стандартная, то соответствующий модификатор будет выставляться для всех групп, кроме первой (то есть, в нашем случае и для "старой" кодировки и для "новой"). 
А вот искать символы "старые" программы будут всегда только во второй группе (об остальных они даже не подозревают). А "новые" программы, совместимые с XKB и так найдут свою раскладку, будь она во второй, третей, или даже в четвертой группе. 
Еще одно замечание. Естественно, добавлять новые символы мы будем в xkb_symbols. При этом будет логично не писать ее "с нуля", а взять за основу уже существующий файл symbols/ru и дополнить его. Если мы наш файл "приплюсуем" к соответствующему описанию xkb_symbols, то у нас получится два фйла описания одних и тех же клавиш, при этом второй полностью переписывает первый. 
Поэтому, логично, если мы из описания вообще выкинем "стандартный" файл "ru", а оставим только свой. 
То есть соответствующая строчка в нашем "полном описании конфигурации" будет выглядеть не как 
xkb_symbols { include "en_US(105)+ru+new-ru" };
а немного короче 
xkb_symbols { include "en_US(105)+new-ru" };
Итак. Берем в свою директорию файл symbols/ru и начинаем его "корежить". 
Надо заметить, что, скорее всего в нем вы обнаружите три блока -
xkb_symbols "toggle" {...};
xkb_symbols "shift_toggle" {...};
xkb_symbols "basic" {...};
Причем, реально расположение русских букв описывает только третий, а первые два просто добавляют два разных способа преключения "рус/лат". 
Обычно, если у вас в полной конфигурации указан просто файл ru, загружается первый блок. И переключателем "рус/лат" становится клавиша CapsLock. 
Во-первых, для нашей задачи это очень плохо (то, как описаны символы для этой конопки). Но об этом поговорим немного позже. 
А сейчас я предлагаю просто выкинуть ("вычистить") два первых блока и оставить только блок "basic". А переключатель допишем потом прямо в блок "basic", или "приплюсуем" подходящий блок из файла symbols/group (в нем описано аж шесть разных способов переключения). 
Итак. Выкинули два первых блока и начали исправлять/дополнять блок "basic". 
Нам нужно для каждой кнопки, которая в описаниях содержит символы Cyrillic, дописать в середину (второй группой) еще одну группу с однобайтными символами в кодировке koi8. Напомню, что символы можно задавать не только символическими именами (типа Cyrillic_*), а просто цифровым кодом. Например, клавишу
key  { [               z,               Z      ],
                [     Cyrillic_ya,     Cyrillic_YA      ]       };

мы должны описать как 
key  {  [               z,               Z      ],
                [            0xd1,            0xf1      ],
                [     Cyrillic_ya,     Cyrillic_YA      ]       };
Естественно, первый код соответсвует маленькой букве, а второй - большой. 
Как подобрать коды? Ну, во-первых, по названию букв можно догадаться - какую русскую букву они имеют ввиду и, если у вас есть под рукой табличка - какой русской букве, какой код koi8-r соответствует, просто переписать оттуда. 
А во-вторых, могу подсказать, что младший байт кода Cyrillic на самом деле соответствует коду этой буквы в koi8, а в старшем байте всегда шестерка. 
Поэтому, можно взять файл, в котором описываются числовые значения для кодов типа Cyrillic_* - это файл /usr/X11R6/include/X11/keysymdef.h. И списать соответствующие коды оттуда, отбрасывая первую шестерку. 
Особо ленивые могут взять готовый файл здесь. 
Итак, мы составили новый файл описания клавиатуры, в котором теперь три группы. Надо не забыть о переключателе меджу группами. 
Во-первых, надо заметить, что все варианты переключателей используют для своих целей специальный символ - ISO_Next_Group, а его семантика, описанная в xkb_compat такова, что он просто перебирает все возможные группы. То есть, при нажатии клавиши (или комбинации клавиш) с таким символом просто текущее значение группы увеличивается на единицу, а когда счетчик доходит до последней группы, он просто возвращается на первую (см. "Внутренности":"Методы выравнивания номера группы"). 
Таким образом тем же самым перключателем "рус/лат" мы можем последовательно перебирать все три группы. 
Вы можете выбрать ваш любимый способ переключения из файла symbols/group и "приплюсовать" его к описанию xkb_symbols, например, 
xkb_symbols { include "en_US(105)+new_ru+group(shift_toggle)" };
Только одно замечание о переключении клавишей CapsLock. 
Дело в том, что традиционно на эту же клавишу "подвешивают" и символ Caps_Lock, чтобы она могла выполнять и свою основную функцию (нажатая с Shift'ом).
Проблема в том, что к этому символу присоединен реальный модификатор Lock. При этом, в конечном счете, XKB привязывает реальные модификаторы не к символу, а к скан-коду клавиши. Поэтому при нажатии этой клавиши в "состоянии модификаторов" появится не только модификатор, который указывает на то, что выбрана альтернативная группа, но и реальный модификатор Lock (хотя вы нажимаете клавишу как ISO_Next_Group, а не как Caps_Lock). 
В результате, клиентская программа увидит, что вы не только выбрали альтернативную группу, но "намертво" прижали Shift (хотя Shift должен отменять действие Lock, но... почему-то не работает). Естественно, при этом жми, не жми Shift - у вас всегда будут получаться только маленькие (или только большие буквы). 
Для того, чтобы этого не происходило, надо бы "отцепить" реальный модификатор Lock от этой клавиши. К сожалению, "привязка" модификатора к символу Caps_Lock "зарыта" глубоко в файлах, которые "инклюдятся" в en_US. А отменить это присвоение в дополнительных файлах уже нельзя. 
Поэтому, чтобы не "перелопачивать" все файлы, которые неявно включаются в нашу полную конфигурацию, лучше просто убрать символ Caps_Lock из описания клавиши . 
Если вам жалко расставаться с этой функцией - "подвесьте" ее на какую-нибудь другую клавишу. А если вы используете для "рус/лат" другой способ - то и описанной проблемы у вас не будет. 
Итак. Если ваш любимый способ переключения - клавиша CapsLock, то последнее, что нам надо сделать - не "приплюсовывать" этот способ из файла symbols/group (там эта клавиша с кодом Caps_Lock), а просто вписать в нашу новую раскладку определение для клавиши  - 
key  { [ISO_Next_Group] };
Теперь можно прегрузить конфигурацию программой xkbcomp и посмотреть результат. 
Кстати, забавно, что "старые" программы теперь работают когда у вас включена и вторая группа и третья. Потому, что они в обоих случаях видят в "состоянии модификаторов" модификатор, который указывает, что включена альтернативная группа, а коды символов всегда берут из второй группы. То есть, для них не заметна разница между двумя состояниями XKB (включена вторая или третья группы). 
Интересно, что некоторые "новые" программы, например - xterm, тоже правильно работают с обоими группами. Потому, что... фиг его знает - почему :-)
Единственное неудобство - сложное переключение групп (особенно, если вы не пользуетесь никаким индикаторами перключения групп). Очень непривычно, когда переключатель "рус/лат" вдруг обретает не два, а три состояния. 
А вот о том, как сделать переключение между тремя (и больше) группами более приятным, мы рассмотрим в следующем примере - 

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

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

                  

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

Ну, во-первых, давайте вообще забудем пока про символ 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). 
Таким образом, наш основной переключатель (давайте уже определимся, что это будет ), приобретает вид 
key  { 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" (скан-код ). Но, если у вас такой клавиши нет, то можно задействовать одну из парных клавиш (Shift, Control, Alt). 
Итак. Сочиняем "поведение" клавиши . 
Прежде всего - я думаю, что вам не хочется держать ее все время нажатой, пока это нужно. То есть, нам надо, чтобы по первому нажатию она "подняла" модификатор, а по второму - опустила.
Как раз это делает "действие" LockMods (в переменной locked modifiers). Напомню, что SetMods и LatchMods "держат" модификаторы только пока нажата клавиша. 
Итак, используем "действие" LockMods - 
key      { [ 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 и ведет себя в соответствии со своим пониманием этой клавиши. 
Поэтому, чтобы избежать всяких "побочных" эффектов, нам надо не только связать модификатор с нашей клавишей , но и "отцепить" этот реальный модификатор от других. Как я уже говорил в примере "добавление новой группы", "отцепить" модификатор, который был объявлен где-то "в глубинах" includ'ов, очень трудно. но, к счастью, они там привязываются не к скан-кодам, а к символам. Поэтому, если мы в своей конфигурации просто уберем эти символы из описаний скан-кодов, то связка "модификатор-символ" просто "повиснет в воздухе" и таким образом нам удасться "отцепить" модификатор. 
Итак. Давайте возьмем реальный модификатор Mod5. Он используется только для символа Scroll_Lock, а этот символ, в свою очередь, "подвешен" только на клавишу ScrollLock (скан-код ). Да и клавиша эта не самая "часто используемая". 
Теперь нам надо добавить в описание клавиши  виртуальный модификатор LevelThree. Добавить инструкцию modifier_map, который свяжет Mod5 с нашей клавишей. И, наконец, переопределить клавишу , чтобы в ней не было упоминания и символе Scroll_Lock. 
key     { virtualMods = LevelThree,
                [ NoSymbol],
                actions[Group1]=[ LockMods(modifiers=LevelThree) ]};

modifier_map Mod5 {  };

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
Сочиняем описание клавиш (пусть это будут те же  и ) 
key  { [NoSymbol],[NoSymbol],[NoSymbol],
            actions[Group1]= [ LockGroup(group=2) ],
            actions[Group2]= [ LockGroup(group=1) ],
            actions[Group3]= [ LockGroup(group=3) ] };

key         { [ 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  {   [ ISO_Next_Group_Lock ],
                [ ISO_First_Group_Lock ],
                [ ISO_Last_Group_Lock ] };

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

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

Предположим, что у вас раскладка с четырьмя группами (больше вам не позволит XKB). 
Причем, опять же, первая группа - "латиница". А остальные три - варианты "русской" раскладки (например - koi8, alt, cp1251). 
И опять же, нам хочется, чтобы основной переключтель имел только два состояниия - "лат"/"какой-нибудь рус". 
Попробуем придумать какой-нибудь переключатель для выбора одной из трех "альтернативных" раскладок. Понятно, что одним переключателем с двумя состояниями мы тут не обойдемся (хотя можно сделать один с тремя состояниями, но я его рассматривать не буду). 
Давайте поступим радикально - выберем три кнопки, и пусть каждая включает свою группу. 

Как это можно сделать?

Прежде всего, хочу заметить, что я не могу предложить вам раскладку с четырьмя группами (откуда я знаю - что вы в них хотите "вложить"). 
Но если вам надо только поэкспериментировать, можно взять одну из кнопок (не обязательно "буквенную") и "навесить" на нее четыре группы. 
Я сделал такую кнопку из клавиши "1". 
key  {        [   1, exclam ],
                [   2, exclam ],
                [   3, exclam ],
                [   4, exclam ]  };
(значение клавиши отражает текущий номер группы :-). 
Итак. 

Вариант первый - "выделенные скан-коды".

Возьмем за основу метод, рассмотренный в предыдущих примерах - две переменные для номера группы. 
Пусть у нас дополнительная переменная (например - base group) хранит номер альтернативной группы. Забудем пока про "гнусное поведение" base group (что она хранит значение только пока нажата соответствующая клавиша). 
Тогда пусть основной переключатель манипулирует значением в locked group. 
Он должен
  • для выбора "одной из рус" просто "обнулить" locked group
  • для "возвращения" в "лат" записать в locked group подходящую "добавку".
Для вычисления "добавки" на время перейдем к "внутренним представлениям номера группы" - 0,1,2... 
Тогда
  • группа "лат" - 0
  • первая "рус" - 1
  • вторая "рус" - 2
  • третья "рус" - 3
  • максимальное значение номера группы - 3
"Добавки" 
  • если текущая группа - 0, "добавка" - 0
  • если текущая группа - 1, "добавка" - 3
  • если текущая группа - 2, "добавка" - 2
  • если текущая группа - 3, "добавка" - 1
Возвращаясь к обозначениям групп из "конфигов", описание клавиши - основного переключателя получится 
key  {        [NoSymbol], [NoSymbol], [NoSymbol], [NoSymbol],
                actions[Group1]=[ LockGroup(group=1) ],
                actions[Group2]=[ LockGroup(group=4) ],
                actions[Group3]=[ LockGroup(group=3) ],
                actions[Group4]=[ LockGroup(group=2) ] };
Все хорошо. Но что нам делать с дополнительным переключателем (переключателями)? 
Давайте сформулируем - как должны работать наши переключатели. 
Итак, у нас три кнопки, каждая включает свою "рус". Поскольку они должны сохранять соответствующие значения в base group, мы должны повесить на них "действия" SetGroup (с подходящим аргументом). Естественно, поскольку base group "держит" значение только пока нажата соответствующая клавиша, наши кнопки должны быть "залипающими". Но при этом...
Если мы нажали кнопку, ответственную за первую "рус", она должна записать свое значение в base group и "залипнуть". Если мы после этого нажмем кнопку, отвечающую за вторую "рус", она должна 
  • "отлепить" "залипшую" кнопку
  • выполнить свое "действие"
  • и, в свою очередь, "залипнуть".
Аналогично должна работать и третья кнопка (из дополнительных переключателей). 
Но то, что я только что описал, точь в точь описывает поведение кнопок, принадлежащих одной "радио-группе". 
Ну и отлично! Объединим наши три дополнительных переключателя в радио-группу, подвесим на них SetGroup с соответствующими аргументами, и... все. 
Однако, "засада" в этом решении (как же без "засады"?) в том, что "залипает" именно клавиша (скан-код), а не отдельное ее "действие" (или символ). То есть, нам понадобятся три кнопки, которые будут использоваться только для этих целей и ни для чего больше, поскольку, даже если мы и "подвесим" на них еще какие-нибудь символы (ну, например, на другой shift level), то воспользоваться ими все равно не сможем. Так как - 
  • кнопка будет "залипать" в любом случае, независимо от того - какая группа и уровень выбраны;
  • если уж она "залипла", то использовать ее, пока не "отлипнет", невозможно.
Итак. Нам придется выделить на клавиатуре три кнопки, которые будут только переключателями "альтернативных" "русских" групп и ничем больше. 
(Не огорчайтесь, потом мы это ограничение снимем :-)
Я предлагаю взять кнопки F10, F11, F12. Они стоят рядом и используются сравнительно редко. 
Можно составлять их описание (это скан-коды ,,) 
key  {    radiogroup=2, [NoSymbol],
                actions[Group1]=[ SetGroup(group=2) ] };
key  {      radiogroup=2, [NoSymbol],
                actions[Group1]=[ SetGroup(group=3) ] };
key  {      radiogroup=2, [NoSymbol],
                actions[Group1]=[ SetGroup(group=4) ] };
Обратите внимание, что номер радио-группы должен быть больше единицы. Вообще-то, это "баг" XKB, тем более неприятный, что xkbcomp не будет "возражать" если вы объявите радио-группу номер 1, а вот X-сервер потом "сдуреет". 
Теперь можно пробовать. 
Надо заметить, что есть еще одно маленькое неудобство. В самом начале, пока еще не нажата ни одна из кнопок радио-группы, получается, что ни одна из альтернативных "рус" раскладок не выбрана и основной переключатель не будет ничего переключать.
Но после первого же нажатия одного из "дополнительных переключателей" все будет работать как задумано. (Если не считать того, что опять же - менять номер альтернативной группы надо только тогда, когда основной переключатель находится в положении "рус". Иначе результаты получаются не очень приятные). 

Вариант второй - "перекрытия".

Конечно, предыдущий вариант имеет очень серьезный недостаток - три клавиши "изымаются из общего пользования". 
Давайте, все-таки попытаемся вернуть их обратно.
Эта проблема легко решилась бы, если бы могли свободно добавлять на свою клавиатуру новые кнопки (не символы, а именно - физические кнопки). 
Хотя это и звучит как пустые фантазии, но не лишено смысла. Конечно, новую физическую кнопку мы на клавиатуре не сделаем, но вот скан-кодов к существующим кнопкам добавить можем. 
Напомню, что если кнопка принадлежит к "группе перекрытия" (overlay), она может эмулировать нажатие другой кнопки (с другим скан-кодом), естественно, эта другая кнопка не обязана физически присутствовать на клавиатуре и може быть вполне "виртуальной". К тому же, та кнопка, которая будет эмулировать "виртуальную" не обязана заниматься этим все время. Для того, чтобы он перешла в такой режим, нужно "поднять" "управляющий флаг" Overlay1 ( или Overlay2), а все остальное время она может ваполнять свои "основные обязанности". 
В свою очередь, "управляющий флаг" можно "выставить" с помощью специального "действия" (такого же как SetGroup или SetMods), причем можно сделать так, чтобы этот флаг "держался" только пока нажата соответствующая кнопка. 
Итак. Давайте сначала объявим скан-коды "виртуальных" кнопок. Заглянем в файл типа xkb_keycodes (скорее всего у вас это keycodes/xfree86). 
Можно заметить, что под реальные кнопки задействованы скан-коды от 9 до 120 (ну, на самом деле - даже меньше, в зависимости от типа клавиатуры). 
То есть, мы для своих нужд можем спокойно взять скан-коды, например - 121, 122, 123.
Сочиним файл-"добавку" к описанию keycodes (не забудьте его "приплюсовать" к полной конфигурации в строчку, описывающую xkb_keycodes). 
xkb_keycodes {
    = 121;
    = 122;
    = 123;
};
(напомню, что "название скан-кода" может быть произвольное, но не длинее 4 символов). 
Теперь мы можем наши "действия" убрать с реальных кнопок  -  и "повесить" на наши виртуальные (просто поменяем названия скан-кодов) - 
key  {    radiogroup=2, [NoSymbol],
                actions[Group1]=[ SetGroup(group=2) ] };
key  {      radiogroup=2, [NoSymbol],
                actions[Group1]=[ SetGroup(group=3) ] };
key  {      radiogroup=2, [NoSymbol],
                actions[Group1]=[ SetGroup(group=4) ] };
Дальше нам надо решить - на какие реальные кнопки мы возложим задачу эмулировать наши виртуальные клавиши. 
Поскольку это никак не "ущемляет" реальную кнопку (ее основные обязанности), выбирать мы можем так - как нам будет удобнее. Можно для этого выбрать "цифровые" кнопки - 1,2,3. А можно - какие-нибудь "буквенные", например - K (koi8), A (alt), W (Windows). 
Я остановлюсь на первом варианте.
Итак. Описание реальных кнопок (можно не писать их заново, а просто добавить нужную строчку в те описания, которые уже должны быть в нашей раскладке) - 
key  {    overlay1 = ,
                [       1,  exclam ],
                [  exclam,       1 ] };

key  {      overlay1 = ,
                [       2,      at ],
                [ quotedbl,      2 ] };

key  {      overlay1 = ,
                [   3,  numbersign ],
                [ apostrophe,    3 ] };
Осталось только выбрать кнопку, которая будет "махать флагом" и превращать наши реальные кнопки в виртуальные. В общем-то, это может быть не одиночная кнопка, а комбинация кнопок, но не забудьте, что вместе с этой "комбинацией" нам надо будет нажимать и одну из "виртуальных" кнопок (пальцев хватит?). 
Я остановлюсь на той же . 
Нам нужно "подвесить" на нее "действие" SetControls(controls=overlay1). Напомню, что это "действие" "держит" флаг только пока кнопка нажата. То есть, кнопки 1-2-3 нажатые вместе с Menu (Menu+1, Menu+2, Menu+3) будут действовать как дополнительные переключатели. А если кнопку Menu не трогать, то эти клавиши работают как обычно.
key  {        [NoSymbol],
                actions[Group1]=[ SetControls(controls=overlay1) ]};
Все. Можно попробовать. 
(Если это не будет работать, попробуйте заменить SetControls на LockControls. Естественно, в этом случае клавишу не надо удерживать в нажатом состоянии. По первому нажатию она будет "включать" управляющий флаг overlay1 и кнопки 1-2-3 превратятся в переключатели групп, а по повторному нажатию флаг overlay1 будет "сбрасываться" и кнопки 1-2-3 вернутся к своей "основной работе").
На этом я заканчиваю свои примеры, хотя можно было бы выдумать еще пару-тройку переключателей. 
Но, основные механизмы (и "подводные камни") я описал. Все остальное зависит от вашей фантазии. 

Почему руссификация через XKB не работает?

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

Кто виноват?

Что такое "русские буквы"?

Давайте не поленимся и рассмотрим проблему с самого начала. 
Под русским алфавитом в Юниксах обычно понимают кодировку KOI8-R (хотя иногда используется iso-8859-5 или "вариации на тему" KOI8-R - KOI8-U).
В этой кодировке русские буквы занимают коды 0xc0-0xff (в кодах 0x40-0x7f расположены латинские буквы).
Но в этом же диапазоне могут размещаться 
  • буквы национальных алфавитов стран Западной Европы (буквы с "шляпкой", "тильдой", "умлаутом" и т.п.)
  • греческий алфавит
  • арабский алфавит
  • и т.п.
То есть, на одно и то же место в "кодовой таблице" претендуют куча разных алфавитов. 
Попытки совместить весь этот "зоопарк" в X-Window, привели к тому, что под код символа в раскладке клавиатуры были выделены два байта. В старшем байте хранится некий номер определяющий charset (набор символов), а в младшем - собственно код символа. (В общем, идея та же что и у UNICODE, хотя это и не "уникод"). 
Кстати, "кириллическому" алфавиту (не обязательно "русскому") достались коды, у которых в старшем байте число 6 (признак "кириллицы"), а младший байт содержит код, который совпадает, как правило, с кодом буквы в koi8-r (хотя это и не так уж важно, как мы увидим в дальнейшем). 
Называются эти символы - Cyrillic_a, Cyrillic_be, Cyrillic_tse и т.д. Обычно про них говорят - Cyrillic коды. 
Итак. При нажатии клавиши, когда активна "русская" раскладка, в программу должны попадать двубайтные Cyrillic коды. 
Но проблема в том, что большинство программ внутри хранит строки (и выдает их на экран) как цепочку байтов. То есть, двубайтные коды на входе в программу должны быть преобразованы в однобайтные. При этом, естественно, часть информации (принадлежность символа к конкретному национальному алфавиту) теряется. 

Процедуры Xlib для преобразования кодов.

Для такого преобразования в Xlib существует две процедуры - XLookupString и XmbLookupString (mb означает - "multi byte"). В качестве аргумента этим подпрограммам передается "сообщение о нажатии" (или отпускании) клавиши (Key Press Event), а "на выходе" должен получаться однобайтный код символа. 
(Строго говоря, есть еще одна аналогичная процедура - XwcLookupString (wc означает - "wide char"). Но она используется крайне редко.)
Надо отметить, что в большинстве современных программ выбор конкретной процедуры происходит следующим образом. 
Процедура XLookupString более "древняя" и более "глупая". Процедура XmbLookupString более сложная и более гибкая, но в своей работе пользуется "input context'ом" текущего "окна". Не вдаваясь в подробности, можно сказать, что "input context" - это некий объект, описывающий особенности ввода (input) в "свое окно", и содержащий список "методов" для различных преобразований входных цепочек символов. 
Поэтому, большинство "иксовых" программ пытаются создать для своих "окон" input context. И, если им это удается, используют XmbLookupString, а если по каким-то причинам input context не может быть создан, то используют XLookupString. 
Так вот. Для того, чтобы избежать путаницы в алфавитах, обе эти процедуры ориентируются на "текущую locale" (которая, в частности, определяет "национальный" алфавит с которым в данный момент работает система). 
Если вы не знаете - что такое locale, могу порекомендовать сайт - "Locale AS IS", где это об этом рассказано достаточно подробно (и по русски!). 
Здесь замечу только, что ... 
  • программы обычно узнают о текущей locale из "переменных окружения" (environment) LANG, или LC_ALL, LC_CTYPE и т.п.;
  • для каждой locale в системе существует набор файлов, в которых содержится вся информация "зависящая от языка" (и, соответствено - алфавита);
  • в системе X-Window (а ее можно рассматривать как отдельную ОС), основными файлами, описывающим "категории locale" является файлы XLC_LOCALE, разложенные по отдельным поддиректориям в директории X11R6/lib/X11/locale. Каждая поддиректория соответствует одной конкретной locale.
  • процедуры Xlib в своей работе используют некоторые значения (какие именно - рассмотрим чуть позже), описанные в файле XLC_LOCALE.
Кроме того надо заметить, что не бывает "никакой locale". Если locale не задана "переменными окружения" или задана, но такая, что система затрудняется найти нужные файлы, то будет использоваться locale - "C". 
Так вот. При старте программа должна установить нужную locale (ниже мы поговорим об этом немного подробнее). Библиотека Xlib найдет соответствующий файл, описывающий эту locale. Процедуры *LookupString по этому описанию будут принимать решение - как преобразовать двубайтные коды (в нашем случае - Cyrillic коды) в однобайтные символы. 
Если заглянуть немного глубже в работу этих процедур, то можно заметить, что 
  • обе процедуры ориентируются на параметр locale (точнее, он назывется - класс) - encoding_name;
  • значение этого класса (для работы с Cyrillic) должно быть KOI8-R или ISO8859-5;
  • в зависимости от значения этого класса...
    • если он KOI8-R - Cyrillic превращаются в однобайтные коды koi8-r
    • если ISO8859-5 - Cyrillic превращаются в коды iso8859-5
    • если ни то ни другое - Cyrillic вообще ни во что не преобразуется. То есть "на выход" выдается строчка нулевой длины. Это и выглядит как "русские буквы не вводятся".
(Надо отметить, что эти названия "зашиты" в библиотке Xlib вместе с таблицами перекодировки. Поэтому никакими внешними файлами изменить их, или добавить новые, нельзя.) 
Кстати, если locale "никакая" (то есть - C), или encoding_name не определен, то нормально вводятся коды "национальных алфавитов Западной Европы", которые занимают то же место в кодовой таблице, что и koi8-r. А если нормально преобразуются коды Cyrillic, то наоборот - "подавляется" ввод "западноевропейских" символов. 
Кроме того, есть отличия в работе двух *LookupString. 
  • XLookupString прежде всего пытается взять encoding_name из "переменной окружения" _XKB_CHARSET. Если такая переменная есть, то используется ее значение и locale устанавливать не нужно.
  • XmbLookupString, напротив, не только обязательно требует правильной locale, но и еще использует в своей работе значение двух классов - encoding_name и ct_encoding (ct - "compaund text"). Поэтому, для ее нормальной работы, значения этих двух классов должно совпадать (по крайней мере - для "кириллицы").

Что значит - "установить locale"?

Конечно, об этом лучше прочитать в man'ах или уже упомянутой "Locale AS IS". 
Но я постараюсь вкратце описать основные моменты этого действа.
Во-первых, надо заметить, что существует "системная" locale (или "libc'ишная"), которая влияет на работу процедур libc, а не "иксов". 
В X-Window существует как бы "продолжение" этой locale - дополнительные файлы, в которых описываются параметры влияющие на работу процедур из Xlib. 
Для того, чтобы программа "настроила" libc под нужную locale, она вызывает в начале процедуру libc - setlocale(). 
Вызов это процедуры имеет три формы (возможно, так о них не говорят, но мне легче будет ссылаться) 
  1. setlocale(..., "ru_RU.KOI8-R") - в вызове явно указывается - какую locale требуется установить;
  2. setlocale(..., "") - в этом случае процедура пытается взять название соответствующей "категории locale" (вот что это такое, я объяснять не буду), заданной первым аргументом, из "одноименной" переменной окружения; если такой пременной нет, то из переменной окружения LANG, если и такой нет, то locale будет "никакая", то есть - "C".
  3. setlocale(..., NULL) - а вот это, скорее, не "set", а "get", поскольку она ничего не устанавливает, а наоборот - возвращает название locale, которое было установленно с помощью одной из первых двух форм вызова setlocale().
Итак. Для того, чтобы "установить locale" ("libc'ишную"), программа вызывает setlocale() в первой или второй форме. Обычно используется вторая форма, поскольку это позволяет пользователю гибко менять текущую locale с помощью переменных окружения. 
Если программа "иксовая", то есть использует Xlib, то при первых же вызовах процедур Xlib, зависящих от locale, происходит настройка "иксовой" locale. 
Надо заметить, что это делается автоматически. То есть никаких дополнительных вызовов не требуется. 
Соответствующие процедуры Xlib узнают название текущей "libc'ишной" locale с помощью "третей формы вызова" setlocale() (это важно!) и по этому названию пытаются найти соответствующий файл (XLC_LOCALE), содержащий "иксовые" компоненты locale. 
Для этого они ищут подходящую поддиректорию в файле X11R6/lib/X11/locale/locale.dir. Если там название не находится, то сначала пытаются "подменить" его с помощью файла X11R6/lib/X11/locale/locale.alias, а потом, опять же, найти с помощью locale.dir. (Содержимое этих файлов достаточно понятно без дополнительных пояснений.) 
Если и после этого "ничего подходящего" не находится, то используется "иксовая" locale "C" (хотя "libc'ишная" может быть другая).
Таким образом, для "настройки locale", как "системной", так и "иксовой", необходимо в начале программы вызвать "libc'ишную" процедуру setlocale(). 
И вот здесь есть одна тонкость... 

"Иксовая" setlocale().

Дело в том, что процедуры Xlib для того, чтобы узнать название "текущей locale" используют "третью форму" вызова "libc'ишной" setlocale(). 
Но, на тот случай, если в "системной" libc нет такой процедуры, в Xlib существует "заглушка" - (вообще-то, она называется_Xsetlocale()), которая может вызываться вместо системной setlocale(). 
Для того, чтобы работала "заглушка", библиотека Xlib должна быть собрана с "опцией" 
#define       X_LOCALE
(при этом вызовы setlocale() автоматически заменяются на вызовы _Xsetlocale()) 
Надо заметить, что "иксовая" setlocale(), хотя и вызывается точно так же, как и "системная" (те же три формы вызова), имеет некоторые отличия во "второй форме" (наиболее популярном способе установки "текущей locale"). 
  • Категории (первый аргумент) могут быть только LC_ALL или LC_CTYPE.
  • При этом название locale она пытается взять из переменных окружения LC_CTYPE и, если не получилось - LANG.
  • На переменную окружения LC_ALL она внимания не обращает (даже если первый аргумент - "категория" LC_ALL).
Ну и, естественно, эта "заглушка" устанавливает только "иксовые" параметры locale, а не "libc'ишные". 
Так вот. Проблемы могут возникнуть, если у вас в системе Xlib почему-то собрана с "опцией" X_LOCALE, хотя в libc соответствующая процедура имеется. 
Тогда вызов setlocale() из libc запомнит название locale в своих внутренних переменных, а процедуры Xlib будут спрашивать "текущую locale" у своей "заглушки", которая, естественно, ее не знает. 
Если уж у вас Xlib собрана с X_LOCALE, то и программы должны вызывать не "системную" setlocale(), а "иксовую". 
Для этого перед вызовом setlocale() должно стоять 
#define        X_LOCALE
#include 
Если Xlib "нормальная" (то есть ориентруется на "системную" setlocale), то этих строчек не нужно. (Хотя, конечно, понадобится #include ) 

Программы "правильные" и "неправильные".

Итак. Можно сказать, что программы могут быть "правильными" и "неправильными" в смысле "установки locale". 
"Правильные" программы вызывают в начале setlocale() и в них "русские буквы вводятся" (если, конечно, у вас переменные окружения указывают на существующий файл XLC_LOCALE). 
"Неправильные" программы "забывают" установить текущую locale (соответствено, в них используется locale "C") и в них "русские буквы НЕ вводятся" (хотя замечательно вводяться "западноеворопейские" буквы). 

Почему работают другие способы руссификации?

Кроме "руссификации через XKB" часто используются "старые" методы - "загрузка xmodmap" и "программы - переключатели клавиатуры" (xruskb, xes и т.п.). 
В общем-то, принципиальная разница между ними в том, что ... 
  • Xmodmap "честно" размещает русские буквы во второй группе таблицы символов (при этом используются функции core protocol'а, а не XKB) и определяет клавишу - переключатель между группами.
  • А программы-переключатели "сидят резидентом" в памяти и на каждое переключение раскладки, просто "нагло" переписывают первую группу таблицы символов.
А общее у них то, что они в качестве русских букв используют не коды Cyrillic, а их однобайтные аналоги. Которые, вообще-то, в X-Window отведены под символы национальных алфавитов Западной Европы ("умлауты", "тильды" и т.п.) 
То есть, "неправильные" программы (работающие с locale "C") воспринимают их как "родные". 
Проблемы как раз возникают с "правильными" программами, которые выставляют "кириллическую" locale. Но с ними, обычно борятся "шаманскими" методами - либо устанавливают "переменные окружения" так, чтобы они НЕ указывали на "кириллическую" locale, либо убирают из XLC_LOCALE строчку, описывающую encoding_name (именно по ней *LookupString "догадываются", что допустимыми являются только коды Cyrillic). 

Почему иногда вводятся "не те буквы"?

Надеюсь, вы поняли - почему бывает, что "русские буквы не вводятся" или "вводятся, но не везде" (не вводятся, если программа - "неправильная"). 
Почему же бывает, что "вводятся, но не те"? 
Ну, во-первых, возможно у вас программа использует "не те шрифты" (fonts). 
В обычных "фонтах" на месте "русских" букв расположены "западноевропейские" (которые имеют те же коды, то и русские в koi8-r).
Во-вторых, может быть, что у вас неправильно настроена locale (переменными окружения). То есть, она указывает на "русский" XLC_LOCALE, но не для кодировки KOI8-R, а для ISO8859-5. Такая ситуация может быть, если вы используете "сокращенное" название locale (ru, ru_RU, ru_SU, russian), которое в файле locale.alias указывает на locale "ru_RU.ISO8859-5". 
И, наконец, как это ни странно звучит, возможно, что ошибка в Xlib. Дело в том, что в XFree86 3.3.3 как-раз в Xlib содержится ошибка. Из-за которой Cyrillic коды перекодируются в iso8859-5, если encoding_name - KOI8-R (если encoding_name ISO8859-5, то вообще ничего не получится, поскольку они, в свою очередь, перепутаны там с Arabic). 

Что делать?

Так что же делать? 
Давайте сначала найдем хотя бы одну программу, которая работает с "XKB руссификацией" правильно. Что делать с "неправильными" программами решим потом.
Лучше всего взять программу xterm, если, конечно, она у вас из того же "комплекта", что и X-сервер. То есть собрана с "текущей Xlib" и с теми же "опциями", что и Xlib. 

Настройка системы.

Если у вас клавиатура уже руссифицирована каким-нибудь "старым" способом (с помощью xmodmap или программ-руссификаторов xruskb, xes и т.п.), то уберите их. Они будут только мешать. Во всяком случае, если будет "очень надо", вернете их потом. 
Короче, позаботьтесь о том, чтобы у вас не было русского .Xmodmap в домашней директории (и/или в {XROOT}/lib/X11/xinit/) и никакие xruskb не стартовали бы автоматически при запуске "иксов". 
Итак. Прежде всего убедитесь, что у вас правильно установлены "русские фонты". Для этого нужно взять текст, "набитый" в koi8 (например, в какой-нибудь "консольной" программе). Если он нормально читается в xterm, то шрифты установлены "правильные". (Как установить "кириллические" шрифты я здесь описывать не буду. В Интернете достаточно инструкций на эту тему.) 
Дальше. Проверьте, что у вас правильно установлена locale. Обычно она устанавливается переменной окружения LANG. Убедитесь, что значение этой переменной указывает на существующие файлы для "libc'ишной" locale (в /usr/share/locale) и "иксовой" locale (через locale.dir и, если нужно, locale.alias). 
Не полагайтесть на результаты выдачи команды locale. В некоторых случаях она может показывать не совсем то, что и процедура setlocale() вызываемая внутри прикладных программ. 
Если будут проблемы, можете воспользоваться моей тестовой программкой testXlc. 
Она определяет
  • "системную" locale,
  • locale для Xlib,
  • полный путь до файла XLC_LOCALE,
  • значения некоторых классов "иксовой" locale,
  • в том числе - "encodingName" (которое влияет на работу *LookupString).
Для "сборки" этой программы надо последовательно набрать команды 
xmkmf
make
Надо также заметить, что эта программа использует внутренние процедуры Xlib (не предназначенные для вызова в прикладных программах). Поэтому, нет гарантии, что она будет "собираться" и правильно работать во всех версиях XFree. Во всяком случае, она должна давать требуемый результат в версиях 3.3.2, 3.3.3, 3.3.3.1. 
Далее. Можно проверить, что XKB действительно переключается на "русскую" раскладку и выдает коды Cyrillic. 
Для этого можно воспользоваться программой xev (если ее нет в вашей системе, то можно найти ее в XFree86-contrib). 
Вообще-то, это "универсальный тестер" "событий" (X events). Ее надо запустить из под xterm. При нажатии кнопок она должна писать что-то вроде этого 
KeyPress event, serial 21, synthetic NO, window 0x5800001,
    root 0x25, subw 0x0, time 3744190622, (533,270), root:(610,437),
    state 0x2000, keycode 38 (keysym 0x6c6, Cyrillic_ef), same_screen YES,
    XLookupString gives 0 characters:  ""

KeyRelease event, serial 21, synthetic NO, window 0x5800001,
    root 0x25, subw 0x0, time 3744190755, (533,270), root:(610,437),
    state 0x2000, keycode 38 (keysym 0x6c6, Cyrillic_ef), same_screen YES,
    XLookupString gives 0 characters:  ""
Обратите внимание, что если у вас клавиатура перключена в "русский режим", то в третей строчке сообщения должно быть название Cyrillic кода клавиши 
Кстати, xev - "неправильная" программа. Если сделать ее "правильной", добавив в начале вызов (и "пересобрав" ее) 
setlocale(LC_CTYPE, "");
то она будет показывать и результат трансляции Cyrillic кодов в koi8-r (ну, или iso8859-5), например - 
KeyPress event, serial 21, synthetic NO, window 0x5800001,
    root 0x25, subw 0x0, time 3744190622, (533,270), root:(610,437),
    state 0x2000, keycode 38 (keysym 0x6c6, Cyrillic_ef), same_screen YES,
    XLookupString gives 1 characters:  "ф"

KeyRelease event, serial 21, synthetic NO, window 0x5800001,
    root 0x25, subw 0x0, time 3744190755, (533,270), root:(610,437),
    state 0x2000, keycode 38 (keysym 0x6c6, Cyrillic_ef), same_screen YES,
    XLookupString gives 1 characters:  "ф"
Ну и, наконец, надо убедится, что у вас не XFree86 3.3.3 (хотя с этого надо было начинать :-). 
(Надо бы написать простенький "тестер", который проверяет правильность работы *LookupString при заведомо правильной encoding_name. Но пока сойдет "подправленная" xev. Она, кстати, использует только XLookupString.)
Итак, если 
  • у вас стоят "кириллические" шрифты;
  • программа testXlc показала, что название "системной" и "иксовой" locale совпадают, и значение "encodingName" - KOI8-R;
  • xev показывает Cyrillic коды;
  • "поправленный" xev транслирует их в нормальные koi8-r коды;
то система у вас настроена. И "правильные" программы должны работать нормально. 
А вот что делать с "неправильными" программами - разговор отдельный... 

Что делать с "неправильными" программами?


                  

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

Программы, имеющие отношение к XKB.

xkbcomp

Это самая важная программа. Во всяком случае, она отрабатывет каждый раз при старте "иксов" для того, чтобы перевести файлы конфигурации XKB в бинарный формат, понятный самому X-серверу. (Как понятно из названия, это - "XKB compiler"). 
Но более полезно то, что ее можно вызвать и "вручную". При этом, она может выполнять и обратное преобразование - из бинарной формы в "человекочитаемый" конфигурационный файл. И что самое полезное - она может обмениваться данными (записывать и читать) непосредственно с работающим X-сервером. 
То есть, ее можно использовать для того, чтобы в ходе работы загрузить новую конфигурацию XKB в X-сервер, или наоборот - прочитать текущую конфигурацию и перевести ее в "читабельный" формат. 
Последнее тоже важно. Поскольку полная конфигурация складывается из содержимого многих файлов/блоков (не забудьте, что в любом файле может быть 'include'), понять - что же должно получится в результате такого "сложения" - не так-то просто. С помощью xkbcomp можно "вытащить" из X-сервера "суммарную" конфигурацию (в виде полной xkb_keymap). 
Полную информацию о xkbcomp можно прочитать в соответствующем man'е. 
Поэтому, приведу лишь примеры - как можно "обменяться информацией" с X-сервером. Вообще-то, если в качестве источника или приемника данных надо указать X-сервер, это можно сделать (как сказано в man X) в форме
hostname:dispalay_number.screen_number
В простейшем случае, когда X-сервер запущен на вашей же машине, причем - только один и с одним "скрином", эта последовательность выглядит как ":0". 
Таким образом, для чтения текущей конфигурации XKB можно использовать команду 
xkbcomp :0 outfile
(можно даже не указывать outfile, в этом случае программа сама сочинит имя для выходного файла, типа - server-0.xkb). 
Обратите также внимание на ключи -a (all) и -dflts (defaults). С ними выходная информация будет более полной. 
Соответствено, для загрузки новой конфигурации можно использовать команду 
xkbcomp xkbfile :0
Подразумевается, что xkbfile в этом случае - файл типа xkb_keymap. Хотя он может и не содержать непосредственно в себе все необходимые описания (как это получается в выходном файле при чтении из X-сервера), а только указывать с помощью include файлы-компоненты полной конфигурации. 
Кстати, эти файлы xkbcomp ищет сначала в текущей директории и, если не находит, то в "базе данных" конфигураций (X11R6/lib/X11/xkb/). 
Примеры составления своих файлов конфигурации для "подсовывания" их xkbcomp можно посмотреть в "Примеры изменения конфигурации XKB: Где будем экспериментировать." 

setxkbmap

Еще одна утилита, которая позволяет "на ходу" поменять конфигурацию XKB. 
Пожалуй, главное ее преемущество перед xkbcomp в том, что она понимает задание конфигурации в виде rules-model-layout... (xkbcomp этих слов не понимает).
С другой строны, setxkbmap может брать данные только из файлов "базы данных" настроек (причем, только на той же машине, где запущен X-сервер). То есть, если вы составите свой файл конфигурации, то прежде чем воспользоваться setxkbmap, необходимо поместить этот файл в нужное место (в соответствующую поддиректорию) "базы данных". (Кстати, setxkbmap еще и не понимает входные данные в виде xkb_keymap, хотя это просто "глюк" программы.) 
Кстати, если будете с ее помощью менять русскую раскладку, не забудьте, что файл ru и все его "вариации" обычно содержать только "частичное" описание клавиатуры. 
Если вы выполните команду типа 
setxkbmap -symbols ru
то результат будет очень неприятным. У вас "отсохнут" все "небуквенные" клавиши, включая Esc, BackSpace, Enter (!) и т.д. 
Поэтому, правильная команда должна выглядеть как 
setxkbmap -symbols "en_US+ru"
Самое полезное применение этой программы - "сброс" XKB в исходное состояние после неудачных экспериментов со своими файлами конфигурации. :-) 
Если запустить ее без параметров, то она прочитает ту конфигурацию, которая описана в XF86Config и загрузит ее.
Более подробно об этой утилите можно прочитать в соответствующем man'е, а краткую сводку ключей можно получить по команде 
setxkbmap -?

xmodmap

Как ни странно, но эта программа тоже может использоваться для работы с XKB. Просто, она работает с клавиатурным модулем с помощью "старого" протокола (core protocol). Но поскольку XKB "из соображений совместимости" этот протокол понимает, то вполне может воспринять таблицы, загружаемые с помощью xmodmap. 
Естественно, используя xmodmap, вы лишаетесь всех дополнительных возможностей XKB. Но для простейших исправлений раскладки клавиатуры ее вполне можно использовать. 
Единственное, что я бы не советовал - использовать широкораспростаненные файлы для руссификации клавиатуры в формате xmodmap (.Xmodmap). Почему этого делать не стоит, написано в "Почему руссификация через XKB не работает". 
Подробности об этой программа читайте в соответствующем man'е. (Но если вы не знаете - что такое xmodmap, то ... лучше и не знать :-). 

xkbwatch

Эта утилитка показывает текущее состояние "виртуальных" и "реальных" модификаторов. К сожалению, она имеет очень "скромную внешность" (никаких подписей - только какие-то безымянные "лампочки") и, к тому же, остутствует какое-либо описание к ней. (Похоже, авторы писали ее "только для себя", но зачем-то засунули в дистрибутив. :-) 
На самом деле, если знать - что именно она показывает, xkbwatch может оказаться весьма полезной при отладке своих файлов конфигурации. 
Итак, восполняя пробелы в описании, привожу табличку, поясняющую значение индикаторов xkbwatch 
base O O O O O O O O
latched O O O O O O O O
locked O O O O O O O O
effective O O O O O O O O
реальные O O O O O O O O
Mod5 Mod4 Mod3 Mod2 Mod1 Control Lock Shift

xkbvleds и mxkbledpanel

Утилита xkbvleds предназначаена для отображения состояния виртуальных индикаторов XKB (в том числе и тех, которые имеют "реальные" аналоги в виде светодиодов на клавитатуре). 
К сожалению, она выполнена в том же аскетичном стиле (никаких подписей), что и xkbwatch. И точно так же не имеет никакого описания. 
Поэтому я не буду ничего о ней рассказывать (хотя она даже имеет какие-то ключи), а посоветую забыть о ней и, если хочется чего-то подобного, воспользоваться другой аналогичной утилиткой - mxkbledpanel. 
Эта утилита имеет более "человеческое лицо". Все индикаторы на ней подписаны (естественно - те, которые реально используются; напомню, что в XKB может быть до 32 виртуальных индикаторов, но это не значит, что все они задействованы в конкретной конфигурации). 
Кроме того, эта программа позволяет осуществлять и "обратную связь", если это предусмотренно в конфигурации (подробнее - смотрите в "Немного о внутренностях XKB":"Индикаторы" и "Описание поведения индикатора":drivesKeyboard). То есть, "щелкая мышью" по индикатору можно включать/выключать его и таким образом менять соответствующее состояние XKB. (Кстати, благодаря этому, mxkbledpanel может служит простейшим индикатором-переключателем "РУС/ЛАТ"). 
Недостатки этой программы в том, что она во-первых требует для сборки Motif (хотя должно хватить и lesstif'а), а во-вторых - не входит в дистрибутивы XFree. Найти ее можно, например, на - ftp://ftp.x.org/pub/unsupported/Motif/mxkbledpanel/ 

xkbevd и xkbbell

Судя по названию, xkbevd - "XKB event daemon". Как сказано в man xkbevd, "эта команда очень 'сырая'... мы предлагаем ее скорее как 'черновой прототип' для разработчиков, а не как инструмент для 'конечных юзеров'". 
В основном она принимает bell event'ы и может служить "музыкальной приставкой" (подробнее смотрите в "Внутренности XKB: Расширенные возможности 'пищалки'"). 
Правда сам xkbevd звуки не воспроизводит. В его конфигурационном файле можно указать имя программы для воспроизведения звуков и соответствия между "именем звука" и аудио-файлом, который надо передать в качестве аргумента этой программе. 
А вот программа xkbbell служит "тестером" для "расширенных возможностей 'пищалки'". Она просто посылает запрос модулю XKB для воспроизведения звука. Причем, с ее помощью можно заказать как обычный "писк" (с помощью ключа -force), так и "музыкальный фрагмент", указав в качестве аргумента "имя звука". 
С помощью дополнительных ключей можно симитировать запрос от конкретного приложения (окна).
Как я уже сказал, xkbbell служит скорее "тестером", чем полезной утилитой. Хотя ее можно использовать и в своих "скриптах", как команду для проигрывания звуков (естественно, должна быть запущена хоть какая-нибудь XKB-совместимая "музыкальная приставка"). 
Подробности о xkbevd можно почитать в соответствующем man'е. А для xkbbell никакого описания нет. Правда, можно посмотреть хотя бы список ключей, запустив ее с ключем -help. 

xkbprint

Эта утилита генерирует картинку (в формате postscript'а) изображающую клавиатуру. При этом она использует как описание геометрии (xkb_geometry), так и "назначение" клавиш. То есть, кроме отрисовывания самой геометрии клавиатуры, она еще и подписывает - какой символ генерируется каждой клавишей. 
Особой пользы от нее я не вижу. Разве что - проверить правильность описания геометрии (собственно, кроме этой утилитки ее никто и не использует), да еще увидеть на картинке расположение сразу всех символов. 
Подробности - в man xkbprint. 

Программа для редактирования раскладки - xkeycaps.

Вообще-то, нормальной программы для создания/редактирования раскладок клавиатур в формате XKB - не существует. 
Но при желании, для этой цели можно использовать "редактор xmodmap'ов" - xkeycaps. Обратите внимание, что она позволяет на каждую клавишу "подвешивать" до 8-и символов. Что вполне соответствует "четырем группам по два уровня" в терминах XKB. 
И хотя она и сохраняет результат вашей работы в формате xmodmap, преобразовать его в формат файла типа xkb_symbols достаточно просто. Можете воспользоваться моим "скриптом" modmap2xkb, который делает это автоматически (конечно, программка очень "сырая". Если у кого-то есть желание ее улучшить - welcome). 
Не могу не заметить, что xkeycaps в нынешнем виде - довольно плохой редактор. И дело даже не в том, что она не работает с файлами XKB и, соответственно, не может использоваться для редактирования xkb_types, xkb_compat и т.п. и, кроме того, не использует "геометрию" из xkb_geometry (все "геометрии" "зашиты" в самой программе). 
Основные ее недостатки в том, что 
  • нет групповых операций - перемещения символов сразу всех (или выбраных) клавиш в другую группу
  • вообще нет "вырезания-вставки" даже для одной клавиши
  • нет возможности "подгружать" раскладки из файлов (даже в формате xmodmap). Выбирать можно только из тех раскладок, которые "вшиты" в саму программу.
  • и наконец - при сохранении результата она сама сочиняет имя файла (если вы хотите наделать несколько раскладок, вам после каждого сохранения придется вручную переименовывать файл)
Короче, xkeycaps представляет собой скорее забавный "эмулятор клавиатуры", чем полноценный "редактор раскладок". 

Индикаторы-переключатели.

Несмотря на то, что XKB сам может поддерживать несколько (до четырех) различных раскладок и легко переключать их "на ходу", ему очень не хватает подходящей программы для удобной манипуляции раскладками и отображения текущего состояния ("лампочек" на клавиатуре явно недостаточно для индикации состояния, если раскладок больше двух). 
Кроме того, большинство пользователей уже привыкло к "индикаторам-переключателям" (типа xrus), которые позволяют переключать раскладки не только с клавиатуры, но и "щелчком мыши" и, к тому же, отображать "текущий язык" иконкой на дисплее. 
Что же из таких программ можно посоветовать для работы "в паре с XKB"? 
Надо заметить, что популярные "переключалки" типа xruskb, xes, cyrx, kikbd (кого я еще забыл упомянуть?) для этого плохо подходят. 
Во-первых, они используют раскладки "в старом стиле", то есть не с кодами Cyrillic, а "западноевропейскими" буквами. А это вызывает проблемы у "правильных" программ при корректно установленной locale (подробнее об этом в "Почему руссификация через XKB не работает?").
А во-вторых, все они не используют возможности XKB для переключения раскладок, и "на каждое телодвижение" сами перезагружают раскладки в X-сервер.
"Настоящими" XKB-переключателями являются - 
  • kkb - http://www.logic.ru/peter
  • FvwmKb - http://linuxfan.com/~sparrow/software/
  • fookb - http://linux.piter-press.ru/fookb/
  • xxkb - http://www.tsu.ru/~pascal/other/xxkb/
Не желая никого обидеть :-), все-таки выскажу некоторые замечания относительно этих программ (возможно - несколько запоздалые). 

kkb

"Честная" Xkb-переключалка. Хотя в последних версях автор несколько отошел от "идеи XKB" и добавил "подгрузку" раскладок в процессе работы. Но надо заметить, что сделано это средствами XKB, то есть наиболее оптимальным способом. 
Главный недостаток - для сборки требуется "тулкит" Qt. 

FvwmKb

Главное достоинство этой программы - она запоминает состояние клавиатуры ("язык") для каждого открытого приложения и автоматически переключает его при изменении фокуса окна. Кроме этого, состояние клавиатуры отображается на "обрамлении" окон - иконкой, цветом рамки и т.п. 
Главный недостаток - она "заточена" для работы с конкретным window manager'ом - Fvwm-2 (хотя со временем, возможно, будут версии и для других wm). 

fookb

Очень простая и "легкая" переключалка. Основное достоинство - "легкость". Для сборки достаточно стандартных библиотек и, кроме того, из всех собратьев она - самая компактная. 
Недостаток - опять же - простота. :-) Все остальные программы имеют какие-либо "фичи", недоступные fookb. 

xxkb

Себя сильно хвалить не буду :-). 
Замечу только, что основное достоинство как и у FvwmKb - индивидуальное состояние для каждого окна. Но в отличии от FvwmKb - работает с любым wm.
Недостаток - довольно "аскетичный" вид. Выбор раскладки осуществляется просто перебором "иконок", а не с помощью "меню".