Linux で SandS の実現を目指す : xf86-input-evdev の修正(メモ)
追記(2011年10月8日)
定家さんのコードの2.6.2がリリースされました。
https://gitorious.org/at-home-modifier/pages/Home
詳細は以下のURLで。
http://gitorious.org/at-home-modifier/at-home-modifier/blobs/raw/master/README
追記(2011年2月21日)
定家さんがこの記事のコードの改良版をgitに公開(下のURL)してくださいました。
http://gitorious.org/at-home-modifier
しっかりとしたREADMEなどが付随しているので、現状ではそちらのコードを使用したほうが良いでしょう。
SandSって?
SandS とは、入力システムの機能で
Spaceを押すとShiftで離すとSpaceというもの。
SKKとの相性が抜群です。
現状
少なくとも2つの方法が存在している。
(1) キーボードカスタマイズの魅力 — ありえるえりあ
キーボードドライバ xf86-input-keyboard に修正を加える。
(2) http://www.yagi.sh/blog/archives/352
XGrabKeyboard でキー入力を横取りし XTest でフェイクキーを送る
(1) は xf86-input-keyboard ではなく、 xf86-input-evdev を使用しているのでそのままでは使用不可。
(2) は正常動作しない。auto repeat を切ってもなぜか、押しっぱなしにしていると、
2回イベントが送られてくる。
環境
- OS ArchLinux
- kernel 2.6.32.9
- xorg-server 1.7.5
- xf86-input-evdev 2.3.2
作戦会議
汎用性が高いのは(2)の方法か。しかし、XGrabKeyboardで横取りしちゃうって
なんかこわい。しかも、うまくいかない理由がよく分からない。
となると、残るは、(1)の方法を evdev に対応させる、となる。
evdev って何よ?
Xでホットプラグ(お手軽接続:再起動等無しで、外部装置を接続、認識する)を行うためのドライバー。
マウスとキーボードを担当する。
ちなみに、ホッツプラグは hal と dbus を利用しているんだとか。
でも、hal って廃止予定だったような。
より、詳しくは以下の ArchLinux の Wiki で
Xorg - ArchWiki
で、できそうなの?
(1)をもうすこし詳しく見てみる。
kbd.c と xf86OSkbd.h の両方を変更している。
kbd.cでの変更箇所
- 関数 AddMadKey の追加
- 関数 KbdPreInt の変更
- xf86FindOptionを使い、AddMadKeyを行う
- 関数 InitKBD の変更
- lastScanCode, stickyPhase の初期化
- 関数 PostKbdEvent の変更
- tmpScanCode, lastScanCode のローカル変数宣言
- Sticky, Psedo, One-Shot 等の機能追加
xf86OSkbd.hの変更
- 構造体 MadKeyの追加
- 構造体 KbdDevRec, *KbdDevPtr に lastScanCode, stickyScanCode, stickyPahse, madKeyListの追加
結論
できるよ、多分。
つぎに対応箇所を evdev から探す。
EvdevRec, *EvdevPtr がドライバかと、こいつに内部変数を持たせればよさそうな雰囲気である。
イベント処理関数の関係(大雑把)。
EvdevProcessEvent | +--EvdevProccessKeyEvent | | | +--EvdevProcessButtonEvent | | | +--EvdevQueueKbdEvent--[Code] pQueue = &pEvdev->queue[pEvdev->num_queue] | +--EvdevProcessSyncEvent | +--EvdevPostQueuedEvents | +-- EV_QUEUE_KEY -> xf86PostKeyboardEvent
これを見ると、xf86PostKeyboardEventを呼び出している、EvdevQueueKbdEventに
Sticky, Psedo, One-Shot 等の機能追加部分を足せばよさそうである。
初期化関数の関係(大雑把)
EvdevInit | +--EvdevAddKeyClass | +--SetXkbOption-- xf86SetStrOption EvdevPreInit | +-- EvdevSetCalibration +-- EvdevAddDevice +-- EvdevMBEmuPreInit +-- EvdevWheelEmuPreInit +-- EvdevDragLockPreInit
EvdevAddKeyClassでlastScanCode, stickyPhase の初期化する感じか。
ところで、EvdevKeyPreInitがない件について。
多分、オプション設定の読み込みとかは EvdevPreInit でやるはず。
if (pEvdev->flags & EVDEV_KEYBOARD_EVENTS)
とかすればよいか。
で、パッチができた。
http://dl.dropbox.com/u/662567/xf86-input-evdev-2.3.2-mad-key.patch
結果、ほとんど、(1)と同じ。
あとは、これを適用してコンパイルすればよい。
ArchLinux では ABSを利用するのが楽。以下 PKGBUILD。
pkgname=xf86-input-evdev pkgver=2.3.2 pkgrel=1 pkgdesc="X.org evdev input driver" arch=(i686 x86_64) url="http://xorg.freedesktop.org/" license=('custom') depends=('glibc') makedepends=('pkgconfig' 'xorg-server>=1.7.0' 'inputproto>=2.0' 'randrproto>=1.3.1') conflicts=('xorg-server<1.7.0') options=('!libtool') groups=('xorg-input-drivers') source=(${url}/releases/individual/driver/${pkgname}-${pkgver}.tar.bz2 ${pkgname}-${pkgver}-mad-key.patch) build() { cd "${srcdir}/${pkgname}-${pkgver}" patch -p0 -i ${srcdir}/${pkgname}-${pkgver}-mad-key.patch || return 1 ./configure --prefix=/usr || return 1 make || return 1 make DESTDIR="${pkgdir}" install || return 1 install -m755 -d "${pkgdir}/usr/share/licenses/${pkgname}" install -m644 COPYING "${pkgdir}/usr/share/licenses/${pkgname}/" || return 1 } md5sums=('b2bfe368022eedf2671ee28daba31efc' 'b414ce6ea7c63195730c3b7767609217')
オプションを有効にする。
/etc/X11/xorg.conf に何か書いても有効にはなりません。
evdev をつかっているので、オプション等は hal 経由になる。
といことで、/etc/hal/fdi/policy/10-keymap.fdi に書く。
<merge key="input.x11_options. PseudoModSpace" type="string">50</merge>
これは、/etc/X11/xorg.conf に以下を書いたのと同様。
Option "PseudoModSpace" "50"
現在の 10-keymap.fdi の内容。
<?xml version="1.0" encoding="ISO-8859-1"?> <!-- -*- SGML -*- --> <deviceinfo version="0.2"> <device> <match key="info.capabilities" contains="input.keymap"> <append key="info.callouts.add" type="strlist">hal-setup-keymap</append> </match> <match key="info.capabilities" contains="input.keys"> <merge key="input.x10_driver" type="string">evdev</merge> <merge key="input.x11_options.XkbRules" type="string">xorg</merge> <merge key="input.x11_options.XkbModel" type="string">jp106</merge> <merge key="input.x11_options.XkbLayout" type="string">jp</merge> <merge key="input.x11_options.XkbVariant" type="string"></merge> <merge key="input.x11_options.XkbOptions" type="string">terminate:ctrl_alt_bksp</merge> <merge key="input.x11_options.PseudoModSpace" type="string">50</merge> </match> </device> </deviceinfo>
参考
参考にしたもの。
kernel26/src/linux-2.6.32/Documentation/input.txt
キーボードカスタマイズの魅力 — ありえるえりあ
http://www.yagi.sh/blog/archives/352
http://www.aya.or.jp/~takuo/input_subsystem/input_subsystem.html
XorgInputHOWTO
patch
一応、パッチを全部のせておく。
diff -crN src.orig/evdev.c src/evdev.c *** src.orig/evdev.c 2010-03-06 06:58:52.897183337 +0900 --- src/evdev.c 2010-03-06 10:29:38.118835311 +0900 *************** *** 137,142 **** --- 137,170 ---- * cannot be used by evdev, leaving us with a space of 2 at the end. */ static EvdevPtr evdev_devices[MAXDEVICES] = {NULL}; + + static void AddMadKey(EvdevPtr pEvdev, int trigger, int transfer, MadKeyType type) + { + MadKeyList *keyList = pEvdev->madKeyList; + MadKeyList *key; + if (keyList != NULL) { + while (keyList->next != NULL) + keyList = keyList->next; + } + + + key = xcalloc(sizeof(MadKeyList), 1); + if (key == NULL) + return; + + key->madKey = &key->madKeyEntity; + key->madKey->trigger = trigger; + key->madKey->transfer = transfer; + key->madKey->type = type; + key->next = NULL; + + if (keyList == NULL) + pEvdev->madKeyList = key; + else + keyList->next = key; + } + + static size_t CountBits(unsigned long *array, size_t nlongs) { unsigned int i; *************** *** 252,265 **** static int wheel_left_button = 6; static int wheel_right_button = 7; void EvdevQueueKbdEvent(InputInfoPtr pInfo, struct input_event *ev, int value) { int code = ev->code + MIN_KEYCODE; static char warned[KEY_CNT]; - EventQueuePtr pQueue; EvdevPtr pEvdev = pInfo->private; /* Filter all repeated events from device. We'll do softrepeat in the server, but only since 1.6 */ if (value == 2 --- 280,307 ---- static int wheel_left_button = 6; static int wheel_right_button = 7; + static void + EvdevEnqueKeyEvent(EvdevPtr pEvdev, int code, int value) + { + EventQueuePtr pQueue = &pEvdev->queue[pEvdev->num_queue]; + pQueue->type = EV_QUEUE_KEY; + pQueue->key = code + MIN_KEYCODE; + pQueue->val = value; + pEvdev->num_queue++; + } + void EvdevQueueKbdEvent(InputInfoPtr pInfo, struct input_event *ev, int value) { int code = ev->code + MIN_KEYCODE; static char warned[KEY_CNT]; EvdevPtr pEvdev = pInfo->private; + int scanCode = ev->code; + unsigned int tmpScanCode = scanCode; + unsigned int lastScanCode = pEvdev->lastScanCode; + + /* Filter all repeated events from device. We'll do softrepeat in the server, but only since 1.6 */ if (value == 2 *************** *** 293,303 **** return; } ! pQueue = &pEvdev->queue[pEvdev->num_queue]; ! pQueue->type = EV_QUEUE_KEY; ! pQueue->key = code; ! pQueue->val = value; ! pEvdev->num_queue++; } void --- 335,421 ---- return; } ! /* ! * Sticky Key ! */ ! if (value) { ! if (pEvdev->stickyPhase == 1) { ! pEvdev->stickyPhase = 2; ! EvdevEnqueKeyEvent(pEvdev, pEvdev->stickyScanCode, TRUE); ! goto madKeyFinish; ! } else if (pEvdev->stickyPhase == 2) { ! pEvdev->stickyPhase = 0; ! EvdevEnqueKeyEvent(pEvdev, pEvdev->stickyScanCode, FALSE); ! goto madKeyFinish; ! } ! } else { ! MadKeyList *keyList; ! for (keyList = pEvdev->madKeyList; keyList != NULL; keyList = keyList->next) { ! MadKey *key = keyList->madKey; ! if (key->type == MAD_KEY_STICKY ! && key->trigger == scanCode ! && lastScanCode == scanCode) { ! if (pEvdev->stickyPhase == -1) { /* ignore */ ! pEvdev->stickyPhase = 0; ! break; ! } else { ! pEvdev->stickyPhase = 1; ! pEvdev->stickyScanCode = keyList->madKey->transfer; ! return; ! } ! } ! } ! } ! ! /* ! * Pseudo Modifier ! */ ! { ! MadKeyList *keyList; ! for (keyList = pEvdev->madKeyList; keyList != NULL; keyList = keyList->next) { ! MadKey *key = keyList->madKey; ! if (key->type == MAD_KEY_PSEUDO_MOD ! && key->trigger == scanCode) { ! if (lastScanCode == key->transfer) { ! tmpScanCode = lastScanCode; ! pEvdev->stickyPhase = -1; ! } else if (value) ! scanCode = key->transfer; ! else { ! if (lastScanCode == scanCode) { ! EvdevEnqueKeyEvent(pEvdev, key->transfer, FALSE); ! EvdevEnqueKeyEvent(pEvdev, key->trigger, TRUE); ! } else ! scanCode = key->transfer; ! } ! goto madKeyFinish; ! } ! } ! } ! ! /* ! * One Shot Modifier ! */ ! if (!value) { ! MadKeyList *keyList; ! for (keyList = pEvdev->madKeyList; keyList != NULL; keyList = keyList->next) { ! MadKey *key = keyList->madKey; ! if (key->type == MAD_KEY_ONE_SHOT_MOD ! && key->trigger == scanCode ! && lastScanCode == scanCode) { ! scanCode = key->transfer; ! EvdevEnqueKeyEvent(pEvdev, lastScanCode, FALSE); ! EvdevEnqueKeyEvent(pEvdev, scanCode, TRUE); ! goto madKeyFinish; ! } ! } ! } ! ! madKeyFinish: ! if (value) ! pEvdev->lastScanCode = tmpScanCode; ! ! EvdevEnqueKeyEvent(pEvdev, scanCode, value); } void *************** *** 1160,1165 **** --- 1278,1286 ---- pInfo = device->public.devicePrivate; pEvdev = pInfo->private; + pEvdev->lastScanCode = 0; + pEvdev->stickyPhase = 0; + /* sorry, no rules change allowed for you */ xf86ReplaceStrOption(pInfo->options, "xkb_rules", "evdev"); SetXkbOption(pInfo, "xkb_rules", &pEvdev->rmlvo.rules); *************** *** 2142,2147 **** --- 2263,2326 ---- EvdevDragLockPreInit(pInfo); } + if (pEvdev->flags & EVDEV_KEYBOARD_EVENTS) + { + pEvdev->madKeyList = NULL; + if (xf86FindOption(pInfo->options, "StickyShift")) { + AddMadKey(pEvdev, KEY_LEFTSHIFT, KEY_LEFTSHIFT, MAD_KEY_STICKY); + AddMadKey(pEvdev, KEY_RIGHTSHIFT, KEY_RIGHTSHIFT, MAD_KEY_STICKY); + xf86Msg(X_CONFIG, "%s: StickyShift enabled\n", pInfo->name); + } + if (xf86FindOption(pInfo->options, "StickyCtrl")) { + AddMadKey(pEvdev, KEY_LEFTCTRL, KEY_LEFTCTRL, MAD_KEY_STICKY); + AddMadKey(pEvdev, KEY_RIGHTCTRL, KEY_RIGHTCTRL, MAD_KEY_STICKY); + xf86Msg(X_CONFIG, "%s: StickyCtrl enabled\n", pInfo->name); + } + if (xf86FindOption(pInfo->options, "StickyAlt")) { + AddMadKey(pEvdev, KEY_LEFTALT, KEY_LEFTALT, MAD_KEY_STICKY); + AddMadKey(pEvdev, KEY_RIGHTALT, KEY_RIGHTALT, MAD_KEY_STICKY); + xf86Msg(X_CONFIG, "%s: StickyAlt enabled\n", pInfo->name); + } + if (xf86FindOption(pInfo->options, "PseudoModSpace")) { + int transfer = xf86SetIntOption(pInfo->options, + "PseudoModSpace", + MIN_KEYCODE) - MIN_KEYCODE; + AddMadKey(pEvdev, KEY_SPACE, transfer, MAD_KEY_PSEUDO_MOD); + xf86Msg(X_CONFIG, "%s: PseudoModSpace enabled\n", pInfo->name); + } + if (xf86FindOption(pInfo->options, "OneShotShift")) { + int transfer = xf86SetIntOption(pInfo->options, + "OneShotShift", + MIN_KEYCODE) - MIN_KEYCODE; + AddMadKey(pEvdev, KEY_LEFTSHIFT, transfer, MAD_KEY_ONE_SHOT_MOD); + AddMadKey(pEvdev, KEY_RIGHTSHIFT, transfer, MAD_KEY_ONE_SHOT_MOD); + xf86Msg(X_CONFIG, "%s: OneShotShift enabled\n", pInfo->name); + } + if (xf86FindOption(pInfo->options, "OneShotCtrl")) { + int transfer = xf86SetIntOption(pInfo->options, + "OneShotCtrl", + MIN_KEYCODE) - MIN_KEYCODE; + AddMadKey(pEvdev, KEY_LEFTCTRL, transfer, MAD_KEY_ONE_SHOT_MOD); + AddMadKey(pEvdev, KEY_RIGHTCTRL, transfer, MAD_KEY_ONE_SHOT_MOD); + #ifdef XKB + if (xkb_options != NULL && strstr(xkb_options, "ctrl:swapcaps") != NULL) + AddMadKey(pEvdev, KEY_CAPSLOCK, transfer, MAD_KEY_ONE_SHOT_MOD); + #endif + xf86Msg(X_CONFIG, "%s: OneShotCtrl enabled\n", pInfo->name); + } + if (xf86FindOption(pInfo->options, "OneShotAlt")) { + int transfer = xf86SetIntOption(pInfo->options, + "OneShotAlt", + MIN_KEYCODE) - MIN_KEYCODE; + AddMadKey(pEvdev, KEY_LEFTALT, transfer, MAD_KEY_ONE_SHOT_MOD); + AddMadKey(pEvdev, KEY_RIGHTALT, transfer, MAD_KEY_ONE_SHOT_MOD); + xf86Msg(X_CONFIG, "%s: OneShotAlt enabled\n", pInfo->name); + } + + + + } + return pInfo; } diff -crN src.orig/evdev.h src/evdev.h *** src.orig/evdev.h 2010-03-06 06:58:52.897183337 +0900 --- src/evdev.h 2010-03-06 07:23:23.286619582 +0900 *************** *** 89,94 **** --- 89,114 ---- int traveled_distance; } WheelAxis, *WheelAxisPtr; + + /* key status data for mad-key system */ + typedef enum { + MAD_KEY_STICKY, + MAD_KEY_PSEUDO_MOD, + MAD_KEY_ONE_SHOT_MOD, + } MadKeyType; + + typedef struct { + int trigger; + int transfer; + MadKeyType type; + } MadKey; + + typedef struct MadKeyList { + MadKey madKeyEntity; + MadKey *madKey; + struct MadKeyList *next; + } MadKeyList; + /* Event queue used to defer keyboard/button events until EV_SYN time. */ typedef struct { enum { *************** *** 117,122 **** --- 137,154 ---- int delta[REL_CNT]; unsigned int abs, rel; + + int lastScanCode; + int stickyScanCode; + /* + * -1: ignore + * 0: disabled + * 1: presss enabled + * 2: release enabled + */ + int stickyPhase; + MadKeyList *madKeyList; + /* XKB stuff has to be per-device rather than per-driver */ #if GET_ABI_MAJOR(ABI_XINPUT_VERSION) < 5