X11: XInput (1)

XInput
XInput 拡張機能は、マウス、キーボード、タッチスクリーン、ペンタブレットなどの入力デバイスを扱うための機能です。

XInput を使うと、入力デバイスの詳細な情報を取得することができるため、例えばペンタブレットの筆圧を取得したり、座標を double で取得することができます。

XInput には ver 1.x と ver 2.x があり、この二つのバージョンでは扱いが大きく異なります。
ここでは、XInput ver 2 (XI2) のみを扱います。
ヘッダとライブラリ
XInput のライブラリは、どちらのバージョンでも -lXi でリンクします。

ヘッダファイルは、ver1 と ver2 で分かれています。
ver2 の場合は、<X11/extensions/XInput2.h> をインクルードしてください。
初期化
バージョンの確認
Status XIQueryVersion(Display *dpy, int *major_version_inout, int *minor_version_inout);

サーバーが対応している XI2 のバージョンを確認します。

major_version_inout, minor_version_inout の変数に、クライアントが要求する XI2 バージョンを指定しておく必要があります。
関数が返ると、そのバージョン以下で、サーバーがサポートしている XI2 バージョンが返ります。

※ major_version_inout には、2 以上を指定すること。

ver 2.2 でマルチタッチ、ver 2.3 でバリア、ver 2.4 でジェスチャーの機能が追加されています。

サーバーが XI2 をサポートしている場合、Success が返ります。

サーバーがサポートしているバージョンが、要求したバージョン以上であるかどうかは判断されないので、必要に応じて、クライアント側が、返ったバージョンを判断する必要があります。
(要求するバージョン以下ならエラーとするか、または、返ったバージョンを超える機能は個別に使用しないようにするか)
XQueryExtension
XI2 のイベントを判定する場合、オペコード値が必要になりますが、XI2 用の XQueryExtension に相当する関数は定義されていないため、Xlib の XQueryExtension 関数を使い、拡張機能名に "XInputExtension" を指定して取得します。

int opcode,event,error;

if(!XQueryExtension(didp, "XInputExtension", &opcode, &event, &error))
{
    ...error...
}
デバイスの情報
まずは、使用できる入力デバイスの情報が必要になります。
XIQueryDevice
XIDeviceInfo *XIQueryDevice(Display *dpy, int deviceid, int *ndevices_return);

複数の入力デバイスの情報を取得します。

deviceid情報を取得するデバイスのID。
XIAllDevices (0) で、すべてのデバイス。
XIAllMasterDevices (1) で、すべてのマスターデバイス。
ndevices_return情報を取得したデバイスの数が返る
戻り値XIDeviceInfo 構造体の配列のポインタが返る
XIFreeDeviceInfo
void XIFreeDeviceInfo(XIDeviceInfo *info);

XIQueryDevice() で返されたポインタは、XIFreeDeviceInfo() で解放します。
配列全体を丸ごと解放するため、一度呼ぶだけで構いません。
XIDeviceInfo 構造体
typedef struct
{
    int     deviceid;
    char    *name;
    int     use;
    int     attachment;
    Bool    enabled;
    int     num_classes;
    XIAnyClassInfo **classes;
} XIDeviceInfo;

typedef struct
{
    int type;
    int sourceid;
} XIAnyClassInfo;

デバイス情報の構造体です。

deviceidデバイスのID。
接続されている間は常に同じ値ですが、起動中、削除されたデバイスの ID が再利用される場合もあります。
nameデバイスの名前
useデバイスのタイプ。

XIMasterPointer : マスターポインタ。attachment はペアリングされたマスターキーボードのデバイス ID。
XIMasterKeyboard : マスターキーボード。attachment はペアリングされたマスターポインタの ID。
XISlavePointer : スレーブデバイス。現在、attachment で指定されたマスターポインタに接続されている。
XISlaveKeyboard : スレーブデバイス。現在、attachment で指定されたマスターキーボードに接続されている。
XIFloatingSlave : 現在どのマスターデバイスにも接続されていない、スレーブデバイス。attachment の値は未定義。
attachmentデバイスタイプによって異なります。
enabledTrue の場合、デバイスが現在有効であり、イベントを送信できます。
無効状態のデバイスは、イベントを送信しません。
num_classesclasses の個数
classesデバイスが持っている各クラスの情報のポインタの配列。
XIAnyClassInfo 構造体のポインタの配列になっていますが、実際はクラスのタイプごとに構造体が異なるため、type 値を元に、各クラスの構造体のポインタに型変換する必要があります。

各クラス構造体の先頭には、常に type と sourceid があります。

type はクラスのタイプ。以下のいずれかです。
XIKeyClass, XIButtonClass, XIValuatorClass, XIScrollClass, XITouchClass, XIGestureClass

sourceid は、このクラスの生成元のデバイス ID。
マスターデバイスの場合、通常、現在イベントを送信しているスレーブデバイスの ID です。
スレーブデバイスの場合、通常、そのデバイスの ID です。
デバイスについて
XI2 が扱うデバイスは、大きく分けて、「マスターデバイス (MD)」と「スレーブデバイス (SD)」の2つに分かれています。

マスターデバイス
X サーバーによって作成される、仮想のデバイスです。

「マスターポインタ」と「マスターキーボード」の2つが存在し、この2つは、X のコアイベントと XInput のイベントのいずれかを生成できます。

マスターポインタは、現在ポインタが操作されているデバイスとして扱うことができ、マスターキーボードも同様に、現在操作されているキーボードデバイスとして扱うことができます。
デバイスごとに処理するのではなく、ポインタとキーボードの2つを総合的に扱いたい場合に使います。

マスターポインタとマスターキーボードは、それぞれがペアリングされており、両方が存在する間は一定です。

スレーブデバイス
実際にパソコンに接続されている個々のデバイスや、それに関連する仮想的なデバイスです。
スレーブデバイスは、マスターデバイス(ポインタかキーボード)に接続される場合があります。

スレーブデバイスでイベントが発生すると、接続されているマスターデバイスに応じて、ポインタの位置が移動したり、キーイベントが生成されたりします。
プログラム
XI デバイスの一覧を表示するプログラムです。
各クラスの値については、次回で行います。

$ cc -o run e07-xi1.c -lXlib -lXi

<e07-xi1.c>
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/extensions/XInput2.h>

const char *g_use[] = {
 "MasterPointer", "MasterKeyboard", "SlavePointer", "SlaveKeyboard", "FloatingSlave"
};

int main(int argc,char **argv)
{
    Display *disp;
    XIDeviceInfo *info,*pi;
    int major,minor,dnum,i;
    
    disp = XOpenDisplay(NULL);
    if(!disp) return 1;

    //version

    major = 2, minor = 4;

    if(XIQueryVersion(disp, &major, &minor) != Success)
    {
        printf("unsupported XI2\n");
        XCloseDisplay(disp);
        return 1;
    }

    printf("ver %d.%d\n\n", major, minor);

    //デバイス情報

    info = XIQueryDevice(disp, XIAllDevices, &dnum);

    for(i = 0; i < dnum; i++)
    {
        pi = info + i;

        printf("-- [%s] --\ndeviceid(%d) use(%s) attachment(%d) enabled(%d) num_classes(%d)\n",
            pi->name, pi->deviceid, g_use[pi->use - 1], pi->attachment,
            pi->enabled, pi->num_classes);
    }

    XIFreeDeviceInfo(info);

    //

    XCloseDisplay(disp);

    return 0;
}
結果例
ver 2.4

-- [Virtual core pointer] --
deviceid(2) use(MasterPointer) attachment(3) enabled(1) num_classes(7)
-- [Virtual core keyboard] --
deviceid(3) use(MasterKeyboard) attachment(2) enabled(1) num_classes(1)
-- [Virtual core XTEST pointer] --
deviceid(4) use(SlavePointer) attachment(2) enabled(1) num_classes(3)
-- [Virtual core XTEST keyboard] --
deviceid(5) use(SlaveKeyboard) attachment(3) enabled(1) num_classes(1)
-- [Power Button] --
deviceid(6) use(SlaveKeyboard) attachment(3) enabled(1) num_classes(1)
-- [Video Bus] --
deviceid(7) use(SlaveKeyboard) attachment(3) enabled(1) num_classes(1)
-- [Power Button] --
deviceid(8) use(SlaveKeyboard) attachment(3) enabled(1) num_classes(1)
-- [Sleep Button] --
deviceid(9) use(SlaveKeyboard) attachment(3) enabled(1) num_classes(1)
-- [Wacom One by Wacom M Pen stylus] --
deviceid(10) use(SlavePointer) attachment(2) enabled(1) num_classes(12)
-- [ELECOM ELECOM BlueLED Mouse] --
deviceid(11) use(SlavePointer) attachment(2) enabled(1) num_classes(7)
-- [SEM USB Keyboard] --
deviceid(12) use(SlaveKeyboard) attachment(3) enabled(1) num_classes(1)
-- [SEM USB Keyboard Consumer Control] --
deviceid(13) use(SlavePointer) attachment(2) enabled(1) num_classes(7)
-- [SEM USB Keyboard System Control] --
deviceid(14) use(SlaveKeyboard) attachment(3) enabled(1) num_classes(1)
-- [Wacom One by Wacom M Pen eraser] --
deviceid(15) use(SlavePointer) attachment(2) enabled(1) num_classes(12)
-- [SEM USB Keyboard Consumer Control] --
deviceid(16) use(SlaveKeyboard) attachment(3) enabled(1) num_classes(1)

このパソコンには、キーボード、マウス、ペンタブレットがそれぞれ1つ接続されています。

"Virtual core pointer" がマスターポインタ、"Virtual core keyboard" がマスターキーボードで、他のスレーブデバイスは、そのどちらかに接続されています。

なお、"Virtual core XTEST *" は、XTEST 拡張機能に関するスレーブデバイスです。

スレーブキーボードには、実際のキーボードの他に、電源ボタンやスリープボタンもデバイスとして存在しています。

マウスとペンタブレットは、マスターポインタに接続されています。
そのため、どちらを操作しても、画面上のポインタは移動します。

なお、このペンタブレットの実際のペンには、消しゴム機能がありませんが、"eraser" の名前が付いて、ペンの消しゴム部分で操作した時用のデバイスが存在します。

このように、物理的に接続されているデバイスだけではなく、仮想的なデバイスがいくつかあります。
クライアントがデバイスを判断する際は、名前やクラスの情報を見て、何のデバイスかを判断するか、もしくは、対象のデバイスを直接ユーザーに選択してもらう必要があります。