Почему руссификация через 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". Если locale не задана "переменными окружения" или задана, но такая, что система затрудняется найти нужные файлы, то будет использоваться locale - "C".

Так вот. При старте программа должна установить нужную locale (ниже мы поговорим об этом немного подробнее). Библиотека Xlib найдет соответствующий файл, описывающий эту locale. Процедуры *LookupString по этому описанию будут принимать решение - как преобразовать двубайтные коды (в нашем случае - Cyrillic коды) в однобайтные символы.

Если заглянуть немного глубже в работу этих процедур, то можно заметить, что

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

Кстати, если locale "никакая" (то есть - C), или encoding_name не определен, то нормально вводятся коды "национальных алфавитов Западной Европы", которые занимают то же место в кодовой таблице, что и koi8-r. А если нормально преобразуются коды Cyrillic, то наоборот - "подавляется" ввод "западноевропейских" символов.

Кроме того, есть отличия в работе двух *LookupString.

Что значит - "установить 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").

Ну и, естественно, эта "заглушка" устанавливает только "иксовые" параметры locale, а не "libc'ишные".

Так вот. Проблемы могут возникнуть, если у вас в системе Xlib почему-то собрана с "опцией" X_LOCALE, хотя в libc соответствующая процедура имеется.

Тогда вызов setlocale() из libc запомнит название locale в своих внутренних переменных, а процедуры Xlib будут спрашивать "текущую locale" у своей "заглушки", которая, естественно, ее не знает.

Если уж у вас Xlib собрана с X_LOCALE, то и программы должны вызывать не "системную" setlocale(), а "иксовую".

Для этого перед вызовом setlocale() должно стоять

#define	X_LOCALE
#include <X11/Xlocale.h>

Если Xlib "нормальная" (то есть ориентруется на "системную" setlocale), то этих строчек не нужно. (Хотя, конечно, понадобится #include <locale.h>)

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

Итак. Можно сказать, что программы могут быть "правильными" и "неправильными" в смысле "установки locale".

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

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

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

Кроме "руссификации через XKB" часто используются "старые" методы - "загрузка xmodmap" и "программы - переключатели клавиатуры" (xruskb, xes и т.п.).

В общем-то, принципиальная разница между ними в том, что ...

А общее у них то, что они в качестве русских букв используют не коды 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.
Она определяет

Для "сборки" этой программы надо последовательно набрать команды

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.)

Итак, если

то система у вас настроена. И "правильные" программы должны работать нормально.

А вот что делать с "неправильными" программами - разговор отдельный...

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


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


Иван Паскаль [email protected]