Wayland - ポインタデバイス入力

ポインタデバイス入力
ウィンドウ内でのポインタデバイスの動作を取得して、情報を表示します。

なお、今回、ウィンドウは wl_shell_surface で作成しています。
(今後 xdg-shell のバージョンが上がったりすると、書き直すのが面倒なので)
ウィンドウ内で中ボタンを押すと、プログラムが終了します。

> common1.h
> common1.c

$ cc -o test 07_pointer.c common1.c -lwayland-client -lrt

<07_pointer.c>
#include <stdio.h>
#include <string.h>
#include <linux/input.h>

#include "common1.h"

//-------------

typedef struct
{
    wayland base;

    struct wl_seat *seat;
    struct wl_pointer *pointer;
    uint32_t seat_version;

    int quit_flag;
}wayland_ex;

#define _FLUSH   fflush(stdout)

//-------------


static void _pointer_release(wayland_ex *p)
{
    if(p->seat_version >= WL_POINTER_RELEASE_SINCE_VERSION)
        wl_pointer_release(p->pointer);
    else
        wl_pointer_destroy(p->pointer);

    p->pointer = NULL;
}

//-----------  wl_pointer

/* ポインタがサーフェス内に入った時 */

static void _pointer_enter(void *data, struct wl_pointer *pointer,
    uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y)
{
    printf("enter: (%.2f, %.2f)\n",
        (double)x / (1<<8), (double)y / (1<<8));
    _FLUSH;
}

/* ポインタがサーフェス外に出た時 */

static void _pointer_leave(void *data, struct wl_pointer *pointer,
    uint32_t serial, struct wl_surface *surface)
{
    printf("leave\n");
    _FLUSH;
}

/* サーフェス内でポインタが移動した時 */

static void _pointer_motion(void *data, struct wl_pointer *pointer,
    uint32_t time, wl_fixed_t x, wl_fixed_t y)
{
    printf("motion: (%.2f, %.2f), time %u\n",
        (double)x / (1<<8), (double)y / (1<<8), time);
    _FLUSH;
}

/* ボタンが押された時と離された時 */

static void _pointer_button(void *data, struct wl_pointer *pointer,
    uint32_t serial, uint32_t time, uint32_t button, uint32_t state)
{
    printf("button: %s, no 0x%X, time %u\n",
        (state == WL_POINTER_BUTTON_STATE_PRESSED)? "press": "release",
        button, time);
    _FLUSH;

    //中ボタンで終了

    if(button == BTN_MIDDLE)
        ((wayland_ex *)data)->quit_flag = 1;
}

/* ホイールスクロール、または他の軸 */

static void _pointer_axis(void *data, struct wl_pointer *pointer,
    uint32_t time, uint32_t axis, wl_fixed_t value)
{
    printf("axis: %s, value %.2f, time %u\n",
        (axis == WL_POINTER_AXIS_VERTICAL_SCROLL)? "vert": "horz",
        (double)value / (1<<8), time);
    _FLUSH;
}

//以下は、ver 5 から

/* 属している一連のイベントの終了時 */

static void _pointer_frame(void *data, struct wl_pointer *pointer)
{
    printf("frame\n");
    _FLUSH;
}

/* 軸のソース情報 (frame イベントの前) */

static void _pointer_axis_source(void *data, struct wl_pointer *pointer, uint32_t axis_source)
{
    printf("axis_source: %u\n", axis_source);
    _FLUSH;
}

/* 軸の通知の停止 */

static void _pointer_axis_stop(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis)
{
    printf("axis_stop: axis %u, time %u\n", axis, time);
    _FLUSH;
}

/* 軸のステップ情報 */

static void _pointer_axis_discrete(void *data, struct wl_pointer *pointer, uint32_t axis, int32_t discrete)
{
    printf("axis_discrete: axis %u, discrete %d\n", axis, discrete);
    _FLUSH;
}

static const struct wl_pointer_listener g_pointer_listener = {
    _pointer_enter, _pointer_leave, _pointer_motion, _pointer_button,
    _pointer_axis, _pointer_frame, _pointer_axis_source,
    _pointer_axis_stop, _pointer_axis_discrete
};

//----------- wl_seat

static void _seat_capabilities(void *data, struct wl_seat *seat, uint32_t cap)
{
    wayland_ex *p = (wayland_ex *)data;

    printf("seat.capabilities: cap 0x%u\n", cap);

    if((cap & WL_SEAT_CAPABILITY_POINTER) && !p->pointer)
    {
        //wl_pointer 作成

        p->pointer = wl_seat_get_pointer(seat);

        wl_pointer_add_listener(p->pointer, &g_pointer_listener, p);

    }
    else if(!(cap & WL_SEAT_CAPABILITY_POINTER) && p->pointer)
    {
        //wl_pointer 解放

        _pointer_release(p);
    }
}

static void _seat_name(void *data, struct wl_seat *wl_seat, const char *name)
{
    printf("seat.name: %s\n", name);
}

static const struct wl_seat_listener g_seat_listener = {
    _seat_capabilities, _seat_name
};

//------------ wl_registry

static void _registry_global(
    void *data,struct wl_registry *reg,uint32_t id,const char *itf,uint32_t ver)
{
    wayland_ex *p = (wayland_ex *)data;

    if(strcmp(itf, "wl_seat") == 0)
    {
        if(ver >= 5) ver = 5;

        p->seat = wl_registry_bind(reg, id, &wl_seat_interface, ver);
        p->seat_version = ver;

        wl_seat_add_listener(p->seat, &g_seat_listener, p);
    }
}

//---------------

int main(void)
{
    wayland_ex *wl;
    window *win;

    wl = (wayland_ex *)wayland_new(sizeof(wayland_ex));

    wl->base.handle_registry_global = _registry_global;

    wayland_init(WAYLAND_PTR(wl));

    //ウィンドウ

    win = window_create(WAYLAND_PTR(wl), 256, 256);

    imagebuf_fill(win->img, 0xff0000);
    window_update(win);

    //

    while(wl_display_dispatch(wl->base.display) != -1
        && wl->quit_flag == 0);

    //解放

    window_destroy(win);

    _pointer_release(wl);
    wl_seat_destroy(wl->seat);

    wayland_destroy(WAYLAND_PTR(wl));

    return 0;
}
解説
wl_seat
wl_seat は、入力デバイス (ポインタ、キーボード、タッチ) の管理を行います。
wl_seat を使うと、それぞれのデバイス用のオブジェクトを作成できます。

まずは、wl_registry のハンドラ内で、wl_seat をバインドします。

if(strcmp(itf, "wl_seat") == 0)
{
    if(ver >= 5) ver = 5;

    p->seat = wl_registry_bind(reg, id, &wl_seat_interface, ver);
    p->seat_version = ver;

    wl_seat_add_listener(p->seat, &g_seat_listener, p);
}

2017/09 時点での最新バージョンは 5 です。
なので、使用する最大のバージョンは 5 としています。

今回は、バインドした wl_seat のバージョンを記録しておきます。
wl_seat イベントハンドラ
static void _seat_capabilities(void *data, struct wl_seat *seat, uint32_t cap)
{
    wayland_data *p = (wayland_data *)data;

    printf("seat.capabilities: cap 0x%u\n", cap);

    if((cap & WL_SEAT_CAPABILITY_POINTER) && !p->pointer)
    {
        //wl_pointer 作成

        p->pointer = wl_seat_get_pointer(seat);

        wl_pointer_add_listener(p->pointer, &g_pointer_listener, p);

    }
    else if(!(cap & WL_SEAT_CAPABILITY_POINTER) && p->pointer)
    {
        //wl_pointer 解放

        _pointer_release(p);
    }
}

static void _seat_name(void *data, struct wl_seat *wl_seat, const char *name)
{
    printf("seat.name: %s\n", name);
}

capabilities イベント
各デバイスが使用できるようになった時と、使用できなくなった時に呼ばれます。

引数 cap は、デバイスのタイプのフラグになっています。

WL_SEAT_CAPABILITY_POINTER = 1
WL_SEAT_CAPABILITY_KEYBOARD = 2
WL_SEAT_CAPABILITY_TOUCH = 4

フラグが ON の場合、デバイスが使用できる状態にあります。
wl_seat_get_pointer() などの関数を使って、各デバイスに対応したオブジェクトを作成できます。

フラグが OFF の場合は、USB プラグからデバイスが外されたりして、デバイスが使用できなくなった状態なので、作成したオブジェクトを破棄する必要があります。

今回はポインタデバイスしか使わないので、その分の処理しかしていませんが、キーボードなど他のデバイスも使う場合は、同じように処理してください。

name イベント
seat の名前が通知されます。
複数の seat がある場合、それぞれを判別する時に使います。
weston では "default" でしたが、GNOME では "seat0" でした。

※ wl_seat の ver 2 以降

オブジェクトの破棄について
capabilities イベントで、各デバイスが使用できない状態になった時は、取得したオブジェクトを破棄する必要があります。

wl_seat のバージョンが 5 以上の場合は、wl_<name>_release() を使って解放し、
バージョンが 4 以下の場合は、wl_<name>_destroy() を使って破棄します。

この2つについて、wayland-client-protocol.h でインライン定義を見てみると、以下のようになっています。

static inline void wl_pointer_destroy(struct wl_pointer *wl_pointer)
{
    wl_proxy_destroy((struct wl_proxy *) wl_pointer);
}

static inline void wl_pointer_release(struct wl_pointer *wl_pointer)
{
    wl_proxy_marshal((struct wl_proxy *) wl_pointer,
             WL_POINTER_RELEASE);

    wl_proxy_destroy((struct wl_proxy *) wl_pointer);
}

wl_proxy_destroy() で破棄するのは同じですが、release の場合は、破棄する前に wl_proxy_marshal() で解放処理を要求しています。

他のオブジェクトに関しても、wl_*_release() が存在する場合は、使用しているインターフェイスのバージョンを比較して、destroy/release のいずれかを使って破棄する必要があります。
wl_pointer
wl_pointer は、ポインタデバイスの管理を行います。

wl_pointer にハンドラをセットすると、クライアント上のすべてのウィンドウにおけるポインタイベントを、指定したハンドラ関数で取得できます。

どのウィンドウ上で操作されたかは、struct wl_surface *surface 引数で判別できます。
複数のウィンドウがある場合は、それを使って、各ウィンドウごとに処理を分ける必要があります。
wl_pointer イベントハンドラ
[ frame, axis_source, axis_stop, axis_discrete ] に関しては、wl_seat の ver 5 以降で使用できます。

enter
void (*enter)(void *data, struct wl_pointer *pointer,
    uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y);

ポインタがウィンドウ内に入った時に呼ばれます。

カーソル形状はサーバー側では何も処理されないので (ウィンドウに入ったら自動でカーソルを変えるような処理はしない)、クライアント側でカーソル形状を変える処理をする必要があります。
wl_pointer_set_cursor() を使うと、wl_surface のイメージをカーソル形状としてセットできます。

引数の wl_fixed_t 型は、int32_t 型として定義されていて、24:8 の固定少数点数となっています。
1.0 = 1<<8 = (wl_fixed_t) 256 となるので、浮動小数点数に変換する場合は、256 で割ります。
整数部分だけ取得したい場合は、>> 8 で 8bit 右シフトします。
x, y は、指定サーフェスにおけるポインタの座標です。

leave
void (*leave)(void *data, struct wl_pointer *pointer,
    uint32_t serial, struct wl_surface *surface);

ポインタがウィンドウ外に出た時に呼ばれます。

motion
void (*motion)(void *data, struct wl_pointer *pointer,
    uint32_t time, wl_fixed_t x, wl_fixed_t y);

ウィンドウ内でポインタが移動した時に呼ばれます。

button
void (*button)(void *data, struct wl_pointer *pointer,
    uint32_t serial, uint32_t time, uint32_t button, uint32_t state);

ウィンドウ内で、ボタンが押された or 離された時に呼ばれます。
ポインタ位置は渡されないので、最後の enter/motion イベントから参照してください。
time は、ミリ秒単位のタイムスタンプです。ダブルクリックの判定などで使います。

button はボタン番号ですが、/usr/include/linux/input-event-codes.h で定義されている値を使います。
マウスボタンは、BTN_LEFT などとして定義されています。

state は、enum wl_pointer_button_state の値です。
押されている or 離されたの状態を判別できます。
WL_POINTER_BUTTON_STATE_RELEASED = 0
WL_POINTER_BUTTON_STATE_PRESSED = 1

ポインタのグラブ (キャプチャ) は行う必要がなく、ボタンが押されている間は、ポインタがウィンドウ外に出た場合でも、そのままイベントを受け取ることができます。
また、その場合、leave イベントはボタンが離された後に送られてきます。

逆に言うと、クライアント側で自由にグラブを行うことはできません。

axis
void (*axis)(void *data, struct wl_pointer *pointer,
    uint32_t time, uint32_t axis, wl_fixed_t value);

ホイールなどのスクロールが行われた時に呼ばれます。

axis は、移動した軸です。enum wl_pointer_axis の値となっています。
WL_POINTER_AXIS_VERTICAL_SCROLL = 0
WL_POINTER_AXIS_HORIZONTAL_SCROLL = 1

value は、移動した方向と量です。weston 上でマウスホイールを動かすと、±10.0 の値になりました。
増減幅は、おそらくサーバーごとにユーザー設定などで変更できるようになっているのだと思います。

斜め方向などに対応したデバイスでは、複数の axis イベントが出力されます。

frame
void (*frame)(void *data, struct wl_pointer *pointer);

例えば、スクロールで斜め方向に移動した場合は複数の axis イベントが出力されますが、それらをひとかたまりのイベントとして、一連のイベントが終了した時に呼ばれます。

axis (vert) -> axis (horz) -> frame などといった感じになります。
axis に限らず、motion や button など全てのイベントで呼ばれます。

axis_discrete: axis 0, discrete -1
axis: vert, value -10.00, time 3225991463
frame
motion: (109.00, 99.00), time 3225993063
frame
leave
frame

axis_source
void (*axis_source)(void *data, struct wl_pointer *pointer, uint32_t axis_source);

frame イベントの前にオプションとして出力され、axis の軸のソース情報が渡されます。

axis_source 引数は、enum wl_pointer_axis_source の値です。
WL_POINTER_AXIS_SOURCE_WHEEL = 0
WL_POINTER_AXIS_SOURCE_FINGER = 1
WL_POINTER_AXIS_SOURCE_CONTINUOUS = 2
WL_POINTER_AXIS_SOURCE_WHEEL_TILT = 3

WL_POINTER_AXIS_SOURCE_FINGER の場合は、ユーザーが指を離した時に axis_stop イベントが送信されます。

axis_stop
void (*axis_stop)(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis);

axis の軸の停止が通知されます。

axis_discrete
void (*axis_discrete)(void *data, struct wl_pointer *pointer, uint32_t axis, int32_t discrete);

axis の軸のステップ情報。
axis イベントと一緒に送信されます。

discrete はステップ値です。
-1 なら、負の方向に1段階ステップするという意味です。
マウスホイールの場合は、通常 ±1 の値となります。