ペンタブレット
ペンタブレットからの入力を行うため、拡張プロトコルの zwp_tablet_manager_v2 (stable) を使用します。
wayland-scanner で、stable/tablet/tablet-v2.xml ファイルから、「tablet-v2-client-protocol.h」「tablet-v2-protocol.c」を生成してください。
マウスの中ボタン押しで終了します。
<17-tablet.c>
wayland-scanner で、stable/tablet/tablet-v2.xml ファイルから、「tablet-v2-client-protocol.h」「tablet-v2-protocol.c」を生成してください。
$ wayland-scanner client-header tablet-v2.xml tablet-v2-client-protocol.h $ wayland-scanner private-code tablet-v2.xml tablet-v2-protocol.c
$ cc -o test 17-tablet.c client.c imagebuf.c \ xdg-shell-protocol.c tablet-v2-protocol.c -lwayland-client -lrt
マウスの中ボタン押しで終了します。
<17-tablet.c>
#include <stdio.h> #include <string.h> #include <linux/input-event-codes.h> #include <wayland-client.h> #include "xdg-shell-client-protocol.h" #include "tablet-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; }client_ex; //------------------ //===================== // 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\n"); } static void _pointer_leave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) { printf("wl_pointer # leave\n"); } 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\n", (state == WL_POINTER_BUTTON_STATE_PRESSED)? "press":"release", button); 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) { } //ver 5 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( client *cl,struct wl_registry *reg,uint32_t id,const char *name,uint32_t ver) { client_ex *p = (client_ex *)cl; if(strcmp(name, "zwp_tablet_manager_v2") == 0) p->manager = wl_registry_bind(reg, id, &zwp_tablet_manager_v2_interface, 1); } /* client_ex 解放 */ static void _clientex_destroy(client *cl) { client_ex *p = (client_ex *)cl; if(p->tablet_seat) zwp_tablet_seat_v2_destroy(p->tablet_seat); if(p->manager) zwp_tablet_manager_v2_destroy(p->manager); } /* main */ int main(void) { client_ex *p; Toplevel *win; p = (client_ex *)client_new(sizeof(client_ex)); p->b.destroy = _clientex_destroy; p->b.init_flags = CLIENT_INIT_FLAGS_SEAT | CLIENT_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 = toplevel_create(CLIENT(p), 256, 256, NULL); imagebuf_fill(win->sf.img, 0xffff0000); //イベントループ client_loop_simple(CLIENT(p)); //解放 toplevel_destroy(win); client_destroy(CLIENT(p)); return 0; }
解説
大まかな流れだけ説明します。
詳細は zwp_tablet_manager_v2 をご覧ください。
※今回は、zwp_tablet_pad_v2 の処理は省略しています。
zwp_tablet_pad_v2 では、タブレットデバイス上に物理的に存在している、ボタン・リングなどの操作を行いますが、そういったものが付いていないデバイスでは使用しません。
詳細は zwp_tablet_manager_v2 をご覧ください。
- zwp_tablet_manager_v2 をバインド。
2025年2月時点での最大バージョンは 1。 - 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_pad_v2 では、タブレットデバイス上に物理的に存在している、ボタン・リングなどの操作を行いますが、そういったものが付いていないデバイスでは使用しません。
インターフェイス名について
バインド時のインターフェイス名は zwp_tablet_manager_v2 になっていますが、実際のバインド時に指定するバージョンは 1 です (2025年2月現在)。
これは、stable への移行時に、unstable の ver 2 の名前がそのまま引き継がれたためです。
サーバーとクライアントは、stable 版に対応するためにコードを書き換える必要がないのが、メリットになっていますが、名前は少々ややこしいことになっています。
これは、stable への移行時に、unstable の ver 2 の名前がそのまま引き継がれたためです。
サーバーとクライアントは、stable 版に対応するためにコードを書き換える必要がないのが、メリットになっていますが、名前は少々ややこしいことになっています。
zwp_tablet_v2
zwp_tablet_v2 は、タブレットのデバイスの情報を取得するためのインターフェイスです。
各イベントで、データが送られてきます。
各イベントで、データが送られてきます。
zwp_tablet_seat_v2 # tablet_added zwp_tablet_v2 # name | name:"Wacom Graphire3 Pen" zwp_tablet_v2 # path | path:"/dev/input/event3" zwp_tablet_v2 # id | vid:0x56A, pid:0x13 zwp_tablet_v2 # done
zwp_tablet_tool_v2
zwp_tablet_tool_v2 は、タブレットの各ツールの情報を取得するためのインターフェイスです。
座標などを取得できます。
ツールは、スタイラスペン (通常のペン先の部分) や、ペンの消しゴム部分など、使用用途によって分かれています。
ただし、アプリケーションを起動して、デバイスを認識した段階では、すべてのツールを列挙することはできません。
実際にタブレット上で各ツールを操作した時に、初めて認識されます。
イベントでは、ツールの初期情報や、座標・筆圧などを取得できます。
ペンに付いているボタンを押した時は、zwp_tablet_tool_v2:button イベントが来ます。
※2025年3月現在、GNOME では、デスクトップ設定でタブレットのボタンの割り当てを行わないと、すべて down, up イベントでの扱いになります。
button の値は、"linux/input-event-codes.h" で定義されている値です。
0x14b は「BTN_STYLUS」、0x14c は「BTN_STYLUS2」です。
(タブレットペンに付いている最初のボタンと、2番目のボタンという意味)
ペンをタブレットの接地面に着けた時、zwp_tablet_tool_v2:down イベントが来ます。
グラブはサーバーの方で自動的に行われるので、ペンを着けている状態でウィンドウ外に出たとしても、ツールを接地面から離すか、ツールがタブレットから認識できなくなるまではイベントが来ます。
座標などを取得できます。
ツールは、スタイラスペン (通常のペン先の部分) や、ペンの消しゴム部分など、使用用途によって分かれています。
ただし、アプリケーションを起動して、デバイスを認識した段階では、すべてのツールを列挙することはできません。
実際にタブレット上で各ツールを操作した時に、初めて認識されます。
イベントでは、ツールの初期情報や、座標・筆圧などを取得できます。
ペンを操作してウィンドウ内に入った
ペンを動かして、カーソルがクライアントのウィンドウ内に入った時のイベントは、以下のようになります。
プログラムの起動後、初めてペンがクライアントのウィンドウ内に入った時に、zwp_tablet_seat_v2:tool_added イベントが来ます。
※同じツールを使っている状態で、一度ウィンドウから離れて再び入った時は、このイベントは来ませんが、消しゴムペンなど、別のツールに切り替わった時は再び来ます。
ここで、イベントで渡された zwp_tablet_tool_v2 にハンドラを設定すると、その直後に zwp_tablet_tool_v2 の各イベントが発生し、現在操作されているツールの情報が来ます。
上記の場合、ツールのタイプは「ZWP_TABLET_TOOL_V2_TYPE_PEN (通常のペン)」で、機能は ZWP_TABLET_TOOL_V2_CAPABILITY_PRESSURE (2) のみがあり、「筆圧」の情報を持っています。
GNOME の場合、タブレットペンを操作した時は、wl_pointer の enter イベントは来ないので、このイベントを enter の代わりとして扱う必要があります。
また、このプロトコルを使って処理をしない場合、代わりに wl_pointer でイベントが送られてくるということはないので、カーソルがウィンドウに入ると非表示になってしまったり、座標やボタンが全く取得できないことになるので、注意してください。
GNOME では、ペンがタブレット面から離れると、カーソルが非表示になります。
(2025年3月現在)
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 | 0x000000002 zwp_tablet_tool_v2 # capability | 2 zwp_tablet_tool_v2 # done zwp_tablet_tool_v2 # proximity_in | serial:705 - zwp_tablet_tool_v2 # frame | time:0 zwp_tablet_tool_v2 # motion | (179.66, 0.73) zwp_tablet_tool_v2 # pressure | 0 (0.0000) - zwp_tablet_tool_v2 # frame | time:4810124
プログラムの起動後、初めてペンがクライアントのウィンドウ内に入った時に、zwp_tablet_seat_v2:tool_added イベントが来ます。
※同じツールを使っている状態で、一度ウィンドウから離れて再び入った時は、このイベントは来ませんが、消しゴムペンなど、別のツールに切り替わった時は再び来ます。
ここで、イベントで渡された zwp_tablet_tool_v2 にハンドラを設定すると、その直後に zwp_tablet_tool_v2 の各イベントが発生し、現在操作されているツールの情報が来ます。
上記の場合、ツールのタイプは「ZWP_TABLET_TOOL_V2_TYPE_PEN (通常のペン)」で、機能は ZWP_TABLET_TOOL_V2_CAPABILITY_PRESSURE (2) のみがあり、「筆圧」の情報を持っています。
proximity_in イベント
zwp_tablet_tool_v2:proximity_in イベントは、ツールが特定のサーフェスに焦点を合わせている状態の時、つまり、カーソルがサーフェス内にあって、ペンとタブレット面との距離が、動きを捕捉できる距離にある時に来ます。GNOME の場合、タブレットペンを操作した時は、wl_pointer の enter イベントは来ないので、このイベントを enter の代わりとして扱う必要があります。
また、このプロトコルを使って処理をしない場合、代わりに wl_pointer でイベントが送られてくるということはないので、カーソルがウィンドウに入ると非表示になってしまったり、座標やボタンが全く取得できないことになるので、注意してください。
GNOME では、ペンがタブレット面から離れると、カーソルが非表示になります。
(2025年3月現在)
ペンのボタンを押した
zwp_tablet_tool_v2 # button | serial:891, button:0x14B, state:1 - zwp_tablet_tool_v2 # frame | time:4931133
ペンに付いているボタンを押した時は、zwp_tablet_tool_v2:button イベントが来ます。
※2025年3月現在、GNOME では、デスクトップ設定でタブレットのボタンの割り当てを行わないと、すべて down, up イベントでの扱いになります。
button の値は、"linux/input-event-codes.h" で定義されている値です。
0x14b は「BTN_STYLUS」、0x14c は「BTN_STYLUS2」です。
(タブレットペンに付いている最初のボタンと、2番目のボタンという意味)
GNOME では、ペンのボタンを中ボタンに割り当てると BTN_STYLE に、右ボタンに割り当てると BTN_STYLE2 になります。
右ボタンと言いつつ、Wayland プログラム内では BTN_RIGHT にならないので、注意してください。
X11 では、デフォルトで、ペンの下のボタンが中ボタン、上のボタンが右ボタンとなるので、X11 プログラムとの互換性を考えると、BTN_STYLE を中ボタン、BTN_STYLE2 を右ボタンとして扱うといいでしょう。
右ボタンと言いつつ、Wayland プログラム内では BTN_RIGHT にならないので、注意してください。
X11 では、デフォルトで、ペンの下のボタンが中ボタン、上のボタンが右ボタンとなるので、X11 プログラムとの互換性を考えると、BTN_STYLE を中ボタン、BTN_STYLE2 を右ボタンとして扱うといいでしょう。
ツールを接地させた
zwp_tablet_tool_v2 # down | serial:786 - zwp_tablet_tool_v2 # frame | time:4849516 zwp_tablet_tool_v2 # motion | (45.00, 33.45) zwp_tablet_tool_v2 # pressure | 338 (0.0052) - zwp_tablet_tool_v2 # frame | time:4849524 zwp_tablet_tool_v2 # up - zwp_tablet_tool_v2 # frame | time:4849796
ペンをタブレットの接地面に着けた時、zwp_tablet_tool_v2:down イベントが来ます。
グラブはサーバーの方で自動的に行われるので、ペンを着けている状態でウィンドウ外に出たとしても、ツールを接地面から離すか、ツールがタブレットから認識できなくなるまではイベントが来ます。
カーソル形状について
タブレットツールを操作している時のカーソル形状は、各ツールごとに変更する必要があります。
カーソル形状の変更は、zwp_tablet_tool_v2_set_cursor() を使ってください。
serial 引数には、proximity_in イベント時の値を指定しないと、正しくカーソルを変更できない場合があります。
(GNOME の場合) wl_pointer でセットされているカーソル形状は適用されないので、proximity_in イベントが来た時は、常にカーソルを変更する必要があります。
※ wl_pointer のカーソルとして使用している wl_surface は使わず、別の wl_surface を使うこと。
カーソル形状の変更は、zwp_tablet_tool_v2_set_cursor() を使ってください。
serial 引数には、proximity_in イベント時の値を指定しないと、正しくカーソルを変更できない場合があります。
(GNOME の場合) wl_pointer でセットされているカーソル形状は適用されないので、proximity_in イベントが来た時は、常にカーソルを変更する必要があります。
※ wl_pointer のカーソルとして使用している wl_surface は使わず、別の wl_surface を使うこと。