tablet v2 : ペンタブレット入力

tablet
unstable の「tablet-unstable-v2.xml」のプロトコルを解説します。

これは、ペンタブレットからの入力を行うためのものです。

2019年12月時点では、GNOME が対応しています。
プログラム
$ cc -o test p03_tablet.c client.c imagebuf.c \
 tablet-unstable-v2-protocol.c -lwayland-client -lrt

scanner で、「tablet-unstable-v2-client-protocol.h」「tablet-unstable-v2-protocol.c」を生成してください。

p03_tablet.c
/******************************
 * tablet-unstable-v2
 ******************************/

#include <stdio.h>
#include <string.h>
#include <linux/input.h>

#include <wayland-client.h>
#include "tablet-unstable-v2-client-protocol.h"

#include "client.h"
#include "imagebuf.h"


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

typedef struct
{
    Client b;

    struct zwp_tablet_manager_v2 *manager;
    struct zwp_tablet_seat_v2 *tablet_seat;
}ClientEx;

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


//----------------------
// zwp_tablet_tool_v2
//----------------------


static void _tablettool_type(void *data,
    struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t tool_type)
{
    printf("zwp_tablet_tool_v2 # type | tool_type:0x%X\n", tool_type);
}

static void _tablettool_hardware_serial(void *data,
    struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2,
    uint32_t hardware_serial_hi, uint32_t hardware_serial_lo)
{
    printf("zwp_tablet_tool_v2 # hardware_serial | 0x%X%08X\n",
        hardware_serial_hi, hardware_serial_lo);
}

static void _tablettool_hardware_id_wacom(void *data,
    struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2,
    uint32_t hardware_id_hi, uint32_t hardware_id_lo)
{
    printf("zwp_tablet_tool_v2 # hardware_id_wacom | 0x%X%08X\n",
        hardware_id_hi, hardware_id_lo);
}

static void _tablettool_capability(void *data,
    struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t capability)
{
    printf("zwp_tablet_tool_v2 # capability | %u\n", capability);
}

static void _tablettool_done(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2)
{
    printf("zwp_tablet_tool_v2 # done\n");
}

static void _tablettool_removed(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2)
{
    printf("zwp_tablet_tool_v2 # removed\n");

    zwp_tablet_tool_v2_destroy(zwp_tablet_tool_v2);
}

static void _tablettool_proximity_in(void *data,
    struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t serial,
    struct zwp_tablet_v2 *tablet, struct wl_surface *surface)
{
    printf("zwp_tablet_tool_v2 # proximity_in | serial:%u\n", serial);
}

static void _tablettool_proximity_out(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2)
{
    printf("zwp_tablet_tool_v2 # proximity_out\n");
}

static void _tablettool_down(void *data,
    struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t serial)
{
    printf("zwp_tablet_tool_v2 # down | serial:%u\n", serial);
}

static void _tablettool_up(void *data, struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2)
{
    printf("zwp_tablet_tool_v2 # up\n");
}

static void _tablettool_motion(void *data,
    struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, wl_fixed_t x, wl_fixed_t y)
{
    printf("zwp_tablet_tool_v2 # motion | (%.2f, %.2f)\n",
        x / 256.0, y / 256.0);
}

static void _tablettool_pressure(void *data,
    struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t pressure)
{
    printf("zwp_tablet_tool_v2 # pressure | %u (%.4f)\n",
        pressure, pressure / 65535.0);
}

static void _tablettool_distance(void *data,
    struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t distance)
{
    printf("zwp_tablet_tool_v2 # distance | %u (%.4f)\n",
        distance, distance / 65535.0);
}

static void _tablettool_tilt(void *data,
    struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2,
    wl_fixed_t tilt_x, wl_fixed_t tilt_y)
{
    printf("zwp_tablet_tool_v2 # tilt | x:%.2f, y:%.2f\n",
        tilt_x / 256.0, tilt_y / 256.0);
}

static void _tablettool_rotation(void *data,
    struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, wl_fixed_t degrees)
{
    printf("zwp_tablet_tool_v2 # rotation | %.2f\n", degrees / 256.0);
}

static void _tablettool_slider(void *data,
    struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, int32_t position)
{
    printf("zwp_tablet_tool_v2 # slider | %d\n", position);
}

static void _tablettool_wheel(void *data,
    struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2,
    wl_fixed_t degrees, int32_t clicks)
{
    printf("zwp_tablet_tool_v2 # wheel | degrees:%.2f, clicks:%d\n",
        degrees / 256.0, clicks);
}

static void _tablettool_button(void *data,
    struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2,
    uint32_t serial, uint32_t button, uint32_t state)
{
    printf("zwp_tablet_tool_v2 # button | serial:%u, button:0x%X, state:%u\n",
        serial, button, state);
}

static void _tablettool_frame(void *data,
    struct zwp_tablet_tool_v2 *zwp_tablet_tool_v2, uint32_t time)
{
    printf("- zwp_tablet_tool_v2 # frame | time:%u\n", time);
}


static const struct zwp_tablet_tool_v2_listener g_tablet_tool_listener = {
    _tablettool_type, _tablettool_hardware_serial, _tablettool_hardware_id_wacom,
    _tablettool_capability, _tablettool_done, _tablettool_removed,
    _tablettool_proximity_in, _tablettool_proximity_out,
    _tablettool_down, _tablettool_up, _tablettool_motion,
    _tablettool_pressure, _tablettool_distance, _tablettool_tilt,
    _tablettool_rotation, _tablettool_slider, _tablettool_wheel,
    _tablettool_button, _tablettool_frame
};


//----------------------
// zwp_tablet_v2
//----------------------


static void _tablet_name(void *data, struct zwp_tablet_v2 *zwp_tablet_v2, const char *name)
{
    printf("zwp_tablet_v2 # name | name:\"%s\"\n", name);
}

static void _tablet_id(void *data, struct zwp_tablet_v2 *zwp_tablet_v2, uint32_t vid, uint32_t pid)
{
    printf("zwp_tablet_v2 # id | vid:0x%X, pid:0x%X\n", vid, pid);
}

static void _tablet_path(void *data, struct zwp_tablet_v2 *zwp_tablet_v2, const char *path)
{
    printf("zwp_tablet_v2 # path | path:\"%s\"\n", path);
}

static void _tablet_done(void *data, struct zwp_tablet_v2 *zwp_tablet_v2)
{
    printf("zwp_tablet_v2 # done\n");
}

static void _tablet_removed(void *data, struct zwp_tablet_v2 *zwp_tablet_v2)
{
    printf("zwp_tablet_v2 # removed\n");

    zwp_tablet_v2_destroy(zwp_tablet_v2);
}

static const struct zwp_tablet_v2_listener g_tablet_listener = {
    _tablet_name, _tablet_id, _tablet_path, _tablet_done, _tablet_removed
};


//----------------------
// zwp_tablet_seat_v2
//----------------------


static void _tabletseat_tablet_added(void *data, struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2,
    struct zwp_tablet_v2 *id)
{
    printf("zwp_tablet_seat_v2 # tablet_added\n");

    zwp_tablet_v2_add_listener(id, &g_tablet_listener, data);
}

static void _tabletseat_tool_added(void *data, struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2,
    struct zwp_tablet_tool_v2 *id)
{
    printf("zwp_tablet_seat_v2 # tool_added\n");

    zwp_tablet_tool_v2_add_listener(id, &g_tablet_tool_listener, data);
}

static void _tabletseat_pad_added(void *data, struct zwp_tablet_seat_v2 *zwp_tablet_seat_v2,
    struct zwp_tablet_pad_v2 *id)
{
    printf("zwp_tablet_seat_v2 # pad_added\n");
}

static const struct zwp_tablet_seat_v2_listener g_tablet_seat_listener = {
    _tabletseat_tablet_added, _tabletseat_tool_added, _tabletseat_pad_added
};


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


/* enter */

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("wl_pointer # enter | surface:%p\n", surface);
}

static void _pointer_leave(void *data, struct wl_pointer *pointer,
    uint32_t serial, struct wl_surface *surface)
{
    printf("wl_pointer # leave | surface:%p\n", surface);
}

static void _pointer_motion(void *data, struct wl_pointer *pointer,
    uint32_t time, wl_fixed_t x, wl_fixed_t y)
{
    printf("wl_pointer # motion | (%d,%d)\n", x >> 8, y >> 8);
}

/* ボタン */

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

    if(state != WL_POINTER_BUTTON_STATE_PRESSED)
        return;

    switch(button)
    {
        //中ボタンで終了
        case BTN_MIDDLE:
            CLIENT(data)->finish_loop = 1;
            break;
    }
}

static void _pointer_axis(void *data, struct wl_pointer *pointer,
    uint32_t time, uint32_t axis, wl_fixed_t value)
{

}

static void _pointer_frame(void *data, struct wl_pointer *pointer)
{

}

static void _pointer_axis_source(void *data, struct wl_pointer *pointer, uint32_t axis_source)
{

}

static void _pointer_axis_stop(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis)
{

}

static void _pointer_axis_discrete(void *data, struct wl_pointer *pointer, uint32_t axis, int32_t discrete)
{

}

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
};




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


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

    if(strcmp(name, "zwp_tablet_manager_v2") == 0)
        p->manager = wl_registry_bind(reg, id, &zwp_tablet_manager_v2_interface, 1);
}

/* ClientEx 解放 */

static void _clientex_destroy(Client *cl)
{
    ClientEx *p = (ClientEx *)cl;

    if(p->tablet_seat)
        zwp_tablet_seat_v2_destroy(p->tablet_seat);

    if(p->manager)
        zwp_tablet_manager_v2_destroy(p->manager);
}


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


int main(void)
{
    ClientEx *p;
    Window *win;

    p = (ClientEx *)Client_new(sizeof(ClientEx));

    p->b.destroy = _clientex_destroy;
    p->b.init_flags = INIT_FLAGS_SEAT | INIT_FLAGS_POINTER;
    p->b.pointer_listener = &g_pointer_listener;
    p->b.registry_global = _registry_global;

    Client_init(CLIENT(p));

    if(!p->manager)
    {
        Client_destroy(CLIENT(p));
        printf("[!] not found 'zwp_tablet_manager_v2'\n");
        return 1;
    }

    //zwp_tablet_seat_v2

    p->tablet_seat = zwp_tablet_manager_v2_get_tablet_seat(p->manager, p->b.seat);

    zwp_tablet_seat_v2_add_listener(p->tablet_seat,
        &g_tablet_seat_listener, p);

    //メインウィンドウ

    win = Window_create(CLIENT(p), 256, 256, NULL);

    ImageBuf_fill(win->img, 0xffff0000);
    Window_update(win);

    //イベントループ

    Client_loop_simple(CLIENT(p));

    //解放

    Window_destroy(win);

    Client_destroy(CLIENT(p));

    return 0;
}

マウスの中ボタン押しで終了します。
解説
大まかな流れだけ説明します。
詳細は関数リファレンスをご覧ください。

  • zwp_tablet_manager_v2 をバインド。
  • zwp_tablet_seat_v2 を作成。
    (wl_seat が必要)
  • zwp_tablet_seat_v2 にハンドラを設定。
  • zwp_tablet_seat_v2 のハンドラ内で、各オブジェクトにハンドラを設定。
    tablet_added : 各デバイスごとの zwp_tablet_v2
    tool_added : 各ツールごとの zwp_tablet_tool_v2
    pad_added : パッドの zwp_tablet_pad_v2
  • 各オブジェクトでイベントを処理。

※ ここでは、zwp_tablet_pad_v2 の処理は省略しています。
zwp_tablet_v2
zwp_tablet_v2 は、タブレットのデバイスの情報を取得するためのものです。
イベントでデータが送られてきます。

zwp_tablet_seat_v2 # tablet_added
zwp_tablet_v2 # name | name:"Wacom One by Wacom M Pen"
zwp_tablet_v2 # path | path:"/dev/input/event4"
zwp_tablet_v2 # id | vid:0x56A, pid:0x37B
zwp_tablet_v2 # done
zwp_tablet_tool_v2
zwp_tablet_tool_v2 は、タブレットの各ツールの情報を取得するためのもです。
メインで使うのはこのオブジェクトです。

ツールは、通常のスタイラスペンや、ペンの消しゴム部分などです。

アプリケーションを起動して、デバイスを認識した段階では、すべてのツールを列挙することはできません。
実際にタブレット上でツールを使うことで、初めて認識されます。

イベントでは、ツールの初期情報や、座標・筆圧などを取得できます。
イベント
▼ カーソルがウィンドウ内に入った
wl_pointer # enter | surface:0x56071f9e96e0
zwp_tablet_seat_v2 # tool_added
zwp_tablet_tool_v2 # type | tool_type:0x140
zwp_tablet_tool_v2 # hardware_serial | 0x000000000
zwp_tablet_tool_v2 # hardware_id_wacom | 0x000000000
zwp_tablet_tool_v2 # capability | 2
zwp_tablet_tool_v2 # capability | 3
zwp_tablet_tool_v2 # done
zwp_tablet_tool_v2 # proximity_in | serial:438
- zwp_tablet_tool_v2 # frame | time:1501273
zwp_tablet_tool_v2 # motion | (174.62, 253.90)
zwp_tablet_tool_v2 # pressure | 0 (0.0000)
zwp_tablet_tool_v2 # distance | 46810 (0.7143)
- zwp_tablet_tool_v2 # frame | time:1501273

▼ ペンのボタンを押した
zwp_tablet_tool_v2 # button | serial:445, button:0x14b, state:1
- zwp_tablet_tool_v2 # frame | time:1510157

▼ ペンを接地させた
zwp_tablet_tool_v2 # down | serial:447
- zwp_tablet_tool_v2 # frame | time:1510861
zwp_tablet_tool_v2 # motion | (190.89, 157.19)
zwp_tablet_tool_v2 # pressure | 22026 (0.3361)
zwp_tablet_tool_v2 # distance | 0 (0.0000)

カーソルがサーフェス内に入った
wl_pointer の enter が来た時に、zwp_tablet_seat_v2:tool_added イベントが来ています。
最初に、ツールの情報が送られてきます。

ここでは、タイプは「ZWP_TABLET_TOOL_V2_TYPE_PEN」で、筆圧と距離の情報を持っています。

zwp_tablet_tool_v2:proximity_in で、ツールが、指定サーフェスに焦点が合っている状態となります。

ペンのボタンを押した
タブレットまたはペンに付いているボタンを押した時は、zwp_tablet_tool_v2:button イベントが来ます。

ボタンの値は、"linux/input-event-codes.h" で定義されている値です。

0x14b は「BTN_STYLUS」で、スタイラスペンに付いている一番目 (下側) のボタンとなります。

ツールを接地させた
ペンをタブレットの接地面に着けた時、zwp_tablet_tool_v2:down イベントが来ます。

グラブは自動的に行われるので、ツールを接地面から離すか、ツールがタブレットから認識できなくなるまで、イベントは継続されます。
カーソル形状について
タブレット入力では、各ツールごとにカーソル形状をセットする必要があります。

GNOME では、デスクトップ上でペンを操作すると、十字カーソルになりました。

カーソル形状の変更は、zwp_tablet_tool_v2_set_cursor() を使ってください。