Aim
Цель: Сделать переключение раскладки X Window и внутренней раскладки Emacs по одним и тем же комбинациям клавиш: Fn+j (английский), Fn+k (русский), Fn+l (итальянский).
X Window
Добавляем Option "XkbLayout" "us,ru(winkeys),it" в секцию "Input Device" в файл xorg.conf
emxkb
Смотрим http://akshaal.livejournal.com/58473.html, модифицируем код, получаем emxkb.c . Компилируем:
gcc -L/usr/X11R6/lib -lX11 -o emxkb emxkb.c
При исполнении emxkb 0 раскладка переключается на "us", emxkb 1 -- "ru(winkeys)", emxkb 2 -- "it". А если значение WM_CLASS окна равно строке "emacs", то emxkb шлет нажатие клавиш F31, F32 или F33 (в зависимости от параметра) и устанавливает раскладку для X Window на "us".
emxkb.c:
/* Thanks to Evgeny Chukreev (C) 2005, GNU GPL */
/* See http://akshaal.livejournal.com/58473.html */
/* Modified a little by Bzek .) */
/* Compilation: gcc -L/usr/X11R6/lib -lX11 -o emxkb emxkb.c */
/* Usage:
* emxkb 0 # For first group
* emxkb 1 # For second group
* emxkb 2 # For third group
*/
#include <X11/Xlib.h>
#include <X11/XKBlib.h>
#include <X11/keysym.h>
#include <X11/Xutil.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/* For locking a group */
int lock_group (int window_id, int group);
/* For sending a key to Emacs */
int send_key_to_emacs (Display *display, int window_id, int group);
/* Main function */
int
main (int argc, char *argv[]) {
Display *display;
XClassHint class_hint;
Window focus;
int revert, group;
/* Check arguments */
if (argc < 2) {
fprintf (stderr, "Usage: %s <group>\n", argv[0]);
return -1;
}
/* Parse arguments */
group = strtol (argv[1], NULL, 0);
/* Open display */
display = XOpenDisplay (NULL);
if (!display) {
fprintf (stderr, "%s: Can't open display\n", argv[0]);
return -2;
}
/* Get window id */
if (!XGetInputFocus (display, &focus, &revert)) {
fprintf (stderr, "%s: Can't get focus\n", argv[0]);
return -3;
}
/* Get WM_CLASS */
XGetClassHint (display, focus, &class_hint);
/* printf ("WM_CLASS: %s\n", class_hint.res_name); */
if (class_hint.res_name != NULL
&& !strncmp (class_hint.res_name, "emacs", strlen("emacs"))) {
/* Yeah, Emacs! Send a key. */
if (!send_key_to_emacs (display, focus, group)) {
fprintf (stderr, "%s: Failed during sending a key to Emacs\n", argv[0]);
return -4;
}
/* Change group to 0 (it should be "us") for Emacs */
group = 0;
}
if (!lock_group (focus, group)) {
fprintf (stderr, "%s: Failed during locking group\n", argv[0]);
return -5;
}
/* Close display */
XCloseDisplay (display);
/* That is all */
return 0;
}
int
send_key_to_emacs (Display *display, int window_id, int group) {
XKeyEvent event;
/* Init event */
memset (&event, 0, sizeof (event));
event.window = window_id;
event.display = display;
event.root = RootWindow (display, DefaultScreen (display));
event.state = 0;
switch (group) {
case 0:
event.keycode
= XKeysymToKeycode (display, XStringToKeysym ("F31"));
break;
case 1:
event.keycode
= XKeysymToKeycode (display, XStringToKeysym ("F32"));
break;
case 2:
event.keycode
= XKeysymToKeycode (display, XStringToKeysym ("F33"));
break;
default:
event.keycode
= XKeysymToKeycode (display, XStringToKeysym ("F31"));
break;
}
/* Send KeyPress event */
event.type = KeyPress;
if (!XSendEvent (display, window_id, False, 0, (XEvent *) &event)) {
fprintf (stderr, "send_key_to_emacs(): Can't send KeyPress event\n");
return -1;
}
/* Send KeyRelease event */
event.type = KeyRelease;
if (!XSendEvent (display, window_id, False, 0, (XEvent *) &event)) {
fprintf (stderr, "send_key_to_emacs(): Can't send KeyRelease event\n");
return -2;
}
XSync (display, False);
return 1;
}
int
lock_group (int window_id, int group) {
Display *xkb_display;
int res;
/* Open xkb display */
xkb_display = XkbOpenDisplay (NULL, NULL, NULL, NULL, NULL, NULL);
if (!xkb_display) {
fprintf (stderr, "lock_group(): Can't open display\n");
return -1;
}
/* Init XKB */
res = XkbQueryExtension (xkb_display, NULL, NULL, NULL, NULL, NULL);
if (!res) {
fprintf (stderr, "lock_group(): Can't init XKB\n");
return -2;
}
/* Set Focus */
if (window_id > 0) {
XSetInputFocus (xkb_display, window_id, RevertToParent, CurrentTime);
XSync (xkb_display, False);
}
/* Lock Group */
res = XkbLockGroup (xkb_display, XkbUseCoreKbd, abs (group % 4));
if (!res) {
fprintf (stderr, "lock_group(): Can't lock group\n");
return -3;
}
XSync (xkb_display, False);
/* Close xkb display */
XCloseDisplay (xkb_display);
return 1;
}
Window Manager
Запускаем xev, жмем Fn+j, Fn+k, Fn+l, получаем что-то типа:
state ..., keycode ... (keysym ..., KP_End), ...
state ..., keycode ... (keysym ..., KP_Down), ...
state ..., keycode ... (keysym ..., KP_Next), ...
То есть, для Fn+j (вероятно в вашем случае будет не KP_End) -- это KP_End, Fn+k -- KP_Down, Fn+l -- KP_Next
Теперь прописываем в window manager, чтобы по KP_End запускалось emxkb 0, по
KP_Down -- emxkb 1 и по KP_Next -- emxkb 2 .
xmodmap
Определеям keycode для F31, F32, F33:
xmodmap -e "keycode 241 F31"
xmodmap -e "keycode 242 F32"
xmodmap -e "keycode 243 F33"
На будущее добавляем в файл ~/.xmodmaprc строки:
keycode 241 F31
keycode 242 F32
keycode 243 F33
В ~/.xinitrc добавляем:
xmodmap ~/.xmodmaprc
Emacs
Добавляем в .emacs (или init.el) строки:
(defun reset-flyspell-with-new-dict (dict)
"Set new dictionary and restart flyspell"
(unless (equal dict ispell-local-dictionary)
(setq ispell-local-dictionary dict)
(when flyspell-mode
(flyspell-mode)
(flyspell-mode)))
(when flyspell-mode
(save-excursion
(flyspell-region (window-start) (window-end))))
(message nil))
(global-set-key [(f31)]
(lambda ()
(interactive)
;; (reset-flyspell-with-new-dict "american")
(inactivate-input-method)))
(global-set-key [(f32)]
(lambda ()
(interactive)
;; (reset-flyspell-with-new-dict "russian")
(set-input-method 'russian-computer)))
(global-set-key [(f33)]
(lambda ()
(interactive)
;; (reset-flyspell-with-new-dict "italian")
(set-input-method 'italian-keyboard)))
(defun toggle-specified-isearch-input-method (new-input-method)
"Toggle specified input method in interactive search."
(interactive)
(let ((overriding-terminal-local-map nil)))
(if (eq new-input-method 'default-method)
(inactivate-input-method)
(set-input-method new-input-method))
(setq isearch-input-method-function input-method-function
isearch-input-method-local-p t)
(setq input-method-function nil)
(isearch-update))
(add-hook 'isearch-mode-hook
(lambda ()
(define-key isearch-mode-map (kbd "<f31>")
(lambda ()
(interactive)
(toggle-specified-isearch-input-method 'default-method)))
(define-key isearch-mode-map (kbd "<f32>")
(lambda ()
(interactive)
(toggle-specified-isearch-input-method 'russian-computer)))
(define-key isearch-mode-map (kbd "<f33>")
(lambda ()
(interactive)
(toggle-specified-isearch-input-method 'italian-keyboard)))))
Для необходимости можно раскомментировать строки с вызовом (reset-flyspell-with-new-dict).
xxkb
Если есть желание использовать xxkb, то вот конфиг, с которым был протестирован весь этот "костыль":
XXkb.group.base: 1
XXkb.group.alt: 2
XXkb.mainwindow.type: normal
! possible values - normal, top, tray, wmaker
XXkb*label.text.1: En
XXkb*label.text.2: Ru
XXkb*label.text.3: It
XXkb.mainwindow.label.enable: yes
XXkb.mainwindow.enable: yes
XXkb.mainwindow.appicon: no
XXkb.mainwindow.geometry: 15x15-0+0
XXkb.mainwindow.label.background: yellow
XXkb.mainwindow.label.foreground: blue4
XXkb.mainwindow.label.font: -misc-*-r-*-13-*
XXkb.button.enable: no
XXkb.controls.add_when_start: yes
XXkb.controls.add_when_create: yes
XXkb.controls.add_when_change: no
XXkb.controls.two_state: no
XXkb.controls.button_delete: yes
XXkb.controls.button_delete_and_forget: yes
XXkb.controls.mainwindow_delete: yes
XXkb.ignore.reverse: no
XXkb.app_list.wm_class_class.alt_group1: emacs Emacs
Последняя строчка впрочем даже и не нужна.
Порой (у меня это случалось только с одним приложением и довольно редко) xxkb начинает капризничать и менять обратно раскладку, тогда помогает клик по значку xxkb (чудесатое решение :)).
В xmonad и некоторых других wm, по свидетельствам очевидцев, xxkb вообще не хочет работать с этим "костылем", не давая переключать раскладку с помощью emxkb. Если для вас это так, тогда стоит посмотреть в сторону другого индикатора раскладки клавитуры (например в сторону xneur).
Тестировалось под X.Org 6.7, xxkb (1.10 и 1.11), icewm, fluxbox, ion3, awesome.
При замечаниях и более элегантных решениях просьба оставить комментарий.
Спасибо Павлу Вязовому за помощь.
2 комментария:
Описал вариант решения проблемы с помощью scim, читать здесь.
Правильный формат для xmodmap:
keycode 241 = F31
Отправить комментарий