ポインタデバイス入力
今回は、マウスなどのポインタデバイスの動作を取得します。
これまでのクライアント関連の処理は client.c にまとめてあるので、そちらを使います。
トップページからソースコードをダウンロードしてください。
<08-pointer.c>
これまでのクライアント関連の処理は client.c にまとめてあるので、そちらを使います。
トップページからソースコードをダウンロードしてください。
$ cc -o test 08-pointer.c client.c imagebuf.c xdg-shell-protocol.c -lwayland-client -lrt
<08-pointer.c>
#include <stdio.h> #include <string.h> #include <linux/input-event-codes.h> #include <wayland-client.h> #include "xdg-shell-client-protocol.h" #include "client.h" #include "imagebuf.h" //------------- typedef struct { client b; struct wl_seat *seat; struct wl_pointer *pointer; uint32_t seat_ver; }client_ex; //------------- /* wl_pointer 解放 */ static void _pointer_release(client_ex *p) { if(p->seat_ver >= 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", x / 256.0, y / 256.0); } /* ポインタがサーフェス外に出た時 */ static void _pointer_leave(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface) { printf("leave\n"); } /* サーフェス内でポインタが移動した時 */ 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", x / 256.0, y / 256.0, time); } /* ボタンが押された時と離された時 */ 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, bttno:0x%X, time:%u\n", (state == WL_POINTER_BUTTON_STATE_PRESSED)? "press": "release", button, time); } /* ホイールスクロール、または他の軸 */ 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", value / 256.0, time); } /* 一連のイベントの終了時 */ static void _pointer_frame(void *data, struct wl_pointer *pointer) { printf("frame\n"); } /* 軸のソース情報 (frame イベントの前) */ static void _pointer_axis_source(void *data, struct wl_pointer *pointer, uint32_t axis_source) { printf("axis_source | %u\n", axis_source); } /* 軸の通知の停止 */ 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); } /* 軸のステップ情報 */ 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); } /* axis (120) [ver 8] */ static void _pointer_axis_value120(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t value120) { printf("axis_value120 | axis:%u, value120:%d\n", axis, value120); } 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, _pointer_axis_value120 }; //==================== // wl_seat //==================== /* デバイスの利用状態が変更された時 */ static void _seat_capabilities(void *data, struct wl_seat *seat, uint32_t cap) { client_ex *p = (client_ex *)data; printf("wl_seat # capabilities | cap:0x%X\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("wl_seat # name: \"%s\"\n", name); } static const struct wl_seat_listener g_seat_listener = { _seat_capabilities, _seat_name }; //------------------------ static void _registry_global( client *client,struct wl_registry *reg,uint32_t id,const char *name,uint32_t ver) { client_ex *p = (client_ex *)client; if(strcmp(name, "wl_seat") == 0) { if(ver > 8) ver = 8; p->seat = wl_registry_bind(reg, id, &wl_seat_interface, ver); p->seat_ver = ver; wl_seat_add_listener(p->seat, &g_seat_listener, p); } } /* client_ex 解放 */ static void _clientex_destroy(client *cl) { client_ex *p = (client_ex *)cl; //wl_pointer _pointer_release(p); //wl_seat if(p->seat_ver >= WL_SEAT_RELEASE_SINCE_VERSION) wl_seat_release(p->seat); else wl_seat_destroy(p->seat); } //--------------- int main(void) { client_ex *p; Toplevel *win; p = (client_ex *)client_new(sizeof(client_ex)); p->b.destroy = _clientex_destroy; p->b.registry_global = _registry_global; client_init(CLIENT(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; }
wl_seat
基本的な入力デバイス (ポインタ、キーボード、タッチ) の操作を行うには、wl_seat が必要になります。
一つの wl_seat に対して、一つのポインタ、キーボード、タッチデバイスがグループとして含まれています。
wl_seat から、wl_pointer, wl_keyboard, wl_touch が作成できます。
wl_seat が対応している各デバイスが、「使用できるようになった」時と、「使用できなくなった」時に呼ばれます。
「使用できるようになった」時は、プログラムの開始時や、USB プラグにデバイスが接続された時などです。
「使用できなくなった」時は、プラグからデバイスが外された時などです。
引数 cap は、現在使用可能なデバイスのフラグです。
フラグが ON の場合は、そのデバイスが使用できる状態にあるため、wl_seat_get_pointer() などの関数を使って、各デバイスに応じたオブジェクトを作成できます。
フラグが OFF に変化した場合は、作成済みの各オブジェクトを破棄する必要があります。
今回はポインタデバイスにしか対応していないため、wl_pointer の処理のみ行っていますが、キーボードなど他のデバイスも使う場合は、同じように処理してください。
一つの wl_seat に対して、一つのポインタ、キーボード、タッチデバイスがグループとして含まれています。
wl_seat から、wl_pointer, wl_keyboard, wl_touch が作成できます。
wl_seat
wl_seat をバインドして、ハンドラを設定します。
2025年1月時点での wl_seat の最大バージョンは「9」ですが、GNOME では 8 までしか対応していないので、今回は ver 8 までを使います。
また、オブジェクトの破棄時にバージョンを参照するので、バインド時の wl_seat のバージョンを記録しておきます。
int wl_seat_add_listener(struct wl_seat *wl_seat, const struct wl_seat_listener *listener, void *data); struct wl_seat_listener *listener { void (*capabilities)(void *data, struct wl_seat *wl_seat, uint32_t capabilities); //ver 2 void (*name)(void *data, struct wl_seat *wl_seat, const char *name); }; //破棄 void wl_seat_destroy(struct wl_seat *wl_seat); //解放 (ver 5) void wl_seat_release(struct wl_seat *wl_seat); //wl_pointer の作成 struct wl_pointer *wl_seat_get_pointer(struct wl_seat *wl_seat);
2025年1月時点での wl_seat の最大バージョンは「9」ですが、GNOME では 8 までしか対応していないので、今回は ver 8 までを使います。
また、オブジェクトの破棄時にバージョンを参照するので、バインド時の wl_seat のバージョンを記録しておきます。
capabilities イベント
static void _pointer_release(client_ex *p) { if(p->seat_ver >= WL_POINTER_RELEASE_SINCE_VERSION) wl_pointer_release(p->pointer); else wl_pointer_destroy(p->pointer); p->pointer = NULL; } static void _seat_capabilities(void *data, struct wl_seat *seat, uint32_t cap) { client_ex *p = (client_ex *)data; printf("wl_seat # capabilities | cap:0x%X\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); } }
wl_seat が対応している各デバイスが、「使用できるようになった」時と、「使用できなくなった」時に呼ばれます。
「使用できるようになった」時は、プログラムの開始時や、USB プラグにデバイスが接続された時などです。
「使用できなくなった」時は、プラグからデバイスが外された時などです。
引数 cap は、現在使用可能なデバイスのフラグです。
WL_SEAT_CAPABILITY_POINTER = 1 WL_SEAT_CAPABILITY_KEYBOARD = 2 WL_SEAT_CAPABILITY_TOUCH = 4
フラグが ON の場合は、そのデバイスが使用できる状態にあるため、wl_seat_get_pointer() などの関数を使って、各デバイスに応じたオブジェクトを作成できます。
フラグが OFF に変化した場合は、作成済みの各オブジェクトを破棄する必要があります。
今回はポインタデバイスにしか対応していないため、wl_pointer の処理のみ行っていますが、キーボードなど他のデバイスも使う場合は、同じように処理してください。
name イベント
※wl_seat の ver 2 以降で使用できます。
この seat の名前が通知されます。
複数の seat がある場合に、それらを判別する時に使います。
この seat の名前が通知されます。
複数の seat がある場合に、それらを判別する時に使います。
wl_pointer
wl_pointer は、ポインタデバイスの管理を行います。
wl_seat の capabilities イベントで、WL_SEAT_CAPABILITY_POINTER が ON になった時に wl_pointer を作成し、ハンドラを設定します。
wl_pointer のイベントでは、クライアントプログラム上の、すべてのウィンドウにおける、ポインタイベントを取得できます。
複数のウィンドウがある場合は、それを使って、各ウィンドウごとに処理を分ける必要があります。
「1.0 = 1<<8 = 256」となるので、浮動小数点数に変換する場合は、256 で割ります。
整数部分だけ取得したい場合は、「>> 8」で、8 bit 分右にシフトします。
開始時間は未定義で、他のイベントとの時間差を判定するために使います。
ボタンが押された時、ダブルクリック操作にするかどうかを判定する時などに使います。
ポインタが、クライアントのウィンドウ内に入った時に呼ばれます。
通常、ポインタが各ウィンドウ内に入ると、その位置に応じて、カーソル形状が変わりますが、Wayland サーバーはカーソル形状を一切変更せず、現在のカーソル形状を維持します。
そのため、クライアントは、カーソル形状の変更を、自身で判断して行う必要があります。
wl_pointer_set_cursor() を使うと、wl_surface のイメージを、カーソル形状としてセットできます。
wl_surface はウィンドウでも使いますが、画面上にイメージを表示するという意味では、カーソル形状も同じになります。
x, y は、指定サーフェス座標におけるポインタの位置です。
ポインタがウィンドウ外に出た時に呼ばれます。
ウィンドウ内でポインタが移動した時に呼ばれます。
ウィンドウ内で、ボタンが押されたか、離された時に呼ばれます。
※ポインタ位置は渡されないので、最後に来た enter または motion イベントの値を使ってください。
button はボタン番号です。
Linux の evdev のボタン番号と同じなので、「linux/input-event-codes.h」で定義されている値を使います。
ポインタのボタンは、BTN_* の名前になります。
state は、enum wl_pointer_button_state の値です。
押されたか離されたかの状態を判別できます。
ボタンが押された時、サーバーは自動的にグラブを行い、ボタンが押されている間は、すべてのポインタイベントを、そのウィンドウに対して送ります。
ボタンが押されている間に、ポインタがウィンドウ外に出たとしても、ボタンが押された時のウィンドウに対して、イベントが送られ続けます。
leave イベントは、ボタンが離された後に送られてきます。
マウスホイールなどで、スクロール操作が行われた時に呼ばれます。
axis は、軸の方向 (enum wl_pointer_axis)。
value は、移動した方向と量。増減幅はサーバーによって異なります。
斜め方向に対応したデバイスでは、VERT, HORZ というように、複数の axis イベントが出力されます。
例えば、スクロールで斜め方向に移動した場合、複数の axis イベントが出力されますが、それらのイベントをグループとして、一連のイベントが終了した時に呼ばれます。
「axis (vert) → axis (horz) → frame」といった順で呼ばれます。
また、axis に限らず、motion や button など、全てのイベントが対象となります。
▼ 例
frame イベントの前にオプションとして出力され、イベントが物理的にどうやって生成されたかを送信します。
ソースが不明な場合は、送信されません。
axis_source は、enum wl_pointer_axis_source の値です。
CONTINUOUS は、ボタンを押している間スクロールするといった動作の場合です。
WL_POINTER_AXIS_SOURCE_FINGER の場合は、ユーザーが指を離した時に axis_stop イベントが送信されます。
axis 引数で指定された軸の停止が通知されます (タッチ操作の場合)。
このイベント後に、同じ axis_source を持つ axis イベントが来た場合、新しい軸の開始と見なす必要があります。
axis の軸のステップ情報。
1段階上下するようなホイール操作などで、オプションとして送信されます。
単独では発生せず、axis イベントと一緒に送信されます。
discrete はステップ値。
マウスホイールの場合は、通常 ±1 の値となります。
1 なら正方向に1段階ステップ、-1 なら負の方向に1段階ステップするという意味になります。
ホイールスクロールの高解像度の情報。
このイベントは、wl_pointer の ver 8 以降をサポートするクライアントにおいて、axis_discrete イベントの代わりになります。
axis_discrete イベントでは、ステップ値は基本的に ±1 の値なので、それより細かい値を取得したい時に使います。
同じ frame 内において、axis_source イベントがある場合、その axis_source が、このイベントに適用されます。
value120 は、120 を1単位とした値です。
例えば 30 なら、120 の 1/4 の値なので、正方向の1ステップの 1/4 量のスクロールということになります。
負の値の場合も同様です。
▼例
wl_seat の capabilities イベントで、WL_SEAT_CAPABILITY_POINTER が ON になった時に wl_pointer を作成し、ハンドラを設定します。
wl_pointer のイベントでは、クライアントプログラム上の、すべてのウィンドウにおける、ポインタイベントを取得できます。
struct wl_pointer_listener { void (*enter)(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t surface_x, wl_fixed_t surface_y); void (*leave)(void *data, struct wl_pointer *wl_pointer, uint32_t serial, struct wl_surface *surface); void (*motion)(void *data, struct wl_pointer *wl_pointer, uint32_t time, wl_fixed_t surface_x, wl_fixed_t surface_y); void (*button)(void *data, struct wl_pointer *wl_pointer, uint32_t serial, uint32_t time, uint32_t button, uint32_t state); void (*axis)(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis, wl_fixed_t value); //ver 5 void (*frame)(void *data, struct wl_pointer *wl_pointer); void (*axis_source)(void *data, struct wl_pointer *wl_pointer, uint32_t axis_source); void (*axis_stop)(void *data, struct wl_pointer *wl_pointer, uint32_t time, uint32_t axis); void (*axis_discrete)(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t discrete); //ver 8 void (*axis_value120)(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t value120); //ver 9 void (*axis_relative_direction)(void *data, struct wl_pointer *wl_pointer, uint32_t axis, uint32_t direction); };
引数の値
struct wl_surface *surface
イベントがどのウィンドウ上で操作されたかは、surface 引数で判別できます。複数のウィンドウがある場合は、それを使って、各ウィンドウごとに処理を分ける必要があります。
wl_fixed_t
wl_fixed_t 型は、int32_t 型として定義されており、24:8 の固定少数点数となっています。「1.0 = 1<<8 = 256」となるので、浮動小数点数に変換する場合は、256 で割ります。
整数部分だけ取得したい場合は、「>> 8」で、8 bit 分右にシフトします。
time
ミリ秒単位のタイムスタンプ。開始時間は未定義で、他のイベントとの時間差を判定するために使います。
ボタンが押された時、ダブルクリック操作にするかどうかを判定する時などに使います。
enter イベント
void (*enter)(void *data, struct wl_pointer *pointer, uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y);
ポインタが、クライアントのウィンドウ内に入った時に呼ばれます。
通常、ポインタが各ウィンドウ内に入ると、その位置に応じて、カーソル形状が変わりますが、Wayland サーバーはカーソル形状を一切変更せず、現在のカーソル形状を維持します。
そのため、クライアントは、カーソル形状の変更を、自身で判断して行う必要があります。
wl_pointer_set_cursor() を使うと、wl_surface のイメージを、カーソル形状としてセットできます。
wl_surface はウィンドウでも使いますが、画面上にイメージを表示するという意味では、カーソル形状も同じになります。
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);
ウィンドウ内で、ボタンが押されたか、離された時に呼ばれます。
※ポインタ位置は渡されないので、最後に来た enter または motion イベントの値を使ってください。
button はボタン番号です。
Linux の evdev のボタン番号と同じなので、「linux/input-event-codes.h」で定義されている値を使います。
ポインタのボタンは、BTN_* の名前になります。
state は、enum wl_pointer_button_state の値です。
押されたか離されたかの状態を判別できます。
WL_POINTER_BUTTON_STATE_RELEASED = 0 WL_POINTER_BUTTON_STATE_PRESSED = 1
グラブについて
Wayland では、ポインタのグラブ (キャプチャ) を明示的に行うことができません。ボタンが押された時、サーバーは自動的にグラブを行い、ボタンが押されている間は、すべてのポインタイベントを、そのウィンドウに対して送ります。
ボタンが押されている間に、ポインタがウィンドウ外に出たとしても、ボタンが押された時のウィンドウに対して、イベントが送られ続けます。
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)。
value は、移動した方向と量。増減幅はサーバーによって異なります。
//enum wl_pointer_axis WL_POINTER_AXIS_VERTICAL_SCROLL = 0 WL_POINTER_AXIS_HORIZONTAL_SCROLL = 1
斜め方向に対応したデバイスでは、VERT, HORZ というように、複数の axis イベントが出力されます。
frame イベント (ver 5)
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:3117731904 frame motion | (208.00, 114.00), time:3117730992 frame leave frame
axis_source イベント (ver 5)
void (*axis_source)(void *data, struct wl_pointer *pointer, uint32_t axis_source);
frame イベントの前にオプションとして出力され、イベントが物理的にどうやって生成されたかを送信します。
ソースが不明な場合は、送信されません。
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 //連続 # ver 6 WL_POINTER_AXIS_SOURCE_WHEEL_TILT = 3 //ホイールの傾き (横)
CONTINUOUS は、ボタンを押している間スクロールするといった動作の場合です。
WL_POINTER_AXIS_SOURCE_FINGER の場合は、ユーザーが指を離した時に axis_stop イベントが送信されます。
axis_stop (ver 5)
void (*axis_stop)(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis);
axis 引数で指定された軸の停止が通知されます (タッチ操作の場合)。
このイベント後に、同じ axis_source を持つ axis イベントが来た場合、新しい軸の開始と見なす必要があります。
axis_discrete
void (*axis_discrete)(void *data, struct wl_pointer *pointer, uint32_t axis, int32_t discrete);
axis の軸のステップ情報。
1段階上下するようなホイール操作などで、オプションとして送信されます。
単独では発生せず、axis イベントと一緒に送信されます。
discrete はステップ値。
マウスホイールの場合は、通常 ±1 の値となります。
1 なら正方向に1段階ステップ、-1 なら負の方向に1段階ステップするという意味になります。
axis_value120 (ver 8)
void (*axis_value120)(void *data, struct wl_pointer *wl_pointer, uint32_t axis, int32_t value120);
ホイールスクロールの高解像度の情報。
このイベントは、wl_pointer の ver 8 以降をサポートするクライアントにおいて、axis_discrete イベントの代わりになります。
axis_discrete イベントでは、ステップ値は基本的に ±1 の値なので、それより細かい値を取得したい時に使います。
同じ frame 内において、axis_source イベントがある場合、その axis_source が、このイベントに適用されます。
value120 は、120 を1単位とした値です。
例えば 30 なら、120 の 1/4 の値なので、正方向の1ステップの 1/4 量のスクロールということになります。
負の値の場合も同様です。
▼例
# ホイール上 axis_source | 0 axis_value120 | axis:0, value120:-120 axis | vert, value:-10.00, time:4019744 frame frame # ホイール下 axis_source | 0 axis_value120 | axis:0, value120:120 axis | vert, value:10.00, time:4021229 frame frame