rkremap: キーボードデバイスの自動検出
https://zenn.dev/tmtms/articles/202201-rkremap の続き。
Rkremap.new
時に引数でキーボードデバイスファイルを指定しないといけなかったんだけど、USB や Bluetooth キーボードとかデバイスファイル名がわからない場合に調べるのが面倒なので自動検出するようにしてみた。
入力デバイスの種類の取得
/dev/input/event*
に対して ioctl(EVIOCGBIT(0))
をすれば入力デバイスの種類が得られる。
こんな感じ:
#include <linux/input.h>
#include <stdio.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
unsigned char type[(EV_MAX-1)/8+1];
int fd = open("/dev/input/event3", O_RDONLY);
ioctl(fd, EVIOCGBIT(0, sizeof(type)), type);
for (int i=0; i<sizeof(type); i++) {
printf("%02x ", type[i]);
}
puts("");
}
この type
の EV_*
ビット目が立ってればその種類のデバイスということになるらしい。
EV_*
定数は /usr/include/linux/input-event-codes.h
に定義されている。
ThinkPad T460s 本体のキーボード(/dev/input/event3
)だと 13 00 12 00
という出力になった。
並び替えて2進数で表すと 0000 0000 0001 0010 0000 0000 0001 0011
となり、対応するビットは、0, 1, 4, 17, 20 なので、EV_SYN
, EV_KEY
, EV_MSC
, EV_LED
, EV_REP
となる。
EV_KEY
が含まれてるので、キーを持つデバイスだということがわかる。
Ruby で同じようなことをするにはこんな感じ:
EV_KEY = 1
EVIOCGBIT_0 = 2147763488 # EVIOCGBIT(0, 4)
f = File.open('/dev/input/event3')
buf = ''
f.ioctl(EVIOCGBIT_0, buf)
p buf[0, 4].unpack('C*').map{'%02X'%_1}.join(' ')
キーデバイスがキーボードかどうかの判定
ただ、この EV_KEY
はキーボードだけじゃなくてキー(ボタン?)を持つデバイス全般が該当するらしい。
ThinkPad で evtest コマンドで調べると EV_KEY
はほかにもあって、たとえば event1
が KEY_SLEEP
だけを持つ Sleep Button で、event2
が KEY_POWER
だけを持つ Power Button、タッチパッド(event15
Synaptics TM3145-003) やトラックポイント(event16
TPPS/2 IBM TrackPoint)も EV_KEY
だった。
rkremap の対象は普通のキーボードデバイスなので、0
, 9
, A
, Z
, SPACE
キーがあるデバイスを対象にした。
キーデバイスが対応しているキーを調べるには、ioctl(EVIOCGBIT(EV_KEY))
で得られた値に対してデバイスに対応する KEY_*
ビットが立っているかどうかで判定できる。
#include <linux/input.h>
#include <stdio.h>
#include <fcntl.h>
int capable(unsigned char key[], int code)
{
return (key[code/8] >> (code%8)) & 1;
}
int main(int argc, char *argv[])
{
unsigned char key[(KEY_MAX-1)/8+1];
int fd = open("/dev/input/event3", O_RDONLY);
ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(key)), key);
printf("KEY_0=%d\n", capable(key, KEY_0));
printf("KEY_9=%d\n", capable(key, KEY_9));
printf("KEY_A=%d\n", capable(key, KEY_A));
printf("KEY_Z=%d\n", capable(key, KEY_Z));
printf("KEY_SPACE=%d\n", capable(key, KEY_SPACE));
}
Ruby だとこんな感じ:
EVIOCGBIT_EV_KEY = 2153792801 # EVIOCGBIT(0, 96)
EV_KEY = 1
KEY_0 = 11
KEY_9 = 10
KEY_A = 30
KEY_Z = 44
KEY_SPACE = 57
def capable?(code)= @key[code/8][code%8] != 0
f = File.open('/dev/input/event3')
buf = ''
f.ioctl(EVIOCGBIT_EV_KEY, buf)
@key = buf.unpack('C*')
puts "KEY_0=#{capable? KEY_0}"
puts "KEY_9=#{capable? KEY_9}"
puts "KEY_A=#{capable? KEY_A}"
puts "KEY_Z=#{capable? KEY_Z}"
puts "KEY_SPACE=#{capable? KEY_SPACE}"
/dev/input/event*
を一つずつ上記のように調べてキーボードかどうかを調べることができる。
キーボードデバイスが複数の場合もあるので、IO.select
で複数の入力を待つようにした。これで、本体のキーボードでCtrl
キーを押しながら、USBキーボードでA
キーを押すみたいなのにも対応できた。
参考にしたもの等
現状では rkremap 起動後にキーボードデバイスが追加/削除されたのには対応できないもどうにかしたいなぁ…。
Discussion