🐧

Linux で X よりも低レイヤでキーマップを変更する

2021/09/24に公開

まとめ

USB 接続の ThinkPad トラックポイントキーボードで、半角/全角EscCapsLockCtrl として使うには、/etc/udev/hwdb.d/10-tmtms.hwdb というファイル(ファイル名は何でもいい)を次の内容で作って:

evdev:name:Lenovo ThinkPad Compact USB Keyboard with TrackPoint:dmi:bvn*:bvr*:bd*:svnLENOVO:pn*
 KEYBOARD_KEY_70035=esc
 KEYBOARD_KEY_70039=leftctrl

次のコマンドを実行する:

% sudo udevadm hwdb --update
% sudo udevadm trigger

以下は調査とかなので読まなくてもいい

30年くらい Emacs を使ってたんだけど、そろそろ VSCode を使ってみようかーと思って使ってみた。
デフォルトではカーソル移動がカーソルキーだけでショートカットキーが用意されてないっぽくて、使い始めくらいはデフォルト設定のまま使ってみようと思ってたんだけど、さすがにカーソルキーを使うのは厳しいので、結局 Awesome Emacs Keymap というのを入れた。

https://qiita.com/kurun_pan/items/507517c3f569b080abe2

Use Meta Prefix Escape という設定を有効にすれば M-Esc キーを使うこともできる。

が、うまく動かなかった。元々の物理キーの Esc であれば効くんだけど、xmodmap や XKB で設定した Esc だと効かない(1 の左隣の 半角/全角Esc に変更してる)。
https://tmtms.hatenablog.com/entry/2013/07/07/keymap

xev で調べてもちゃんと keysym は Escape になってる:

KeyPress event, serial 37, synthetic NO, window 0x1800001,
    root 0x7c7, subw 0x0, time 906346, (72,68), root:(3823,1069),
    state 0x0, keycode 49 (keysym 0xff1b, Escape), same_screen YES,
    XKeysymToKeycode returns keycode: 9
    XLookupString gives 1 bytes: (1b) "
mbLookupString gives 1 bytes: (1b) "
FilterEvent returns: False

ということは keysym じゃなくて keycode を使ってるのかもしれない。

X レベルではどうしようもなさそうなので、それよりも低いレイヤで keycode を変更できないかとググってみたら、setkeycodes というのでできるらしい。
どうやらキーボードからの物理的なコード(スキャンコード)をキーコードに変換するマップを変更できるらしい。

X 上じゃ駄目で、コンソールでやる必要があるみたいなので、Ctrl-Alt-1 とかでコンソールに移ってから実行する(Alt-7 とかで X に戻れる)。

showkey -s半角/全角 のスキャンコードを調べる:

% showkey -s
kb mode was UNICODE
[ if you are trying this under X, it might not work
since the X server is also reading /dev/console ]

press any key (program terminates 10s after last keypress)...
0x29 0xa9

Ctrl-C では止まらない。10秒以上放置すると終了する。

showkey -kEsc のキーコードを調べる:

% showkey -s
kb mode was UNICODE
[ if you are trying this under X, it might not work
since the X server is also reading /dev/console ]

press any key (program terminates 10s after last keypress)...
keycode   1 press
keycode   1 release

スキャンコード 0x29 をキーコード 1 に変換すればいいらしいので、setkeycodes を実行:

% sudo setkeycodes 0x29 1

これでノートPCの本体キーボードの方はちゃんと変わったんだけど、USBキーボードの方は効いてない。

man page には次のように書いてあった。(Ubuntu の man setkeycodes にはこの記述は無かった)

setkeycodes affects only the "first" input device that has modifiable scancode-to-keycode mapping. If there is more than one such device, setkeycodes cannot change the mapping of other devices than the "first" one.

ということで、USB の ThinkPad トラックポイントキーボードを使ってる自分は別の方法を考える必要があるのだった。

またググってみたら、こんなページを見つけた。
https://wiki.archlinux.jp/index.php/スキャンコードをキーコードにマップ

どうやら udev を使えばできるらしい。

evdev:name:<input device name>:dmi:bvn*:bvr*:bd*:svn<vendor>:pn*
 KEYBOARD_KEY_<scancode>=<keycode>

という形式で指定すれば良さそう。

<input device name> はたぶん xinput の名前を使えば良さそう:

% xinput
⎡ Virtual core pointer                    	id=2	[master pointer  (3)]
⎜   ↳ Virtual core XTEST pointer              	id=4	[slave  pointer  (2)]
⎜   ↳ TPPS/2 IBM TrackPoint                   	id=16	[slave  pointer  (2)]
⎜   ↳ Lenovo ThinkPad Compact USB Keyboard with TrackPoint	id=10	[slave  pointer  (2)]
⎜   ↳ Synaptics TM3145-003                    	id=15	[slave  pointer  (2)]
⎣ Virtual core keyboard                   	id=3	[master keyboard (2)]
    ↳ Virtual core XTEST keyboard             	id=5	[slave  keyboard (3)]
    ↳ Power Button                            	id=6	[slave  keyboard (3)]
    ↳ Video Bus                               	id=7	[slave  keyboard (3)]
    ↳ Sleep Button                            	id=8	[slave  keyboard (3)]
    ↳ Generic USB2.0 Microphone               	id=11	[slave  keyboard (3)]
    ↳ Integrated Camera: Integrated C         	id=12	[slave  keyboard (3)]
    ↳ AT Translated Set 2 keyboard            	id=13	[slave  keyboard (3)]
    ↳ ThinkPad Extra Buttons                  	id=14	[slave  keyboard (3)]
    ↳ Lenovo ThinkPad Compact USB Keyboard with TrackPoint	id=9	[slave  keyboard (3)]
    ↳ Lenovo ThinkPad Compact USB Keyboard with TrackPoint	id=17	[slave  keyboard (3)]

Lenovo ThinkPad Compact USB Keyboard with TrackPoint という名前らしい。

<vendor> はよくわからなかったけど、たぶん LENOVO* でもいいのかもしれない。

<scancode> の調べ方は evtest を使えばいいらしい。
https://wiki.archlinux.jp/index.php/特別なキーボードキー

入ってなかったので sudo apt insttall evtest でインストール。

引数の /dev/input/eventXX はこんな感じで調べられた:

% ls -l /dev/input/by-path/*usb*kbd
lrwxrwxrwx 1 root root 9  9月 24 12:15 /dev/input/by-path/pci-0000:00:14.0-usb-0:3:1.0-event-kbd -> ../event5

evtest を起動して 半角/全角 キーと Esc を押す:

% sudo evtest /dev/input/event5
...
Testing ... (interrupt to exit)
Event: time 1632465959.559163, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70035
Event: time 1632465959.559163, type 1 (EV_KEY), code 41 (KEY_GRAVE), value 1
Event: time 1632465959.559163, -------------- SYN_REPORT ------------
Event: time 1632465959.606785, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70035
Event: time 1632465959.606785, type 1 (EV_KEY), code 41 (KEY_GRAVE), value 0
Event: time 1632465959.606785, -------------- SYN_REPORT ------------

Event: time 1632465965.087102, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70029
Event: time 1632465965.087102, type 1 (EV_KEY), code 1 (KEY_ESC), value 1
Event: time 1632465965.087102, -------------- SYN_REPORT ------------
Event: time 1632465965.126598, type 4 (EV_MSC), code 4 (MSC_SCAN), value 70029
Event: time 1632465965.126598, type 1 (EV_KEY), code 1 (KEY_ESC), value 0
Event: time 1632465965.126598, -------------- SYN_REPORT ------------

半角/全角 のスキャンコードは 70035 で、Esc のキーコードは ESC らしい。
(半角/全角 なのに GRAVE と表示されてるのは気にしなくていい。日本語キーボードなのに英語配列を選択してるためなので。)

Esc だけじゃなくて、ついでに CapsLock を左Ctrl に変更する方法も同様にして調べた。
Capslock のスキャンコードは 70039 で、左Ctrl のキーコードは LEFTCTRL だった。

/etc/udev/hwdb.d/10-tmtms.hwdb というファイルを作る(ファイル名は何でもいい):

evdev:name:Lenovo ThinkPad Compact USB Keyboard with TrackPoint:dmi:bvn*:bvr*:bd*:svnLENOVO:pn*
 KEYBOARD_KEY_70035=esc
 KEYBOARD_KEY_70039=leftctrl

キーコードは小文字で書かないといけないらしい。

データベースファイルを更新:

% sudo udevadm hwdb --update

反映:

% sudo udevadm trigger

確認:

% udevadm info /dev/input/event5 | grep KEYBOARD_KEY
E: KEYBOARD_KEY_70035=esc
E: KEYBOARD_KEY_70039=leftctrl

ということでちゃんと VSCode でも 半角/全角Esc として使えるようになった。この記事も VSCode で書いてみた。(まだ慣れない)

Discussion