X11: XInput (3) - イベント

XI2 イベント
XI2 のイベントは、Xlib と同じような形で、個々のウィンドウに対してイベントを選択することで、クライアントが受信することができます。
イベントの選択
int XISelectEvents(Display *dpy, Window win, XIEventMask *masks, int num_masks);

typedef struct {
    int deviceid;
    int mask_len;
    unsigned char *mask;
} XIEventMask;

XISelectEvents で、指定ウィンドウに対して、XI2 イベントを選択します。

masks の配列によって、複数のデバイスごとに、それぞれイベントマスクを指定することができます。
num_masks は、masks の配列の数です。

XSelectInput() と同じで、現在のイベントマスクすべてを上書きする形になるので、注意してください。

XIEventMask 構造体
deviceid は、XIAllDevices または XIAllMasterDevices も指定することができます。
mask_len は、mask のバイト数。
mask の可変長配列で、イベントマスクを指定します。

mask の値は、最初のバイト&下位ビットから順に、イベントマスク (1 << event_type) のビットをセットします。

将来的にイベントの数が増えるのを見越して、mask は可変長サイズになっています。
ローカル変数で固定長の配列を用意して、そのポインタをセットするか、メモリ確保するなどしてください。

現在の最新バージョン (ver 2.4) では、イベントは 32 個あります。
クライアント側で必要になる最大の値をセットできるだけの配列を用意する必要があります。

イベントマスク
ver 2.4 時点で、以下のイベントマスクが存在します。

XI_DeviceChangedMask
XI_KeyPressMask
XI_KeyReleaseMask
XI_ButtonPressMask
XI_ButtonReleaseMask
XI_MotionMask
XI_EnterMask
XI_LeaveMask
XI_FocusInMask
XI_FocusOutMask
XI_HierarchyChangedMask
XI_PropertyEventMask
XI_RawKeyPressMask
XI_RawKeyReleaseMask
XI_RawButtonPressMask
XI_RawButtonReleaseMask
XI_RawMotionMask
XI_TouchBeginMask
XI_TouchEndMask
XI_TouchOwnershipChangedMask
XI_TouchUpdateMask
XI_RawTouchBeginMask
XI_RawTouchEndMask
XI_RawTouchUpdateMask
XI_BarrierHitMask
XI_BarrierLeaveMask
イベントの取得
XI2 では、GenericEvent 拡張機能を使うことで、複雑で大きいサイズのイベントデータを扱うことができるようになっています。
これは、通常の X イベントとは異なる方法で読み込む必要があります。

手順は以下のとおりです。

  1. Xlib の関数で、通常通り XEvent 構造体にデータを読み込む (以下、変数名が xev であるとする)。
  2. xev.type が GenericEvent であれば、GenericEvent である。
    (他の拡張機能も GenericEvent を使う可能性があるので、さらに以下を判断する)
  3. xev.xcookie.extension (xcookie = XGenericEventCookie 構造体) が、XInput のオペコード (XQueryExtension 関数で取得した値) と同じであれば、XInput のイベントである。
  4. XGetEventData(disp, &xev.xcookie) で、イベントの追加データを、XGenericEventCookie 構造体の data メンバに確保して読み込む。
  5. data メンバのポインタが、実際の XI イベントの構造体のポインタになっています。
    xev.xcookie.evtype または、(XIEvent *) に型変換して、evtype の値から XI イベントのタイプを判定した後、各 XI イベントごとの構造体のポインタに型変換する。
  6. イベントを処理したら、最後に XFreeEventData(disp, &xev.xcookie) で、追加のデータを解放する。

XEvent ev;
XIEvent *pxi;

XNextEvent(disp, &ev);

if(ev.type == GenericEvent)
{
    if(ev.xcookie.extension == opcode_xi)
    {
        if(XGetEventData(disp, &ev.xcookie))
        {
            pxi = (XIEvent *)ev.xcookie.data;

            if(pxi->evtype == XI_DeviceChanged)
                _event((XIDeviceChangedEvent *)pxi);
            
            XFreeEventData(disp, &ev.xcookie);
        }
    }
}
GenericEvent 用のイベント構造体と関数
typedef union _XEvent {
 ...
 XGenericEvent       xgeneric;
 XGenericEventCookie xcookie;
 ...
} XEvent;

typedef struct {
    int            type;      /* GenericEvent */
    unsigned long  serial;
    Bool           send_event;
    Display        *display;
    int            extension; /* opcode */
    int            evtype;
    unsigned int   cookie;
    void           *data;
} XGenericEventCookie;

Bool XGetEventData(Display *display, XGenericEventCookie *cookie);
void XFreeEventData(Display *display, XGenericEventCookie *cookie);

X11 R7.7 の Xlib リファレンスの方では記載されていませんが、これらの構造体や関数は Xlib.h で定義されています。

XGetEventData() は、成功した場合 True が返ります。
イベントが無効なものであったり、すでに追加のデータが取得されている場合は、False が返ります。
XIEvent 構造体
typedef struct {
    int           type;    /* GenericEvent */
    unsigned long serial;
    Bool          send_event;
    Display       *display;
    int           extension;
    int           evtype;
    Time          time;
} XIEvent;

XIEvent 構造体は、XI2 のすべてのイベント構造体の先頭に、共通して存在する値を定義した構造体です。
XI2 のイベントタイプを判定するには、evtype 値を参照します。
XI_DeviceChanged イベント
イベントの一例として、ここでは XI_DeviceChanged イベントを紹介します。

typedef struct {
    int           type;     /* GenericEvent */
    unsigned long serial;
    Bool          send_event;
    Display       *display;
    int           extension;
    int           evtype;   /* XI_DeviceChanged */
    Time          time;
    int           deviceid;
    int           sourceid;
    int           reason;
    int           num_classes;
    XIAnyClassInfo **classes;
} XIDeviceChangedEvent;

このイベントは、マスターデバイスにアタッチされているスレーブデバイスが変更された時などに来ます。

例えば、直前までマウスを操作していた状態で、ペンタブレット上のペンを操作した時などです。

その場合、マスターポインタに、マウスのスレーブデバイスがアタッチされていた状態から、ペンタブレットのペンを操作した瞬間、ペンタブレットのスレーブデバイスに切り替わることになります。

deviceid変更されたデバイスの ID
sourceidソースデバイスの ID
reason変更された理由。
XISlaveSwitch (1) : マスターデバイス上で、スレーブデバイスが変更された。sourceid は、新しいスレーブデバイス。
XIDeviceChange (2) : 物理デバイスの変更など。
classesデバイスによって提供される、利用可能なクラスの詳細

ペンタブレットで、ペンに消しゴム機能がある場合は、ペンを逆さにして消しゴム状態にして操作すると、このイベントが来て、スレーブデバイスが切り替わります (Stylus → Eraser)。逆も同様です。
プログラム
XI_DeviceChanged イベントを受け取って、表示します。
※ただし、ポインタとキーボードのいずれかで、切り替えられる2つ以上のデバイスがないと、何も起こりません。

XI2 用のユーティリティ関数を、util_xi.c にまとめています。
以降はこれも使っていくので、コンパイル時にこのファイルを含めてください。


$ cc -o run e09-xi3.x util.c util_xi.c -lXlib -lXi

<e09-xi3.c>
#include <stdio.h>
#include <X11/Xlib.h>
#include <X11/extensions/XInput2.h>
#include "util.h"

static void _xi2_event(XIEvent *ev)
{
    XIDeviceChangedEvent *p;

    if(ev->evtype == XI_DeviceChanged)
    {
        p = (XIDeviceChangedEvent *)ev;
        
        printf("<XI_DeviceChanged>\n"
        " deviceid(%d) sourceid(%d) reason(%d) num_classes(%d)\n",
            p->deviceid, p->sourceid, p->reason, p->num_classes);
    }
}

int main(int argc,char **argv)
{
    Display *disp;
    Window win;
    XEvent ev;
    void *data;
    
    disp = XOpenDisplay(NULL);
    if(!disp) return 1;

    set_display(disp);

    if(xi2_init())
    {
        printf("unsupported XI2\n");
        XCloseDisplay(disp);
        return 1;
    }

    //

    win = create_test_window2(disp, 200, 200, 0,
        ButtonPressMask | KeyPressMask);

    xi2_select_event(win, XIAllMasterDevices, XI_DeviceChangedMask);

    //イベント

    XMapWindow(disp, win);

    while(1)
    {
        XNextEvent(disp, &ev);

        if(event_quit(&ev)) break;

        switch(ev.type)
        {
            case ButtonPress:
                printf("* ButtonPress\n");
                break;
            case KeyPress:
                printf("* KeyPress\n");
                break;
            case GenericEvent:
                data = xi2_get_event(&ev);
                if(data)
                {
                    _xi2_event((XIEvent *)data);
                    
                    XFreeEventData(disp, (XGenericEventCookie *)&ev);
                }
                break;
        }
    }

    //

    XCloseDisplay(disp);

    return 0;
}
実行例
<XI_DeviceChanged>
 deviceid(2) sourceid(10) reason(1) num_classes(12)
<XI_DeviceChanged>
 deviceid(2) sourceid(11) reason(1) num_classes(7)

deviceid = 2 は、マスターポインタです。
deviceid = 10 がペンタブレット、deviceid = 11 がマウスです。

マウスを操作していた状態から、ペンタブレットのペンを操作すると、XI_DeviceChanged が来て、マスターポインタのソースデバイスが、ペンタブレットに切り替わったことが通知されます。

また、再びマウスを操作すると、XI_DeviceChanged が来て、ソースデバイスがマウスに切り替わったことが通知されます。

なお、ポインタボタンやキーを押すと、通常の X イベントが来るのを確認してください。
XI の対象イベントを選択しない限り、通常の X イベントとして処理されます。