Lambdaカクテル

京都在住Webエンジニアの日記です

Invite link for Scalaわいわいランド

Bluetoothキーボードとxcapeとの組み合わせで、スペースキーを押すとなぜか機内モードになる怪奇現象が起こる

ほぼタイトルの通りなのだが、怪奇現象が起こって困っている。

  • FILCO Majestouch Convertible 2を使っている
    • 青軸最高〜〜
  • Linux (OpenSuSE Tumbleweed)ユーザ
  • 自分はスペースキーをShiftに割り当ててSandSを行うため、xcapeを使っている(後述)
  • BluetoothモードでMajestouchを使っているとき、一定の確率で、スペースキーを押下して離すと機内モードに入る
  • もちろん、Bluetoothが停止するのでキーボードも動かない
  • 怒り

という現象が起こっている。

tl;dr

  • xcape使うときはダミーとして255ではなく無難なキーコードを使うこと
    • 255は機内モードスイッチのキーコードになっている
  • キーボードを差し替えたりするとxmodmapの効力が切れる
    • ワイヤレスイヤホンはキーボードとしても追加で認識されることがある

Majestouch Convertible 2

これ(の、英語配列)を使っている。普通に使うぶんには良いキーボードだが、Bluetooth面の品質が若干微妙というレビューが多く、実際自分も時々つながらなかったり、打鍵中にプチフリーズ的な挙動をして困ることがある。しかしいきなり機内モードに入るという怪現象が起こっているのは自分だけのはず。 USB接続だと全く問題ないという声が多く、実際自分もUSB接続にすると全くトラブルは起こらない。

SandS

SandS(Space and Shift)とは、スペースキーをカスタマイズする方法の一種である。スペースキーを押しっぱなしにしたときはシフトキーとして振舞い、そのまま離したときはスペースキーが押されたものとして、または別のキーが押下されたときはShift+そのキーが打鍵されたものとして扱うというもので、比較的ポピュラーなスペースキーのカスタム方法だ。

このカスタマイズは日本語入力方式であるSKK(シフトキーを非常に多用する)のユーザがよく取り入れている。自分もSKKのためにSandSを使っている。

そして、Linux環境(のX11環境)においてSandSを実現するために使われるソフトウェアがxcapeだ。

xcape

github.com

xcape とは、キーボード入力を監視し、修飾キーを押したときと離したときとで別々のキーコードを発するようにできる。今回の場合は以下のような設定で起動している:

# sands.sh
xmodmap -e 'keycode 255=space'
xmodmap -e 'keycode 65=Shift_L'
exec env xcape -d -e '#65=space'

xmodmapはX11についてくるユーティリティで、特定のキーコードを別のキーにマップする。それぞれの行がやっていることは以下の通り:

  • xmodmap
    • キーコード255をスペースに割り当てる
      • キーボードは255を送信できないので、衝突することはない
      • 送信できないが割り当てられてないとは言ってない(ここ伏線です)
    • キーコード65(X11ではスペースバーに対応する)を左シフトに割り当てる
      • スペースが入力されなくなる
  • xcape
    • キーコード65を単独で使った場合はスペースに割り当てる
      • キーコード65の押下とリリースとを連続して検知した場合はキーコード255を送信するという振舞いになる
      • キーコード255が受信されるので、X11は割り当てられたスペースキーが押されたと判定し、スペースが入力される
    • xcapeは-dスイッチでフォアグラウンドで起動させ続け、systemd管理下に置く

これで、スペースバーを押下している間はシフトキーとして振る舞うようにできた。

前提知識として、スキャンコードとキーコードとキーシムとは別の概念

怪現象

Bluetoothモードで使っているとき、どういうわけかスペースキーを押すと機内モードに入ってしまうという現象に悩まされることになった。上掲のスクリプトを再起動させるといったん収まるのでそれでごまかしてきたが、その度にUSB接続に戻したりsystemctlを叩いたりしなければならないので億劫だ。

今回たまたまjournalctl -xeを叩いてログを見る用事があったところ、この現象が起こり、xcapeがログを残していた。

10月 06 22:52:35 localhost.localdomain sands-f.sh[25879]: Intercepted key event 2, key code 65
10月 06 22:52:35 localhost.localdomain sands-f.sh[25879]: Key pressed!
10月 06 22:52:35 localhost.localdomain sands-f.sh[25879]: Intercepted key event 3, key code 65
10月 06 22:52:35 localhost.localdomain sands-f.sh[25879]: Key released!
10月 06 22:52:35 localhost.localdomain sands-f.sh[25879]: Generating space!
10月 06 22:52:35 localhost.localdomain sands-f.sh[25879]: Ignoring generated event.
10月 06 22:52:35 localhost.localdomain sands-f.sh[25879]: Ignoring generated event.
10月 06 22:52:35 localhost.localdomain sands-f.sh[25879]: Intercepted key event 2, key code 65
10月 06 22:52:35 localhost.localdomain sands-f.sh[25879]: Key pressed!
10月 06 22:52:35 localhost.localdomain sands-f.sh[25879]: Intercepted key event 3, key code 65
10月 06 22:52:35 localhost.localdomain sands-f.sh[25879]: Key released!
10月 06 22:52:35 localhost.localdomain sands-f.sh[25879]: Generating XF86RFKill!
10月 06 22:52:35 localhost.localdomain sands-f.sh[25879]: Ignoring generated event.
10月 06 22:52:35 localhost.localdomain sands-f.sh[25879]: Ignoring generated event.
10月 06 22:52:35 localhost.localdomain sands-f.sh[25879]: Intercepted key event 2, key code 22
10月 06 22:52:35 localhost.localdomain sands-f.sh[25879]: Intercepted key event 3, key code 22
10月 06 22:52:35 localhost.localdomain sands-f.sh[25879]: Intercepted key event 2, key code 22
10月 06 22:52:35 localhost.localdomain sands-f.sh[25879]: Intercepted key event 3, key code 22
10月 06 22:52:35 localhost.localdomain sands-f.sh[25879]: Intercepted key event 2, key code 22
10月 06 22:52:35 localhost.localdomain sands-f.sh[25879]: Intercepted key event 3, key code 22
...

XF86RFKillという不穏な文字列が見える。こんな設定した覚えはない。たぶん、GNOMEはXF86RFKillを受信すると、(RadioFrequencyKillというキー名から推測する限り)機内モードを起動するようだ。機内モードはsystemd-rfkill.serviceというsystemdのサービスが担当しているらしく、このサービスが起動していることを確認した。

以下の文献によれば、event 2はキー押下に対応していて、event 3は離すことに対応している。

github.com

4と5はマウスボタンに対応しているっぽいことがわかる。

キーコードは/usr/share/X11/xkb/keycodes/xfree86に定義されていて、65はスペースキーに対応している。

したがって、以下のことが分かった:

  • key code 65を押して離すことをxcapeが認知している
  • 一度目は正常にスペースに対応するkeysymを発出できている
  • 二度目はなぜか同じキーコードの組み合わせに対してXF86RFKillを発出した

keysymを確認しよう。

/usr/include/X11/XF86keysym.hによれば、

#define XF86XK_RFKill       0x1008FFB5   /* Toggle radios on/off */

と書いてある。ちなみに/usr/include/X11/keysymdef.hによれば、普通のスペースは

#define XK_space                         0x0020  /* U+0020 SPACE */

に対応している。正直ぜんぜん似ていない。xcapeのソースコードを読む限り、キーコードからKeySymへの対応を取っている関数はXKeysymToString (XkbKeycodeToKeysym (self->ctrl_conn, k->key, 0, 0))なので、これを調べる。

www.x.org

3、4つめの引数はいつも0なので無視するとして、入力されているキーコードが狂っている可能性を考えて、ちょっと細工をした:

diff --git a/xcape.c b/xcape.c
index 12f48c6..39cba03 100644
--- a/xcape.c
+++ b/xcape.c
@@ -327,9 +327,9 @@ void handle_key (XCape_t *self, KeyMap_t *key,
             {
                 for (k = key->to_keys; k != NULL; k = k->next)
                 {
-                    if (self->debug) fprintf (stdout, "Generating %s!\n",
+                    if (self->debug) fprintf (stdout, "Generating %s (from %d)!\n",
                             XKeysymToString (XkbKeycodeToKeysym (self->ctrl_conn,
-                                    k->key, 0, 0)));
+                                                                 k->key, 0, 0)), k->key);
 
                     XTestFakeKeyEvent (self->ctrl_conn,
                             k->key, True, 0);

すると普段はちゃんと(xmodmapが変換した)255を受け取っていそう:

10月 07 01:22:14 localhost.localdomain sands-f.sh[23117]: Intercepted key event 2, key code 65
10月 07 01:22:14 localhost.localdomain sands-f.sh[23117]: Key pressed!
10月 07 01:22:14 localhost.localdomain sands-f.sh[23117]: Intercepted key event 3, key code 52
10月 07 01:22:14 localhost.localdomain sands-f.sh[23117]: Intercepted key event 3, key code 65
10月 07 01:22:14 localhost.localdomain sands-f.sh[23117]: Key released!
10月 07 01:22:14 localhost.localdomain sands-f.sh[23117]: Generating space (from 255)!
10月 07 01:22:14 localhost.localdomain sands-f.sh[23117]: Ignoring generated event.
10月 07 01:22:14 localhost.localdomain sands-f.sh[23117]: Ignoring generated event.

このまましばらく使ってみて、怪現象が起こるまで待ってみる。

そういえばNothing earを接続したら、(タッチして再生したり停めたりするために)キーボードとしても認識している様子を確認した。嫌な予感がする。

10月 07 01:25:31 localhost.localdomain /usr/libexec/gdm/gdm-x-session[2030]: (II) XINPUT: Adding extended input device "Nothing ear (1) (AVRCP)" (type: KEYBOARD, id 12)
10月 07 01:25:31 localhost.localdomain /usr/libexec/gdm/gdm-x-session[2030]: (**) Option "xkb_layout" "us"

すると 見事完全に再現してしまった。

10月 07 01:29:19 localhost.localdomain sands-f.sh[23993]: Intercepted key event 2, key code 65
10月 07 01:29:19 localhost.localdomain sands-f.sh[23993]: Key pressed!
10月 07 01:29:19 localhost.localdomain sands-f.sh[23993]: Intercepted key event 3, key code 65
10月 07 01:29:19 localhost.localdomain sands-f.sh[23993]: Key released!
10月 07 01:29:19 localhost.localdomain sands-f.sh[23993]: Generating XF86RFKill (from 255)!
10月 07 01:29:19 localhost.localdomain sands-f.sh[23993]: Ignoring generated event.
10月 07 01:29:19 localhost.localdomain sands-f.sh[23993]: Ignoring generated event.

再現手順が分かった。

  1. Majestouchを接続した状態でxcapeを起動する
  2. その状態でNothing earを接続する
  3. Majestouchでスペースキーを押下して離す
  4. すると機内モードになる

なんだよこれ!!!ワザップかよ!!! しかし、キーコードは255を受信している。

仮説

そこで、仮説を立ててみる:

  • 事実として、xcapeはキーコード65の押下とリリースを検出している
  • 事実として、xmodmapはスペースの押下をキーコード255に変換している
  • なんかがミスって、X11の中ではキーコード255がXF86RFKillに割り当てられてしまっている??

もうちょっと調べてみる。 調べてみるとxmodmap -pkするとキーコードに対応するKeySymが得られる:

スクリプトを実行してSandSが有効な状態では・・・

$ xmodmap -pk
...
    255         0x0020 (space)  0x0000 (NoSymbol)       0x0020 (space)

Nothing earを接続した後は・・・

    255         0x1008ffb5 (XF86RFKill) 0x0000 (NoSymbol)       0x1008ffb5 (XF86RFKill)

当たり…………………………………………………

修正

というわけで、以下のことがわかった:

  • Nothing Earをつなぐとキーボードとしても認識される
  • キーボードを差すと xmodmapの効力が切れる
  • キーコード255がそのままX11に送られる
  • キーコード255は元々はXF86RFKillになる
  • XF86RFKillを受信したGnome(か誰か)が機内モードを起動する

この連鎖のどっかを破壊すればよい。とりあえずsystemd-rfkillを停められないか試すが:

# systemctl stop systemd-rfkill.socket
# systemctl disable systemd-rfkill.socket

これだとダメだった(勝手に動く)ので、キーコードを無難そうなやつに修正する。207(Hyper_L)という修飾キーのためのコードが空いてそうだったのでこれを使うことにした:

diff --git a/bin/sands-f.sh b/bin/sands-f.sh
index 3853e3e..fa913e0 100755
--- a/bin/sands-f.sh
+++ b/bin/sands-f.sh
@@ -4,7 +4,8 @@ set -eux
 if pgrep -x xcape ; then
   killall xcape # to prevent xcape daemon duplication
 fi
-xmodmap -e 'keycode 255=space'
+# 207 is for Hyper_L(unused keycode)
+xmodmap -e 'keycode 207=space'
 xmodmap -e 'keycode 65=Shift_L'
 exec env xcape -d -e '#65=space'

これで、勝手に機内モードになるということはなくなった。すさまじい謎解きだった。

★記事をRTしてもらえると喜びます
Webアプリケーション開発関連の記事を投稿しています.読者になってみませんか?