Linux のキーボードイベントを取得する

はじめに

Linux でキーボードを押したり離したりしたイベントをどうやって取得するか考えていた.

Linux Input Subsystemの使い方

参考になりそうな良い記事を見つけてなんとなく実装できたのでその備忘.

入力イベントの確認方法

まずは入力デバイスを確認する.HHKB が event4 として認識されている.

$ cat /proc/bus/input/devices
...
I: Bus=0003 Vendor=0853 Product=0100 Version=0111
N: Name="Topre Corporation HHKB Professional"
P: Phys=usb-0000:00:14.0-6.1/input0
S: Sysfs=/devices/pci0000:00/0000:00:14.0/usb1/1-6/1-6.1/1-6.1:1.0/0003:0853:0100.0011/input/input41
U: Uniq=
H: Handlers=sysrq kbd event4 leds 
B: PROP=0
B: EV=120013
B: KEY=1000000000007 ff9f207ac14057ff febeffdfffefffff fffffffffffffffe
B: MSC=10
B: LED=1f
...

入力イベントの確認は xev が有名だが,X 環境が前提となる.キーボードの a を押して離したときのイベントがこれ.

$ xev
KeyPress event, serial 37, synthetic NO, window 0x9e00001,
    root 0x1d8, subw 0x9e00002, time 1546063273, (66,57), root:(1720,1304),
    state 0x10, keycode 38 (keysym 0x61, a), same_screen YES,
    XLookupString gives 1 bytes: (61) "a"
    XmbLookupString gives 1 bytes: (61) "a"
    XFilterEvent returns: False

KeyRelease event, serial 37, synthetic NO, window 0x9e00001,
    root 0x1d8, subw 0x9e00002, time 1546063409, (66,57), root:(1720,1304),
    state 0x10, keycode 38 (keysym 0x61, a), same_screen YES,
    XLookupString gives 1 bytes: (61) "a"
    XFilterEvent returns: False

hexdump で入力イベントを覗いてみるとこんな感じ.キーボードの a を押して離したときの出力がこれ.HEX ではなんだか分からない.

$ sudo hexdump /dev/input/event4 
0000040 0000 0000 0000 0000 43f8 614c 0000 0000    # press a
0000050 6402 0001 0000 0000 0004 0004 0004 0007    # press a
0000060 43f8 614c 0000 0000 6402 0001 0000 0000    # press a
0000070 0001 001e 0001 0000 43f8 614c 0000 0000    # press a
0000080 6402 0001 0000 0000 0000 0000 0000 0000    # press a
0000090 43f8 614c 0000 0000 32de 0004 0000 0000    # release a
00000a0 0004 0004 0004 0007 43f8 614c 0000 0000    # release a
00000b0 32de 0004 0000 0000 0001 001e 0000 0000    # release a
00000c0 43f8 614c 0000 0000 32de 0004 0000 0000    # release a

この HEX の羅列を適当に処理できれば xev の様な形で入力内容を読み取ることができる.

入力イベントの構造体は input.h で次のように定義されている.上記の HEX をこの構造体に入れてあげれば入力イベントを適当に読み込むことができるようになる.

struct input_event {
        struct timeval time;
        __u16 type;
        __u16 code;
        __s32 value;
};

適当にプログラムを書く

前項までの説明でなんとなく実装方針が見えたので,参考ページのプログラムをベースにキーを押したり離したりするイベントを読み取るプログラムを書いた.ファイルディスクリプタは 1 つだけだが無駄に POLL を使ってみて,こんな感じ.

#include <fcntl.h>
#include <linux/input.h>
#include <stdio.h>
#include <unistd.h>
#include <poll.h>
#include <string.h>

int main(void) {
    sleep(1);

    struct pollfd fds[1];
    int fd = open("/dev/input/event4", O_RDONLY);
    ioctl(fd, EVIOCGRAB, 1);

    memset(&fds, 0, sizeof(fds));
    fds[0].fd = fd;
    fds[0].events = POLLIN | POLLERR;

    do {
        poll(fds,1,-1);
        if(fds[0].revents & POLLIN) {
            struct input_event event;
            read(fd,&event,sizeof(event));
            if(event.type == EV_KEY){
                switch(event.value){
                    case 0:
                        printf("code %d(%04x) release\n", event.code, event.code);
                        break;
                    case 1:
                        printf("code %d(%04x) press\n", event.code, event.code);
                        break;
                    case 2:
                        printf("code %d(%04x) long press\n", event.code, event.code);
                        break;
                    default:
                        break;
                }
                // type esc to break
                if(event.code == KEY_ESC){
                    break;
                }
            }
        }
    } while(1);

    ioctl(fd, EVIOCGRAB, 0);
    close(fd);
    return 0;
}

適当にコンパイルして実行する.

$ g++ -o keycode_test keycode_test.cpp
$ sudo ./keycode_test
...
code 30(001e) press
code 30(001e) release
..

ここで出力されるキーコードは xev で表示されるコードとは別の値になっている.キーコードは input-event-codes.h で定義されており,出力された通り 30 として定義されている.また,イベントの定義も同ファイルに記載されており,キーボード入力イベントは event.type == EV_KEY のものだけ抽出すればいい.

...

/*
 * Event types
 */

#define EV_SYN                  0x00
#define EV_KEY                  0x01
#define EV_REL                  0x02
#define EV_ABS                  0x03
#define EV_MSC                  0x04
#define EV_SW                   0x05
#define EV_LED                  0x11
#define EV_SND                  0x12
#define EV_REP                  0x14
#define EV_FF                   0x15
#define EV_PWR                  0x16
#define EV_FF_STATUS            0x17
#define EV_MAX                  0x1f
#define EV_CNT                  (EV_MAX+1)
...
/*
 * Keys and buttons
 *
 * Most of the keys/buttons are modeled after USB HUT 1.12
 * (see http://www.usb.org/developers/hidpage).
 * Abbreviations in the comments:
 * AC - Application Control
 * AL - Application Launch Button
 * SC - System Control
 */
...
#define KEY_ENTER               28
#define KEY_LEFTCTRL            29
#define KEY_A                   30
#define KEY_S                   31
#define KEY_D                   32
...

おわりに

英語サイトだと Linux の入力イベント取得は難しそうな印象だったが,思いの外あっさり実装できた.evtest コマンドをインストールすれば今回実装したような事ができるらしいが,実装例としてはこんな感じ.

コメント

タイトルとURLをコピーしました