tag:blogger.com,1999:blog-68273632805925329572024-02-08T08:24:51.703+02:00cat **/* | grep яAkshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.comBlogger1125tag:blogger.com,1999:blog-6827363280592532957.post-82177494510514357622005-11-21T16:50:00.003+02:002009-02-22T16:55:36.977+02:00Топчемся по интернациональным граблямЭта печальная и утомительная история началась с того, что я решил чуть-чуть поднастроить emacs, а именно мне хотелось иметь возможность выполнять команды привязанные к клавиатурным комбинациям без переключения на латиницу. Да и не плохо бы, чтобы каждый буфер emacs'a помнил свою раскладку, а раскладок то чтобы три штуки было (латиница, русская, украинская) и переключаться между ними не циклически (чтоб не задумываться о текущей раскладке и количестве нажатий для переключения в нужную). Естественно мне не хотелось у emacs'a иметь отдельный от всех остальных программ набор клавиатурных сочетаний для переключения языков. Одни и те же клавиатурные сочетания должны переключать языки как в emacs так и в остальных программах. По ходу мне захотелось, чтобы помимо emacs'a каждое иксовое окно находилось в своей раскладке и каким-то образом ее отображало. Неплохо бы еще, чтобы при переключении раскладок менялся словарик орфографический на соответствующий выбранному языку.<br /><br />Итогом моего двухдневного труда (помимо нервно изгрызанных ногтей) стало достижение всех выше указанных прихотей и теперь я свободно могу написать:<br />Hello native England!<br />Привет родная Россия!<br />Привіт рідна Україна!<br /><br />Итак, что мы хотим:<br />RAlt-1 латиница<br />RAlt-2 русский<br />RAlt-3 украинский<br />(RAlt - правый Alt)<br /><br />Для начала изучаем <a href="http://www.tsu.ru/~pascal/other/xkb/">документацию по xkb</a> (спасибо Ivan Pascal). Вдумчиво прочитав все от корки до корки пытаемся, сделать первые шаги в написании необходимой конфигурации и сразу выясняем, что setxkbmap ну никак не хочет грузить наши файлы лежащие в домашнем каталоге, ему обязательно их хочется видеть в /usr/-что-то-там-такое. Править системные файлы - очень плохое решение. Если копать дальше, то рано или поздно можно заметить вот такую строку из мана по setxkbmap:<br /><pre>setxkbmap -print us | xkbcomp - $DISPLAY</pre><br />Аха! Вот оно! Оказывается, с помощью xkbcomp можно не только компилировать конфигурации для xkb, но и сразу их загружать в xkb. Делаем: <pre>setxkbmap > ~/.local-xkb</pre> и берем полученный файл за основу нашей конфигурации.<br /><br />В конце-концов (потратив кучу нервов, наступив на множество граблей и попробовав несколько неработающих вариантов) для переключения раскладок с помощью задуманных клавиш создаем вот такой файл конфигурации: <pre><br />// Evgeny "Akshaal" Chukreev (C) 2005<br /><br />xkb_keymap {<br /> xkb_keycodes {<br /> include "xfree86+aliases(qwerty)"<br /> };<br /><br /> xkb_types {<br /> include "complete"<br /> };<br /><br /> xkb_compat {<br /> include "complete"<br /> };<br /><br /> xkb_symbols {<br /> include "pc/pc(pc104)+pc/us+pc/ru(winkeys):2+pc/ua(winkeys):3"<br /><br /> key <AE01> {<br /> overlay1 = <I71><br /> };<br /><br /> key <AE02> {<br /> overlay1 = <I72><br /> };<br /><br /> key <AE03> {<br /> overlay1 = <I73><br /> };<br /><br /> replace key <I71> {<br /> radiogroup=2, [F31],<br /> actions[Group1] = [ LockGroup(group=1) ]<br /> };<br /><br /> replace key <I72> {<br /> radiogroup=2, [F32],<br /> actions[Group1] = [ LockGroup(group=2) ]<br /> };<br /><br /> replace key <I73> {<br /> radiogroup=2, [F33],<br /> actions[Group1] = [ LockGroup(group=3) ]<br /> };<br /><br /> replace key <RALT> {<br /> [NoSymbol],<br /> actions[Group1] = [ SetControls(controls=overlay1) ]<br /> };<br /> };<br /><br /> xkb_geometry {<br /> include "pc(pc104)"<br /> };<br />};</pre><ad></ad><br />Как будет работать икскабэ по этому сценарию: При нажатии на RALT выполняется действие SetControls(controls=overlay1), включающее оверлейный режим. Теперь палец (не отпуская RALT) тянется к кнопкам 1, 2 или даже 3, но допустим к кнопке 2. При нажатии на кнопку два, клавиатура отсылает в икскабэ сканкод клавиши выглядящий в символьном обозначении как <AE02>. Икскабэ смотрит свои таблицы и находит, что для этого сканкода, при включенном оверлейном режиме, надо сделать вид, что клавиатура прислала сканкод с символьным обозначением <I72> (такое поведение определенно строкой overlay1 = <I72>, а сканкод клавиши в оверлейном режиме выбран таким образом, чтлбы клавиатура никогда сама такой сканкод не присала). Теперь икскабэ смотрит запись для сканкода <I72> и выполняет действие actions[Group1] = [ LockGroup(group=2) ], устанавливающее активной вторую группу (согласно этой строке 'include "pc/pc(pc104)+pc/us+p /ru(winkeys):2+pc/ua(winkeys):3"', вторая группа у нас pc/ru(winkeys)). Помимо прочего, икскабэ посылает приложению<br />событие о нажатии на кнопку со сканкодом I72 и символьным обозначением F32. Теперь мы отпускаем RAlt-2 и икскабэ сбрасывает оверлейный режим. Если кратко то все именно так и работает. Для сочетаний RAlt-1 и RAlt-3 все происходит аналогично, но RAlt-1 устанавливает группу 1 и посылает приложениям сообщение о нажатии F31, а сочетание RAlt-3 соответственно группу 3 и кнопку F33.<br /><br />Для использования этой конфигурации необходимо выполнить команду: <pre>xkbcomp ~/.local-xkb $DISPLAY</pre> (лучше всего прописать ее в ~/.xsession)<br /><br />На этом можно было бы и остановиться, но ведь еще нужно настроить емакс, а это самое интересное! Если настройка емакса не нужна, то в конфигурации xkb надо заменить символы F31, F32 и F33 на NoSymbol. Тогда переключение клавиатуры будет незаметным для приложений. Если замену не произвести и больше ничего не делать, то как минимум будут проблемы в шеле, который при нажатии получит загадочную ескэйп последовательность и вставит ее как есть в строку ввода. Но это я продолжил настройку емакса.<br /><br />Первым делом необходимо придумать как уберечь программы от событий нажатия на несуществующие клавиши F31, F32, F33 в момент переключения раскладок. Прослойкой между иксами и сервером является оконный менеджер, он первым отлавливает нажатия и если они не его, то отдает их дальше программе находящейся в фокусе. К счастью или несчастью имеющимся оконным менеджером является ion3. В нем мы может просто забиндить что-то на эти клавиши и все, приложение о нажатии ничего не узнает. Но ведь нам надо, чтобы емакс таки узнал о нажатии и произвел необходимые действия по переключению своей внутренней раскладки... Одним из вероятных решений была бы возможность забиндить нажатие таким образом, чтобы в момент срабатывания мы могли определить какое приложение находится в фокусе и при необходимости передать ему событие дальше или иначе проигнорировать нажатие. Ion3 поддерживает написание скриптов на языке lua и используя эту возможность мы <s>можем...</s> мы могли бы фильтровать событие нажатия, если бы не очередное, но: в ионе нет луа фукции для передачи приложению события о нажатии клавиши. Да, с помощью луа мы можем отловить нажатие на клавиши F31, F32, F33, мы можем определит какому приложению эти клавиши предназначаются, но мы не можем при надобности передать нажатие приложению. В поисках решения я натолкнулся на <a href="http://www.livejournal.com/users/besm6/427.html">пост</a> (за который спасибо) жж юзера besm6 (которого я кажется видел еще в свое время в Фидо). Артем решил проблему передачи нажатия через вызов<br />внешней простенькой утилитки. Взяв идею пишем вариацию на тему, для передачи произвольного сочетания произвольному иксовому окну: <pre><br />/* Evgeny Chukreev (C) 2005, GNU GPL */<br /><br />#include <X11/Xlib.h><br />#include <stdio.h><br />#include <stdlib.h><br />#include <string.h><br /><br />/* Main function */<br />int main (int argc, char *argv[])<br />{<br /> Display *display;<br /> XKeyEvent event;<br /> int window_id;<br /> char *keysym;<br /> int state;<br /><br /> /* Check arguments */<br /> if (argc < 4) {<br /> fprintf (stderr, "Usage: %s <window_id> <symbol> <state>\n", argv[0]);<br /> return -1;<br /> }<br /><br /> /* Parse arguments */<br /> window_id = strtol (argv[1], NULL, 0);<br /> keysym = argv[2];<br /> state = strtol (argv[3], NULL, 0);<br /><br /> /* Open display */<br /> display = XOpenDisplay (NULL);<br /> if (!display) {<br /> fprintf (stderr, "%s: Can't open display\n", argv[0]);<br /> return -4;<br /> }<br /><br /> /* Init event */<br /> memset (&event, 0, sizeof (event));<br /> event.window = window_id;<br /> event.display = display;<br /> event.root = RootWindow (display, DefaultScreen (display));<br /> event.state = state;<br /> event.keycode = XKeysymToKeycode (display, XStringToKeysym (keysym));<br /><br /> /* Send KeyPress event */<br /> event.type = KeyPress;<br /><br /> if (!XSendEvent (display, window_id, False, 0, (XEvent *) &event)) {<br /> fprintf (stderr, "%s: Can't send KeyPress event\n", argv[0]);<br /> return -2;<br /> }<br /><br /> XSync (display, False);<br /><br /> /* Send KeyRelease event */<br /> event.type = KeyRelease;<br /><br /> if (!XSendEvent (display, window_id, False, 0, (XEvent *) &event)) {<br /> fprintf (stderr, "%s: Can't send KeyRelease event\n", argv[0]);<br /> return -3;<br /> }<br /><br /> XSync (display, False);<br /><br /> /* Close display */<br /> XCloseDisplay (display);<br /><br /> /* That is all */<br /> return 0;<br />} </pre><br />Конечно, мы могли бы сделать из этого библиотеку для луа, там это очень просто, но лично я делать этого не стал, ибо луа меня раздражает еще больше чем перл, поэтому перебьется, хотя сделать библиотеку было бы правильнее.<br /><br />Имея возможность передать событие нажатия в программу, мы можем написать луа код для обработки F31, F32, F33. Вот что получается (aksendkey):<pre>-- Запускаем интернацианализирующие механизмы по кнопке<br />-- Соответственно разным приложениям можно посылать<br />-- разные значения групп и разные символы при переключении раскладки<br />function i18n_by_key (win, key)<br /> if obj_is (win, "WClientWin") then<br /> -- Реальное окно приложения<br /> pass_by_class_and_set_group (win, key, 0, 'Emacs', 0)<br /> else<br /> -- Фигня какая-то, оставляем в латинице<br /> ioncore.exec ("aklockxkbgroup -1 0")<br /> end<br />end<br /><br />-- Если класс окна win равен class, то посылаем ей символ keysym в состоянии<br />-- state и устанавливаем текущую группу в значение group.<br />-- Иначе ничего не делаем.<br />function pass_by_class_and_set_group (win, keysym, state, class, group)<br /> if win:get_ident().class == class then<br /> local windowid = win:xid ()<br /> ioncore.exec ("aksendkey " .. windowid .. " " .. keysym .. " " .. state)<br /> ioncore.exec ("aklockxkbgroup " .. windowid .. " " .. group)<br /> end<br />end<br /></pre>Здесь используется еще одна утилита, которая устанавливает текущую группу в заданное значение. Нужно это для того, чтобы при работе с emacs'ом не было двойного переключения раскладок (один раз через хкб, второй раз в емаксе). Вот она эта программа aklockxkbgroup: <pre><br />/* Evgeny Chukreev (C) 2005, GNU GPL */<br /><br />#include <X11/XKBlib.h><br />#include <stdio.h><br />#include <stdlib.h><br /><br />/* Main function */<br />int main (int argc, char *argv[])<br />{<br /> Display *display;<br /> int window_id, group, res;<br /><br /> /* Check arguments */<br /> if (argc < 3) {<br /> fprintf (stderr, "Usage: %s <window_id> <group>\n", argv[0]);<br /> return -1;<br /> }<br /><br /> /* Parse arguments */<br /> window_id = strtol (argv[1], NULL, 0);<br /> group = strtol (argv[2], NULL, 0);<br /><br /> /* Open display */<br /> display = XkbOpenDisplay (NULL, NULL, NULL, NULL, NULL, NULL);<br /> if (!display) {<br /> fprintf (stderr, "%s: Can't open display\n", argv[0]);<br /> return -2;<br /> }<br /><br /> /* Init XKB */<br /> res = XkbQueryExtension (display, NULL, NULL, NULL, NULL, NULL);<br /> if (!res) {<br /> fprintf (stderr, "%s: Can't init XKB\n", argv[0]);<br /> return -3;<br /> }<br /><br /> /* Set Focus */<br /> if (window_id > 0) {<br /> XSetInputFocus (display, window_id, RevertToParent, CurrentTime);<br /> XSync (display, False);<br /> }<br /><br /> /* Main fun */<br /> res = XkbLockGroup (display, XkbUseCoreKbd, abs (group % 4));<br /> if (!res) {<br /> fprintf (stderr, "%s: Can't lock group\n", argv[0]);<br /> return -3;<br /> }<br /><br /> XSync (display, False);<br /><br /> /* Close display */<br /> XCloseDisplay (display);<br /><br /> /* That is all */<br /> return 0;<br />} </pre> Теперь биндим луа функцию на клавиши и почти (хехе) все готово. Это прописывается в разделе WMPlex биндингов: <pre><br /> bdoc ("I18n: LAT mode."),<br /> kpress ("F31", "i18n_by_key (_sub, 'F31')"),<br /><br /> bdoc ("I18n: RUS mode."),<br /> kpress ("F32", "i18n_by_key (_sub, 'F32')"),<br /><br /> bdoc ("I18n: UKR mode."),<br /> kpress ("F33", "i18n_by_key (_sub, 'F33')"),<br /></pre> Таким образом при нажатии, например на RAlt-2, срабатывает наш луа скрипт, он проверяет и если текущее онко содержит емакс, то сбрасывает группу в 0 (латиница) и посылает емаксу событие о нажатии клавиши F32.<br /><br />Теперь пришло время научить emacs реагировать на приходящие к нему нажатия F31, F32, F33. Тут все почти просто. Загвоздка только в том, что я желаю, чтобы русская и украинская расскладки в емакс соответствовали аналогичным<br />в остальных программах. В конфигурации xkb прописаны (мои любимые) ru(winkeys) и ua(winkeys) - это такие же раскладки как и в виндовс. Так вот, в емакс 21.4 ничего подобного нет. То что там есть для русского языка еще куда ни шло, а украинская раскладка вообще фонетическая, логику которой<br />я не понимаю. Поэтому пришлось найти последнюю версию пакета leim и выдернуть оттуда файлик cyrillic.el. Кладем его в ~/elisp/updates/, компилируем через M-x byte-compile-file, добавляем в ~/.emacs строчку: <pre>(load-file "~/elisp/updates/cyrillic.elc")</pre><br /><br />И вот мы имеем методы ввода с названиями: russian-computer, ukrainian-computer. Для переключения методов ввода пишем в ~/.emacs подобный код: <pre><br />;; Клавиша перехода в режим латиницы<br />(define-key global-map [(f31)]<br /> `(lambda ()<br /> (interactive)<br /> (inactivate-input-method)<br /> (reset-flyspell-with-new-dict "american")<br /> ))<br /><br />;; Клавиша для перехода в режим русского языка<br />(define-key global-map [(f32)]<br /> `(lambda ()<br /> (interactive)<br /> (set-input-method 'russian-computer)<br /> (reset-flyspell-with-new-dict "russian")<br /> ))<br /><br />;; Клавиша для перехода в режим украинского языка<br />(define-key global-map [(f33)]<br /> `(lambda ()<br /> (interactive)<br /> (set-input-method 'ukrainian-computer)<br /> (reset-flyspell-with-new-dict "ukrainian")<br /> ))<br /><br /><br />;; Перезапуск проверки орфографии с новым словарем<br />(defun reset-flyspell-with-new-dict (dict)<br /> "Set new dictionary and restart flyspell"<br /><br /> ; Смена словаря<br /> (unless (equal dict ispell-local-dictionary)<br /> (setq ispell-local-dictionary dict)<br /> (when flyspell-mode<br /> (flyspell-mode)<br /> (flyspell-mode)))<br /><br /> ; Перепроверяем видимую область, если флай моде включен<br /> (when flyspell-mode<br /> (save-excursion<br /> (flyspell-region (window-start) (window-end))))<br /><br /> (message nil)<br />)</pre><br />Из код видно, что при переключении происходит орфографическая проверка видимой области буфера. На больших файлах теоретически она может сильно тормозить процесс переключения. В таком случаем можно убрать из кода этот участок: <pre><br /> ; Перепроверяем видимую область, если флай моде включен<br /> (when flyspell-mode<br /> (save-excursion<br /> (flyspell-region (window-start) (window-end))))<br /></pre> Все бы работало замечательно без очередного напильника, но это же GNU! Проблема: при переключении на ukrainian словарь емакс ругается, что такого словаря немає. Поэтому чтобы можно было переключится на украинский словарик пишем очередной код (записи о наличии украинского словаря в списке ispell-dictionary-alist по-умолчанию нет по понятным причинам о которых скажу дальше, но мы это исправляем приведенным кодом): <pre><br />;; Возможные словари<br />(setq ispell-dictionary-alist<br /> '(("american"<br /> "[A-Za-z]"<br /> "[^A-Za-z]"<br /> "[']"<br /> nil<br /> ("-B" "-d" "american")<br /> nil<br /> iso-8859-1)<br /><br /> ("russian"<br /> "[АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯабвгдеёжзийклмнопрстуфхцчшщьыъэюя]"<br /> "[^АБВГДЕЁЖЗИЙКЛМНОПРСТУФХЦЧШЩЬЫЪЭЮЯабвгдеёжзийклмнопрстуфхцчшщьыъэюя]"<br /> "[-]"<br /> nil<br /> ("-C" "-d" "russian")<br /> nil<br /> koi8-r)<br /><br /> ("ukrainian"<br /> "[абвгдґеєжзиіїйклмнопрстуфхцчшщьюяАБВГДҐЕЄЖЗИІЇЙКЛМНОПРСТУФХЦЧШЩЬЮЯ]"<br /> "[^абвгдґеєжзиіїйклмнопрстуфхцчшщьюяАБВГДҐЕЄЖЗИІЇЙКЛМНОПРСТУФХЦЧШЩЬЮЯ]"<br /> "[-']"<br /> nil<br /> ("-C" "-d" "ukrainian")<br /> nil<br /> koi8-u)<br /><br /> (nil<br /> "[A-Za-z]"<br /> "[^A-Za-z]"<br /> "[']"<br /> nil<br /> ("-B")<br /> nil<br /> iso-8859-1))<br />)<br /></pre><ad></ad> Уже лучше, но теперь при попытке переключится возникает сообщение о несуществующей кодировке koi8-u! Вот это уже настоящая проблема (собственного поэтому украинского словаря по-умолчанию и нет в списке доступных). Можно конечно поискать и даже наверное найти способ научить емакс новой кодировке. Наверняка цвс емакс эту кодировку знает и если оттуда выдрать нужный файл, то есть некоторая вероятность, что все заработает,<br />но мне уже лениво, и я все-таки доберусь до Киева и выделенки и сделаю таки apt-get install emacs-snapshot. А пока украинский словарик <<не працюе>>.<br /><br />Вобщем-то почти все сделано. Осталось решить две небольших (еще раз хехе) проблемы: при переключении между окнами должна восстанавливаться группа локальная для окна.<br /><br />Пробуем решить проблему через xxkb. Все замечательно, только эпизодически иконки отображаются белым цветом, когда над одним окном несколько таскбаров иконка пказывается не на том... и вообще, если запускать xxkb через .xsession, то он перестает запоминать группы (если запускать с консоли, то все ок), также он мешает работе нашей aklockxkbgroup (xxkb зачем-то по получению события о смене группы пытается выставить ее еще раз). Вобщем убив кучу времи, на поиск проблемы, мы забиваем на нее. Может вообще забить на запоминание группы для окна? Можно, только при переключении с любого окна в емакс нужно сбрасывать текущую раскладку в латиницу, чтобы в емаксе не было двойной перекодировки так сказать (заметно при нажатии shift-цифра). Можно было бы отловить в емаксе события смены фокуса, но судя по исходникам емакса и его документации так просто он это событие не выдаст, в общем у меня не получилось. К томуже, если даже отловить в емаксе событие получения фокуса, то придет оно в момент нажатия клавиши (такой вот он емакс мерзкий), что есть плохо так как мы должны были сбросить раскладку еще ДО нажатия клавиши. Отловить смену фокуса в ion'e тоже не так просто, если вообще возможно.. Поэтому единственный выход - писать программу, отслеживающую ход фокуса и сбрасывающую фокус в латиницу при достижении фокусом емакса. Но не торопимся. Посмотрим сначало <a hfre="freshmeat.net">свежее мясо</a> и найдем там xkbind - разработка некоего CHG. Судя по описанию, программа замечательная, судя по исходнику хорошо написанная и вообще прелесть. Но она не работает :). Обидно. Открываем исхоники, изучаем процесс передачи фокуса, материм ион, иксы и вообще все. Делаем такой патч для xkbind: <pre><br />diff -urN old/xkbind.c new/xkbind.c<br />--- old/xkbind.c 2005-01-03 21:10:21.000000000 +0200<br />+++ new/xkbind.c 2005-11-21 12:50:14.000000000 +0200<br />@@ -233,7 +233,9 @@<br /> }<br /> fprintf(stderr, "window: %d\n\n", ev.core.xfocus.window);<br /> #endif<br />- if(ev.core.xfocus.detail == NotifyNonlinear &&<br />+ if((ev.core.xfocus.detail == NotifyNonlinear ||<br />+ ev.core.xfocus.detail == NotifyAncestor ||<br />+ ev.core.xfocus.detail == NotifyInferior) &&<br /> (ev.core.xfocus.mode==NotifyNormal ||<br /> ev.core.xfocus.mode==NotifyWhileGrabbed) &&<br /> (group=GetWindowGroup(ev.core.xfocus.window))!=-1)<br /></pre> Теперь все замечательно отслеживается. В заголовке приписывается текущий<br />активный режим и тд.. все как и положено. Прописываем в ~/.xsession что-нибудь типа:<pre>xkbind -label0 '--L-- ' -label1 '==R== ' -label2 '##U## ' -defgrp 0<br /></pre><br />Все! Счастлив тот, кто не убьет столько времени на брожение по различным граблям,<br />прочитав это описание, сколько убил я не найдя подобного. %)<br /><br />Ну вроде ничего не забыл и описал все, что сделал.<br /><br />ЗЫ По ходу избавился полностью от остатков гнома (типа gnome-session) мешавших работать.Akshaalhttp://www.blogger.com/profile/05677582369584740657noreply@blogger.com1