X11: XInput (4) - ポインタ・キーイベント

ポインタ・キーイベント
XI2 でポインタとキーのイベントを受け取りたい場合は、各イベントマスクを選択します。

XI_KeyPressMask, XI_KeyReleaseMask, XI_ButtonPressMask, XI_ButtonReleaseMask, XI_MotionMask
マスターデバイスと X イベント
マスターデバイスで上記のイベントを選択した場合、それに対応する X の通常イベント (KeyPress, ButtonPress など) は報告されません。

マスターキーボードで XI_KeyPressMask を選択した場合、X の KeyPress イベントは送信されません。
マスターポインタで XI_ButtonPressMask を選択した場合、X の ButtonPress イベントは送信されません。

二重にキー押しなどのイベントが送信されることはないので、コア X か XI2 の、どちらかのイベントを使うことになります。
スレーブデバイス
なお、マスターデバイスではなく、スレーブデバイスのみで XI2 イベントを選択した場合は、そのスレーブデバイスが操作された時のみ XI2 イベントが報告され、それ以外のデバイスでは、X の通常イベントが報告されます。

例えば、ペンタブレットのデバイスのみで XI_ButtonPressMask などを選択した場合、マウスを操作した時は通常の X イベントが来て、ペンタブレット操作時は XI2 イベントが来ます。

デバイスごとに、どちらのイベントを使うかを選択することができます。
XIDeviceEvent 構造体
以下の XI2 イベントは、共通で XIDeviceEvent のイベント構造体を使います。

XI_KeyPress, XI_KeyRelease, XI_ButtonPress, XI_ButtonRelease, XI_Motion
(ver 2.2 以降) XI_TouchBegin, XI_TouchUpdate, XI_TouchEnd

typedef struct {
    int           type;
    unsigned long serial;
    Bool          send_event;
    Display       *display;
    int           extension;
    int           evtype;
    Time          time;
    int           deviceid;
    int           sourceid;
    int           detail;
    Window        root;
    Window        event;
    Window        child;
    double        root_x;
    double        root_y;
    double        event_x;
    double        event_y;
    int           flags;
    XIButtonState       buttons;
    XIValuatorState     valuators;
    XIModifierState     mods;
    XIGroupState        group;
} XIDeviceEvent;

deviceidデバイス ID
sourceidソースのデバイス ID。
マスターデバイスなら、イベントの送信元のデバイス ID。
detailボタンの場合、ボタン番号 (1〜)。
キーの場合、キーコード。
タッチの場合、タッチ ID。
root,event,childルートウィンドウ、イベントが起こったウィンドウ、直下の子ウィンドウ
root_x,root_yルート座標 (double)
event_x,event_yevent ウィンドウでの座標 (double)
flagsXIKeyRepeat (1<<16) : (KeyPress 時) キーリピートによって生成されたキー押し。

XIPointerEmulated (1<<16) : (ポインタ時) XI2 によってエミュレートされた、ホイールのボタンイベント。
Rel Vert Scroll 軸または Rel Horiz Scroll 軸の、スムーズスクロールイベント (XI_Motion) も同時に生成された場合、このフラグは、XI_ButtonPress および XI_RawButtonPress イベント (Button 4〜7) で設定されます。
また、ダイレクトタッチデバイスによって生成される Motion、ButtonPress、ButtonRelease イベントでも設定されます。

XITouchPendingEnd (1<<16) : (タッチ時) タッチが物理的に終了したが、別のクライアントがまだグラブを保持しているため、すべてのグラブクライアントが所有権を受け入れるか渡すまで、タッチは生きていると見なされることを意味します。
TouchPendingEnd を持つイベントが受信されると、タッチはそれ以上 TouchUpdate イベントを生成しません。

XITouchEmulatingPointer (1<<17) : (タッチ時) ポインタイベントをエミュレートするタッチイベントで設定されます。
buttonsこのイベント発生前のボタンの状態。
※このイベントで押された/離されたボタンは含みません。
valuatorsイベントで報告される、このデバイスのバリュエータの値。
modsこのイベント発生前の XKB 修飾子の状態
groupこのイベント発生前の XKB キーグループの状態
キーリピート
コア X のキーリピートが有効な場合、通常の X イベントでは、キーを押している間は以下のように送られてきます。

KeyPress -> (repeat) KeyRelease -> (repeat) KeyPress ... -> KeyRelease

KeyRelease の直後に KeyPress があり、イベント時間が同じ場合は、キーリピートとして判断することができます。
ただし、イベントの格納状態によっては、途中で途切れる場合があるので、正確にすべてキーリピートかどうかを判断することができません。

XI2 イベントでのキーリピートの場合は、以下のように送られてきます。

XI_KeyPress -> XI_KeyPress(flags=XIKeyRepeat) -> XI_KeyPress(flags=XIKeyRepeat) -> XI_KeyRelease

このように、キーリピート中は XI_KeyRelease イベントが発生せず、キーリピートによるキー押しであれば、flags でそれを判定することができるので、より正確に、便利に判断することができます。
ホイールによるスクロール
XInput ver 2.1 以降で、デバイスにホイールスクロールの機能がある場合、ホイールを X 従来のボタン (Button4〜7) としてだけではなく、一つのスクロール軸として、XI_Motion イベントで処理することができます。

X サーバーは、各デバイスの水平/垂直のホイールスクロールの値を蓄積しており、ホイールが1度操作されるごとに、バリュエータの ScrollClass で設定されている1単位の値を増減させます。

例えば、垂直ホイールを上に2回操作した場合、以下のようにイベントが来ます。
スクロールの1単位は 120.0 になっています。

[1回目]
XI_Motion: valuator[Rel Vert Scroll] = 78600.0
XI_ButtonPress: detail = 4, flags = XIPointerEmulated, valuator = None
XI_ButtonRelease: detail = 4, flags = XIPointerEmulated, valuator = None

[2回目]
XI_Motion: valuator[Rel Vert Scroll] = 78480.0
XI_ButtonPress: detail = 4, flags = XIPointerEmulated, valuator = None
XI_ButtonRelease: detail = 4, flags = XIPointerEmulated, valuator = None

最初に XI_Motion が来て、蓄積されている垂直スクロール値 78600.0 が、バリュエータ (Rel Vert Scroll) で報告されます。
その後、X の通常イベントと同じように、Button4 (Wheel Up) のボタン押しとボタン離しが来ます。
この時、flags に XIPointerEmulated が設定されています。
XI_Motion イベントでスクロールが報告されている場合は、このフラグが ON になっているので、必要に応じてボタンイベントを処理しないようにすることができます。

2回目に操作した場合、スクロールの蓄積値は、1単位の 120 が減って、78600 - 120 = 78480 になっています。

ホイールのスクロールを、蓄積された値として扱うかどうかはクライアントによるので、従来のようにホイールをボタンとして扱いたい場合は、そのまま XI_ButtonPress で判定できます。
buttons
typedef struct {
    int mask_len;
    unsigned char *mask;
} XIButtonState;

buttons で、ポインタボタンの現在の押し状態を取得できます。
データはデバイス情報の時と同じです。

※このイベントが発生する前のボタンの状態です。XI_ButtonPress/XI_ButtonRelease で変化したボタンは、ここに含まれていないので、注意してください。

何もボタンが押されていない状態で Button1 を押して、XI_ButtonPress が来た時、buttons には全くフラグが設定されていません。
valuators
typedef struct {
    int           mask_len;
    unsigned char *mask;
    double        *values;
} XIValuatorState;

valuators には、デバイスの各バリュエータの、現在の値が指定されています。
可変個数で、このイベントで変化した値だけが指定されている場合があるので、注意してください。

mask_len と mask は、上記の XIButtonState と同じようなデータです。可変長バイトで、フラグが指定されています。
values には、先頭から順に、必要なバリュエータの値だけが、複数個格納されています。

mask では、このデバイスの各バリュエータの番号 (XIValuatorClassInfo の number の値。0〜) が、そのままビット位置となります。
values で値が存在するバリュエータのフラグがセットされています。

例えば、mask で bit0, bit5 が ON になっている場合、このデバイスのバリュエータ番号 0 と 5 の値があるということなので、values[0] にバリュエータ番号 0 の値、values[1] にバリュエータ番号 5 の値があります。

ペンタブレットで、座標や筆圧が前回のイベントの値と同じ場合、一部のバリュエータの値だけ報告されない場合がありました。
すべてのイベントにおいて、必要な値が毎回報告されるとは限らないので、前回の値を記録しておくなどの対策を行ってください。
mods,group
typedef struct
{
    int    base;
    int    latched;
    int    locked;
    int    effective;
} XIModifierState;

typedef XIModifierState XIGroupState;

mods には、このイベント発生前の XKB のキー修飾子の状態。
group には、このイベント発生前の XKB のキーグループの状態が指定されています。

mods の場合は、ShiftMask、LockMask、ControlMask、Mod1Mask〜Mod5Mask のフラグで、group の場合は、グループインデックス (0〜) です。

詳細については、XKB 拡張機能を参照していただきますが、ここでは簡単に説明しておきます。

base現在、物理的または論理的に押されているキー
latchedラッチ状態のキー。
(XKB の機能によって、一度 Shift などの修飾キーを押して離すと、ラッチ状態になり、次に押されたキーが +Shift になる。その後、ラッチ状態は解除される)
lockedロック状態のキー。
NumLock など、キーを押して離すと、ロック状態が設定/解除されるもの。
物理的にキーが押されていない状態でも、有効である。
effective現在有効なキー。
base, latched, locked によって計算される。基本的にはすべてを含む。

キーグループが切り替わると、異なる文字セットに切り替わります。
基本的にはすべて 0 の場合が多いです。
プログラム
マスターデバイス(キーボードとポインタ)で、キーイベントとポインタイベントを選択し、イベントを表示します。

実行時、最初の引数にデバイスIDを指定すると、そのデバイスだけでイベントを選択します。

$ cc -o run e10-xi4.x util.o util_xi.c -lXlib -lXi

<e10-xi4.c>
#include <stdio.h>
#include <stdlib.h>
#include <X11/Xlib.h>
#include <X11/extensions/XInput2.h>
#include "util.h"

static unsigned int g_count = 0;

static void _xi2_event(XIDeviceEvent *ev)
{
    int i;
    double *dval;

    printf("[%u] ", g_count++);

    switch(ev->evtype)
    {
        case XI_KeyPress:
            printf("<XI_KeyPress>\n");
            break;
        case XI_KeyRelease:
            printf("<XI_KeyRelease>\n");
            break;
        case XI_ButtonPress:
            printf("<XI_ButtonPress>\n");
            break;
        case XI_ButtonRelease:
            printf("<XI_ButtonRelease>\n");
            break;
        case XI_Motion:
            printf("<XI_Motion>\n");
            break;
    }

    printf(" deviceid(%d) sourceid(%d) detail(%d)"
        " root(0x%04x) event(0x%04x) child(0x%04x)\n"
        " root_x(%.3f) root_y(%.3f) event_x(%.3f) event_y(%.3f) flags(0x%x)\n",
        ev->deviceid, ev->sourceid, ev->detail,
        (unsigned int)ev->root, (unsigned int)ev->event, (unsigned int)ev->child,
        ev->root_x, ev->root_y, ev->event_x, ev->event_y, ev->flags);

    //buttons

    printf(" buttons(len:%d): ", ev->buttons.mask_len);

    for(i = 0; i < ev->buttons.mask_len * 8; i++)
    {
        if(ev->buttons.mask[i / 8] & (1 << (i & 7)))
            printf("btn%d,", i);
    }

    printf("\n");

    //valuators

    printf(" valuators(len:%d): ", ev->valuators.mask_len);

    dval = ev->valuators.values;

    for(i = 0; i < ev->valuators.mask_len * 8; i++)
    {
        if(ev->valuators.mask[i / 8] & (1 << (i & 7)))
            printf("val%d(%.3f),", i, *(dval++));
    }

    printf("\n");

    //mods

    printf(" mods: base(0x%x) latched(0x%x) locked(0x%x) effective(0x%x)\n",
        ev->mods.base, ev->mods.latched, ev->mods.locked, ev->mods.effective);

    //group

    printf(" group: base(%d) latched(%d) locked(%d) effective(%d)\n",
        ev->group.base, ev->group.latched, ev->group.locked, ev->group.effective);
}

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,
        (argc < 2)? XIAllMasterDevices: atoi(argv[1]),
        XI_KeyPressMask | XI_KeyReleaseMask
            | XI_ButtonPressMask | XI_ButtonReleaseMask | XI_MotionMask);

    //イベント

    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((XIDeviceEvent *)data);
                    
                    XFreeEventData(disp, (XGenericEventCookie *)&ev);
                }
                break;
        }
    }

    //

    XCloseDisplay(disp);

    return 0;
}
解説
マスターデバイスでイベントを選択すると、通常の X イベントが来ないのを確認してください。

また、実行時に引数でデバイスIDを指定して、それ以外のデバイスを操作すると、通常の X イベントが来るのを確認してください。
ポインタイベント
# ポインタ移動 (X 位置)
[25] <XI_Motion>
 deviceid(2) sourceid(11) detail(0) root(0x0764) event(0x1a00002) child(0x0000)
 root_x(127.409) root_y(109.166) event_x(126.409) event_y(89.166) flags(0x0)
 buttons(len:32): 
 valuators(len:8): val0(127.409),
 mods: base(0x0) latched(0x0) locked(0x10) effective(0x10)
 group: base(0) latched(0) locked(0) effective(0)

# 左ボタン押し
[26] <XI_ButtonPress>
 deviceid(2) sourceid(11) detail(1) root(0x0764) event(0x1a00002) child(0x0000)
 root_x(127.409) root_y(109.166) event_x(126.409) event_y(89.166) flags(0x0)
 buttons(len:32): 
 valuators(len:8): 
 mods: base(0x0) latched(0x0) locked(0x10) effective(0x10)
 group: base(0) latched(0) locked(0) effective(0)

# 左ボタン離し
[27] <XI_ButtonRelease>
 deviceid(2) sourceid(11) detail(1) root(0x0764) event(0x1a00002) child(0x0000)
 root_x(127.409) root_y(109.166) event_x(126.409) event_y(89.166) flags(0x0)
 buttons(len:32): btn1,
 valuators(len:8): 
 mods: base(0x0) latched(0x0) locked(0x10) effective(0x10)
 group: base(0) latched(0) locked(0) effective(0)

XI_Motion 時、X 位置のみが移動しているので、バリュエータの 0 番 (Rel X) の値だけが報告されています。
バリュエータ 0 は相対値ですが、実際の値は、ルート座標となっています。

左ボタンを押した時、detail = 1 で、ボタン1 が押されました。buttons は空です。
左ボタンを離した時、前回のイベントでボタンが押されているので、buttons は btn1 が ON になっています。

NumLock が ON の状態なので、locked と effective で Mod2Mask が ON になっています。
キー押し
# Shift キー押し
[23] <XI_KeyPress>
 deviceid(3) sourceid(12) detail(50) root(0x0764) event(0x1a00002) child(0x0000)
 root_x(154.000) root_y(175.000) event_x(153.000) event_y(155.000) flags(0x0)
 buttons(len:32): 
 valuators(len:8): 
 mods: base(0x0) latched(0x0) locked(0x10) effective(0x10)
 group: base(0) latched(0) locked(0) effective(0)

# A キー押し
[24] <XI_KeyPress>
 deviceid(3) sourceid(12) detail(38) root(0x0764) event(0x1a00002) child(0x0000)
 root_x(154.000) root_y(175.000) event_x(153.000) event_y(155.000) flags(0x0)
 buttons(len:32): 
 valuators(len:8): 
 mods: base(0x1) latched(0x0) locked(0x10) effective(0x11)
 group: base(0) latched(0) locked(0) effective(0)

# A キー離し
[25] <XI_KeyRelease>
 deviceid(3) sourceid(12) detail(38) root(0x0764) event(0x1a00002) child(0x0000)
 root_x(154.000) root_y(175.000) event_x(153.000) event_y(155.000) flags(0x0)
 buttons(len:32): 
 valuators(len:8): 
 mods: base(0x1) latched(0x0) locked(0x10) effective(0x11)
 group: base(0) latched(0) locked(0) effective(0)

# Shift キー離し
[26] <XI_KeyRelease>
 deviceid(3) sourceid(12) detail(50) root(0x0764) event(0x1a00002) child(0x0000)
 root_x(154.000) root_y(175.000) event_x(153.000) event_y(155.000) flags(0x0)
 buttons(len:32): 
 valuators(len:8): 
 mods: base(0x1) latched(0x0) locked(0x10) effective(0x11)
 group: base(0) latched(0) locked(0) effective(0)

Shift を押した後、A キーを押すと、A キーを押した時点で Shift 修飾子が有効なので、base に ShiftMask、effective にも ShiftMask が追加されています。
(Shift キーは NumLock のようなロック状態ではないので、locked には含まれていません)

Shift キーを離すイベントも含め、そこまでは、Shift 修飾子が有効な状態になっています。

キー修飾子の状態 mods は、そのイベントが発生する前の状態であることに注意してください。
ペンタブレット
ペンタブレットで操作した場合、以下のようになります。

# ペンを押した (Button1)
[135] <XI_ButtonPress>
 deviceid(2) sourceid(10) detail(1) root(0x0764) event(0x1a00002) child(0x0000)
 root_x(76.085) root_y(144.709) event_x(75.085) event_y(124.709) flags(0x0)
 buttons(len:32): 
 valuators(len:8): val0(856.000),val1(1809.000),val2(18697.000),val3(0.000),val4(0.000),
 mods: base(0x0) latched(0x0) locked(0x10) effective(0x10)
 group: base(0) latched(0) locked(0) effective(0)

[136] <XI_Motion>
 deviceid(2) sourceid(10) detail(0) root(0x0764) event(0x1a00002) child(0x0000)
 root_x(75.996) root_y(144.629) event_x(74.996) event_y(124.629) flags(0x0)
 buttons(len:32): btn1,
 valuators(len:8): val0(855.000),val1(1808.000),val2(19273.000),val3(0.000),val4(0.000),
 mods: base(0x0) latched(0x0) locked(0x10) effective(0x10)
 group: base(0) latched(0) locked(0) effective(0)

[137] <XI_Motion>
 deviceid(2) sourceid(10) detail(0) root(0x0764) event(0x1a00002) child(0x0000)
 root_x(75.996) root_y(144.629) event_x(74.996) event_y(124.629) flags(0x0)
 buttons(len:32): btn1,
 valuators(len:8): val0(855.000),val1(1808.000),val2(19337.000),val3(0.000),val4(0.000),
 mods: base(0x0) latched(0x0) locked(0x10) effective(0x10)
 group: base(0) latched(0) locked(0) effective(0)

[138] <XI_Motion>
 deviceid(2) sourceid(10) detail(0) root(0x0764) event(0x1a00002) child(0x0000)
 root_x(75.996) root_y(144.629) event_x(74.996) event_y(124.629) flags(0x0)
 buttons(len:32): btn1,
 valuators(len:8): val0(855.000),val1(1808.000),val2(20906.000),val3(0.000),val4(0.000),
 mods: base(0x0) latched(0x0) locked(0x10) effective(0x10)
 group: base(0) latched(0) locked(0) effective(0)

バリュエータは、[0] X 座標 [1] Y 座標 [2] 筆圧 (0〜65536) となっています。

バリュエータの X・Y 座標は、タブレット面上の絶対座標のため、ルート座標とは異なります。

筆圧は、デバイス情報のバリュエータクラスで min = 0, max = 65536 となっているため、値を 0.0〜1.0 に正規化するには、((val - min) / (max - min)) とする必要があります。

基本的に、座標や筆圧は毎回報告されますが、まれに一部の値だけ報告されない場合があるので、その時は前回のイベントの値を使うようにしてください。
ほか
他にも、XI2 には、デバイスごとのグラブなどの色々な関数があったり、タッチイベントの処理も行えますが、ここでは説明しません。
詳しくは XInput2 をご覧ください (ここでも、詳細に記述していない情報があります)。