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>

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