Linuxでユーザーからの入力を処理するInput Subsystemの説明と、その使い方を書いた非公式な文章です。
主に使う側の視点で書かれています。ドライバの開発者には向いていないかもしれません。
この文章の筆者はLinuxの開発者ではなく、内容には間違いが含まれる場合があります。
Input Subsystemは使い方を誤るとキーロガーが仕掛けられるなど、システムのセキュリティを低下させる恐れがあります。Input Subsystemを利用するプログラムが悪意のあるユーザの攻撃を受けないように注意してください。
このページで使っているサンプルファイルを予めダウンロードしておいてください。
Input Subsystemを使うと次のようなことができます。
Input Subsystemとは、USBやPS/2などの低水準なドライバと、ユーザーの使うプログラムの橋渡しをするLinuxカーネルの一部です。
ユーザーとのやりとりはデバイスファイルの読み書きによって行なわれます。
Input Subsystemを使うにはバージョン2.2以降のカーネルが必要となります。
ただ、Linux 2.4以前のInput SubsystemはLinux 2.6のものととやや異なっており、2.6用に書いたコードはそのままでは動きません(運が良ければ動くこともある)。また、Linux 2.4のInput SubsystemはPS/2マウスが扱えなかったり、標準ドライバでの処理を抑制できないなど制約があります。この文章では主にLinux 2.6のInput Subsystemについて説明します。
また、Linux 2.6でもInput SubsystemやUser Inputといったカーネルモジュールが有効にされている必要があります。/dev/input/event[0-9]や/dev/uinputといったファイルがあることを確認してください。
/dev/inputというディレクトリを見てみてください。event0とかevent1といったファイルがあるはずです。このファイルがInput Subsystemを使う上で中心となるイベントデバイスファイルです。
このファイル1つ1つが1つのキーボードやマウスなどのデバイスに対応しています。
これらのファイルから読み込めばデバイスからの入力を取得できますし、これらのファイルに書き込めば入力を捏造できます。
では、ちょっと覗いてみましょう。hexdump /dev/input/event0などを実行してみてください。一般ユーザーでは見られないかもしれません。そしてキーボードやマウスを操作してみてください。入力に反応して何やら(内容はめちゃくちゃに見えても)出力が得られるはずです。別のファイルを覗いてみると別のデバイスからの出力が得られます。
この出力の形式は/usr/include/linux/input.hで次のように定められています。
struct input_event { struct timeval time; __u16 type; __u16 code; __s32 value; };
それぞれの意味は次のようになります。詳しくは/usr/include/linux/input.hを見てください。
これを使うと、イベントデバイスファイルを開いて、input_event構造体を読み込んで、その値に反応してさまざまな処理をするプログラムを書くことができます。
例として、最近のキーボードに付いている「ミュート」「音量を上げる」「音量を下げる」ボタンに反応して音量を調整するプログラムを作ってみます。ただし音量を調節するプログラムとしてmuteというコマンドとaumixというコマンドがインストールされていると仮定します。
#include <stdio.h> #include <stdlib.h> #include <linux/input.h> #include <unistd.h> int main(void) { for (;;) { struct input_event event; if (read(0, &event, sizeof(event)) != sizeof(event)) { exit(EXIT_FAILURE); } switch(event.type) { case EV_KEY: switch(event.code) { case KEY_MUTE: if (event.value == 1) { system("mute"); } break; case KEY_VOLUMEDOWN: if (event.value != 0) { system("aumix -v-"); } break; case KEY_VOLUMEUP: if (event.value != 0) { system("aumix -v+"); } break; default: break; } break; default: break; } } }
これをvolme.cとして保存し、gcc volme.c -o volmeとしてコンパイルします。そして./volme < /dev/input/event0とします。ただし、event0はキーボードに対応するイベントデバイスファイルとします。
プログラムは単純で、readして、switchしているだけですね。
「音量を上げる」「音量を下げる」ボタンの場合はevent.value
が1の場合も2の場合も反応するようにしてあるので、ボタンを押しっぱなしにすればどんどん音量を変更できます。
サンプルファイルのdump_event.cはイベントデバイスファイルに関する情報とイベントを人間が読める形式で出力します。デバイスについて調べたいときに使うといいでしょう。
こんどはさきほどの逆です。イベントデバイスファイルにinput_event構造体を書き込んでみましょう。
#include <stdio.h> #include <stdlib.h> #include <linux/input.h> #include <sys/time.h> void write_key_event(int code, int value, int fd) { struct input_event key_event; gettimeofday(&key_event.time, NULL); key_event.type = EV_KEY; key_event.code = code; key_event.value = value; write(fd, &key_event, sizeof(key_event)); } int main(void) { write_key_event(KEY_A, 1, 1); write_key_event(KEY_A, 0, 1); write_key_event(KEY_B, 1, 1); write_key_event(KEY_B, 0, 1); write_key_event(KEY_C, 1, 1); write_key_event(KEY_C, 0, 1); write_key_event(KEY_LEFTSHIFT, 1, 1); write_key_event(KEY_A, 1, 1); write_key_event(KEY_A, 0, 1); write_key_event(KEY_B, 1, 1); write_key_event(KEY_B, 0, 1); write_key_event(KEY_C, 1, 1); write_key_event(KEY_C, 0, 1); write_key_event(KEY_LEFTSHIFT, 0, 1); exit(EXIT_SUCCESS); }
これをwrite.cとして保存し、gcc write.c -o writeとしてコンパイルします。そして./write > /dev/input/event0とします。ただし、event0はキーボードに対応するイベントデバイスファイルとします。するとabcABCというのがキーボードから入力されたように処理されるはずです。
コードでは「Aを押す」「Aを離す」……「shiftを押す」「Aを押す」「Aを離す」……「shiftを離す」というイベントを発生させています。
ところで、このプログラムの出力をマウスに対応するイベントデバイスファイルに書き込むとどうなるでしょうか。イベントは無視されてしまいます。同様にマウス関連のイベントをキーボードに対応するイベントデバイスファイルに書き込んだ場合も無視されてしまいます。また、ホイールの無いマウスに対してホイールイベントを書き込んでも無視されてしまいます。しかし、これではマウスを繋いでいないシステムでマウスをエミュレートするといったことができなくなってしまいます。
そこで次で紹介するUser Inputモジュールを利用します。
User Inputモジュールを使うとユーザーが仮想的なデバイスを作成できます。つまりマウスを繋げてないシステムで仮想的なマウスのイベントデバイスファイルを作成できます。
User Inputモジュールを使うには、uinputデバイスファイル(普通/dev/uinput)に対してどのようなデバイスを作りたいかという情報を書き込んだあと、ioctl(ui_fd, UI_DEV_CREATE, NULL);
を実行します。
具体的にどのように使うかはサンプルファイルのhook_main.cを参照してください(TODO: 詳しく書く)。
ここで、例えば仮想的なキーボードデバイスを作ったとしてもすぐにLinuxのコンソールなどイベントが渡されるようになるわけではありません。デバイスはどのようなイベントを発生する可能性があるかという情報を持っているのですが、Linuxの標準キーボードドライバはこれを見て一定の条件を満したものだけイベントをコンソールなどに渡します。そのため、デバイスを作るときに、Linuxの標準ドライバにキーボードやマウスとして認識されるためには次のようなイベントを出力すると宣言する必要があります。
REL_X, REL_YおよびBTN_LEFT
または、REL_WHEEL
または、ABS_X, ABS_YおよびBTN_TOUCH
または、ABS_X, ABS_Y, ABS_PRESSURE, ABS_TOOL_WIDTHおよびBTN_TTOL_FINGER
つまり、マウスとして認識させるにはioctl(ui_fd, UI_DEV_CREATE, NULL);
をする前に次のようにします。
ioctl(ui_fd, UI_SET_EVBIT, EV_REL); ioctl(ui_fd, UI_SET_RELBIT, REL_X); ioctl(ui_fd, UI_SET_RELBIT, REL_Y); ioctl(ui_fd, UI_SET_EVBIT, EV_KEY); ioctl(ui_fd, UI_SET_KEYBIT, BTN_LEFT);
ここでui_fdはuinputデバイスのファイルデスクリプタとします。
# ただし、Linuxのマウスの標準ドライバ(mousedev.c)はPS/2マウスをエミュレートして/dev/input/mouse*や/dev/input/miceを作るだけであり、しかも最近のXは/dev/input/mouse*や/dev/input/miceではなくイベントデバイスファイルを直接読み込むため、あまり意味はないかもしれません。
ここまでのことを応用すれば入力を簡単に自由に加工できそうです。しかし、CapsLockキーをControlキーにしようと考えて、イベントデバイスファイルからイベントを読み込んでCapsLockキーのイベントが来た時にControlキーのイベントを出力するようにしても、CapsLockキーとControlキーの両方が押されたことになってしまいます。これは標準ドライバがCapsLockキーを通常通り処理してしまうからです。
つまり、標準ドライバ(というか自分以外のプログラム)が処理を行わないようにする必要があります。そのためにはイベントデバイスファイルのファイルデスクリプタに対して次のような操作を行い、デバイスを占有するようにします。
ioctl(fd, EVIOCGRAB, 1);
元に戻すときは0を指定します。
ioctl(fd, EVIOCGRAB, 0);
ただし、grabしてしまうとそのイベントデバイスファイルに書き込んでも標準ドライバで扱われなくなってしまうので、イベントを出力するときはUser Inputモジュールを使わないといけません。
TODO
# 最近のXはイベントデバイスファイルを直接読めるのでこの節は必要無い?
以下ではX Window SystemはX.org X11を使っているものと仮定します。
Input Subsystemを使ってばりばりカスタマイズしようとするとXでつまづくことがあります。
X.org X11 6.8.2現在、Xは/dev/input/event[0-9]を直接読んではくれません。次のバージョンでは読めるらしいのですが、それまでは/dev/input/mice等を使ってPS/2プロトコルで通信してやる必要があります。
/dev/input/mouse[0-9]はデバイスがマウスと認識されれば自動で作られ、PS/2プロトコルへの変換も自動でやってくれるので作成に関しては気にする必要はありません。
しかし、/dev/input/mouse[0-9]でのやりとりにはPS/2プロトコル(正確にはImPS/2プロトコル)が使われるので次のような制約があります。
そのためXで水平ホイールを使うにはちょっとした工夫が必要です。
そもそもXにはホイールのイベントというものはありません。そこでその代りに多くのアプリケーションではボタン4, 5をホイールの上下、6, 7を水平ホイールの左右として扱っています。
そこで、xorg.conf上でホイールをボタン4, 5に変換して、元々のボタン4, 5をボタン6, 7に変換して、水平スクロールを発生させたいときにはボタン4, 5のイベントを発生させれば良いように思えるのですが、xorg.confではホイールからボタンへの変換はできても、ボタンを別のボタンに変換できません。
そこで、ボタン4, 5はそのままで、ホイールにボタン6, 7を割り当て、イベントを生成するときに、REL_WHEELの代りにBTN_SIDE, BTN_EXTRAを、REL_HWHEELの代りにREL_WHEELを使うというややこしいことをしなければなりません。
この場合のxorg.confは次のようになります。
Section "InputDevice" Identifier "Mouse0" Driver "mouse" Option "Protocol" "auto" Option "Device" "/dev/input/mice" Option "Buttons" "7" Option "ZAxisMapping" "6 7" EndSection
ただし、この場合通常のホイールマウスなどを併用しようとしても正常に動かないので注意が必要です。
もう1つの方法として、Xのドライバが標準で持っているホイールのエミュレーションを使うという手があります。
この場合xorg.confは次のようになります。
Section "InputDevice" Identifier "Mouse0" Driver "mouse" Option "Protocol" "auto" Option "Device" "/dev/input/mice" Option "Buttons" "7" Option "EmulateWheel" "true" Option "EmulateWheelButton" "4" Option "EmulateWheelInertia" "50" Option "XAxisMapping" "6 7" Option "YAxisMapping" "4 5" EndSection
こうすると、ボタン4を押しながらマウスを動かすことで垂直/水平ホイールを行うことができます。ボタン4が無いマウスの場合はキーボードの使っていないキーをInput SubsystemでBTN_SIDEに変換してやるといいでしょう。
ホイールの速度はEmulateWheelInertiaで指定します。何ピクセル分マウスを動かしたときにホイールイベントを発生させるかという形で指定します。つまり小さくすると速く、大きくすると遅くなります。
Input Subsystemを使っているとき、デバイスファイルと実際のデバイスの対応が簡単に分ると便利です。また、実際のデバイスと/dev/input/event[0-9]や/dev/input/mouse[0-9]との対応はちょくちょく変わるのでこれを固定できれば便利です。そこでudevを使って/dev/input以下のデバイスファイルのファイル名を制御してやります。
udev自体の導入方法は別のドキュメントを参照してください。ここでは設定方法のみを示します。
まず、udevinfo -a -p /sys/class/input/event4のようにして実際のデバイスを判別するための値を調べます。SYSFS{idProduct}やSYSFS{idVendor}が適当でしょう。SYSFS{product}もわかりやすくておすすめです。
そして/etc/udev/rules.d/10-local.rulesに次のように書きます。
BUS="usb", KERNEL="event*", SYSFS{product}="Kensington USB/PS2 Orbit", NAME="input/%k", SYMLINK="input/event_orbit" BUS="usb", KERNEL="mouse*", SYSFS{product}="Kensington USB/PS2 Orbit", NAME="input/%k", SYMLINK="input/mouse_orbit"
BUS="usb"
とかSYFS{product}="..."
とかSYMLINK="..."
の部分は適宜書き換えてください。こうすると/dev/input/mouse_orbitというシンボリックリンクが作られ、常にOrbit(註: トラックボールの商品名)に対応する/dev/input/mouse[0-9]を指すようになります。event_orbitも同様にOrbitのイベントデバイスファイルを指すようになります。
ただし、User Inputモジュールで作った仮想的なデバイスについては判別するための情報をudevinfoを使っても得られないため上手くいかないので注意が必要です。
hook_main.cはuinputでデバイスを作った後、Input Subsystemからの入力をフックし、加工してuinputで作ったデバイスに出力するというプログラムのテンプレートとなるファイルです。
別ファイルでいくつかの関数や変数を定義し、hook_main.cと一緒にコンパイルすることで利用できます。
使う時にはhook_main.hをインクルードし、次の関数およびグローバル変数を定義します。
ioctl(fd, UI_SET_EVBIT, EV_REL);
やioctl(fd, UI_SET_RELBIT, REL_WHEEL);
等とする。また、次の関数が利用可能です。
hook_main.cを使ったプログラムの例として次のようなものがアーカイブに入っています。これらは筆者が個人的に使っていたものなのでそのままでは使いづらいかと思いますが、改造の元にしてください。