Wayland の構造
クライアント用のヘッダファイル
Wayland クライアントのプログラムでは、「wayland-client.h」のヘッダファイルを使います。
このヘッダファイル内では、以下の2つのヘッダファイルが、内部でインクルードされています。
まずは、この2つをテキストエディタで開いてみてください。
クライアントプログラムを作る場合は、上記の2つのヘッダを使用しますが、通常の C 関数は 40 個くらいしか定義されておらず、他はほとんどインライン関数として定義されています。
このヘッダファイル内では、以下の2つのヘッダファイルが、内部でインクルードされています。
wayland-client-core.h wayland-client-protocol.h
まずは、この2つをテキストエディタで開いてみてください。
wayland-client-core.h | 関数の定義 |
---|---|
wayland-client-protocol.h | プロトコルのクライアント用定義。 インライン関数・列挙型・マクロなどが定義されている。 |
クライアントプログラムを作る場合は、上記の2つのヘッダを使用しますが、通常の C 関数は 40 個くらいしか定義されておらず、他はほとんどインライン関数として定義されています。
クライアントとサーバーのやりとり
Wayland クライアントが Wayland サーバーとやりとりする場合、
「wayland-client-core.h」で定義されている「wl_proxy_*()」関数を使います。
ただし、クライアント側は直接 wl_proxy_*() 関数を使用せず、インライン関数内で間接的に使用する場合がほとんどです。
例えば、「wl_shell」は、基本的なウィンドウの処理を行います。
各インターフェイスでは、関数に相当する各処理ごとに、整数値の ID が定義されています。
例えば、wl_shell_surface で、ウィンドウのタイトルをセットする wl_shell_surface_set_title() の定義を見てみると、
このように、インライン関数で定義されています。
実際に実行される関数は wl_proxy_marshal() で、
その引数で「オブジェクトのポインタ」「処理の整数値 ID」「関連データ (ここではタイトル文字列)」が渡されています。
Wayland では、クライアントとサーバー間でやり取りを行う場合、このような形でデータを送ったり、処理を要求したりします。
「wayland-client-core.h」で定義されている「wl_proxy_*()」関数を使います。
ただし、クライアント側は直接 wl_proxy_*() 関数を使用せず、インライン関数内で間接的に使用する場合がほとんどです。
例
Wayland では、各機能ごとに、インターフェイスが分けられています。例えば、「wl_shell」は、基本的なウィンドウの処理を行います。
各インターフェイスでは、関数に相当する各処理ごとに、整数値の ID が定義されています。
例えば、wl_shell_surface で、ウィンドウのタイトルをセットする wl_shell_surface_set_title() の定義を見てみると、
#define WL_SHELL_SURFACE_SET_TITLE 8 static inline void wl_shell_surface_set_title(struct wl_shell_surface *wl_shell_surface, const char *title) { wl_proxy_marshal((struct wl_proxy *) wl_shell_surface, WL_SHELL_SURFACE_SET_TITLE, title); }
このように、インライン関数で定義されています。
実際に実行される関数は wl_proxy_marshal() で、
その引数で「オブジェクトのポインタ」「処理の整数値 ID」「関連データ (ここではタイトル文字列)」が渡されています。
Wayland では、クライアントとサーバー間でやり取りを行う場合、このような形でデータを送ったり、処理を要求したりします。
インターフェイス
Wayland では、ウィンドウ・イメージ・ポインタやキーボードのデバイスなど、各機能ごとに、それぞれインターフェイスが分けられています。
それらが Wayland サーバー側で実装されている場合は、対応する各オブジェクトが、サーバー側で作成されています。
クライアントがそれらの機能を使用する場合は、まず「バインド」して、サーバーから、各オブジェクトのポインタを取得する必要があります。
その後は、取得したポインタを使って、操作をしていきます。
感覚的にはプラグインと似たようなもので、クライアントは、サーバーが実装している機能から、必要なものを選択して使うことができます。
ただし、サーバー側で、使いたい機能が実際に実装されているとは限りません。
Wayland 側は機能の実装方法を定義するだけで、それを実際に実装するのは、サーバー (デスクトップ) の役目です。
クライアント側が機能を使う方法を知っていても、デスクトップの方で実際に実装されていなければ、その機能を使うことは出来ません。
なお、Wayland のこのような構造を利用することで、各デスクトップやツールキットが、独自の機能を定義・実装して、それをクライアントが使うといったことも可能になります。
それらが Wayland サーバー側で実装されている場合は、対応する各オブジェクトが、サーバー側で作成されています。
クライアントがそれらの機能を使用する場合は、まず「バインド」して、サーバーから、各オブジェクトのポインタを取得する必要があります。
その後は、取得したポインタを使って、操作をしていきます。
感覚的にはプラグインと似たようなもので、クライアントは、サーバーが実装している機能から、必要なものを選択して使うことができます。
ただし、サーバー側で、使いたい機能が実際に実装されているとは限りません。
Wayland 側は機能の実装方法を定義するだけで、それを実際に実装するのは、サーバー (デスクトップ) の役目です。
クライアント側が機能を使う方法を知っていても、デスクトップの方で実際に実装されていなければ、その機能を使うことは出来ません。
なお、Wayland のこのような構造を利用することで、各デスクトップやツールキットが、独自の機能を定義・実装して、それをクライアントが使うといったことも可能になります。
プログラム
サーバーで使用できるインターフェイスの一覧表示と、バインドの例。
<02-registry.c>
サーバー上で現在利用可能なグローバルオブジェクトのリストを出力します。
また、実際のバインドの例として、wl_compositor をバインドしています。
$ cc -o test 02-registry.c -lwayland-client
<02-registry.c>
#include <stdio.h> #include <string.h> #include <wayland-client.h> struct wl_compositor *g_compositor = NULL; /* 利用可能になった時 */ static void _registry_global( void *data,struct wl_registry *registry, uint32_t id,const char *interface,uint32_t version) { printf("%s | id:%u | ver:%u\n", interface, id, version); //wl_compositor が使えるようにバインドする if(strcmp(interface, "wl_compositor") == 0) g_compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 1); } /* 取り外された時 */ static void _registry_global_remove(void *data,struct wl_registry *registry,uint32_t id) { } //wl_registry のハンドラの構造体 static const struct wl_registry_listener g_reg_listener = { _registry_global, _registry_global_remove }; /* main */ int main(void) { struct wl_display *display; struct wl_registry *reg; //接続 display = wl_display_connect(NULL); if(!display) return 1; //wl_registry 取得 reg = wl_display_get_registry(display); //ハンドラセット wl_registry_add_listener(reg, &g_reg_listener, NULL); //サーバーで処理されるまで待つ wl_display_roundtrip(display); //終了 wl_compositor_destroy(g_compositor); wl_registry_destroy(reg); wl_display_disconnect(display); return 0; }
サーバー上で現在利用可能なグローバルオブジェクトのリストを出力します。
また、実際のバインドの例として、wl_compositor をバインドしています。
実行結果
2025年1月時点での実行結果は、以下の通りです。
gtk_* は、GTK+ で使われる機能です。
GNOME
wl_compositor | id:1 | ver:6 wl_drm | id:2 | ver:2 wl_shm | id:3 | ver:2 wl_output | id:4 | ver:4 zxdg_output_manager_v1 | id:5 | ver:3 wl_data_device_manager | id:6 | ver:3 zwp_primary_selection_device_manager_v1 | id:7 | ver:1 wl_subcompositor | id:8 | ver:1 xdg_wm_base | id:9 | ver:6 gtk_shell1 | id:10 | ver:5 wp_viewporter | id:11 | ver:1 wp_fractional_scale_manager_v1 | id:12 | ver:1 zwp_pointer_gestures_v1 | id:13 | ver:3 zwp_tablet_manager_v2 | id:14 | ver:1 wl_seat | id:15 | ver:8 zwp_relative_pointer_manager_v1 | id:16 | ver:1 zwp_pointer_constraints_v1 | id:17 | ver:1 zxdg_exporter_v2 | id:18 | ver:1 zxdg_importer_v2 | id:19 | ver:1 zxdg_exporter_v1 | id:20 | ver:1 zxdg_importer_v1 | id:21 | ver:1 zwp_linux_dmabuf_v1 | id:22 | ver:5 wp_single_pixel_buffer_manager_v1 | id:23 | ver:1 zwp_keyboard_shortcuts_inhibit_manager_v1 | id:24 | ver:1 zwp_text_input_manager_v3 | id:25 | ver:1 wp_presentation | id:26 | ver:1 xdg_activation_v1 | id:27 | ver:1 zwp_idle_inhibit_manager_v1 | id:28 | ver:1 wp_linux_drm_syncobj_manager_v1 | id:29 | ver:1 xdg_wm_dialog_v1 | id:30 | ver:1 wp_drm_lease_device_v1 | id:31 | ver:1
gtk_* は、GTK+ で使われる機能です。
解説
wl_registry の取得
サーバーと直接バインドして使うオブジェクトを、「グローバルオブジェクト」と呼びます。
サーバーで利用可能なグローバルオブジェクトの取得とバインドを行うためには、
まず、wl_display_get_registry() 関数で wl_registry を取得します。
この関数は、以下のように、インライン関数として定義されています。
wl_display など、クライアントと通信する機能を持つオブジェクトの多くは、内部的に、wl_proxy がベースとなった構造になっています。
クライアントがサーバーに何かを要求する場合は、wl_proxy_marshal*() 関数を使います。
サーバーで利用可能なグローバルオブジェクトの取得とバインドを行うためには、
まず、wl_display_get_registry() 関数で wl_registry を取得します。
この関数は、以下のように、インライン関数として定義されています。
static inline struct wl_registry * wl_display_get_registry(struct wl_display *wl_display) { struct wl_proxy *registry; registry = wl_proxy_marshal_constructor((struct wl_proxy *)wl_display, WL_DISPLAY_GET_REGISTRY, &wl_registry_interface, NULL); return (struct wl_registry *)registry; }
wl_proxy について
このインライン関数内で使われている wl_proxy は、クライアントがサーバーと通信するためのオブジェクトです。wl_display など、クライアントと通信する機能を持つオブジェクトの多くは、内部的に、wl_proxy がベースとなった構造になっています。
クライアントがサーバーに何かを要求する場合は、wl_proxy_marshal*() 関数を使います。
イベントハンドラのセット
次に、wl_registry に関連して起こるイベントを処理できるようにするため、wl_registry_add_listener() 関数で、ハンドラをセットします。
まず、グローバル変数で、struct wl_registry_listener の構造体データを用意し、各メンバに、ハンドラ関数のポインタをセットします。
そして、その変数のポインタを wl_registry_add_listener() 関数に渡します。
第3引数の void *data は、ハンドラ関数に渡されるユーザー定義値です。
各オブジェクトでハンドラを設定する際は、それぞれのオブジェクトごとに、専用の構造体「struct <name>_listener」が定義されているので、その構造体をグローバル変数で定義し、関数のポインタをセットして、*_add_listener() 関数に渡します。
wl_proxy_add_listener() 関数の内部では、構造体のポインタである implementation の値を、そのままオブジェクトの内部メンバに代入しているだけなので、渡した構造体変数のポインタは、常に参照可能な状態でなければなりません。
つまり、構造体をローカル変数として定義して渡してはいけないということです。
今回のソースコードの場合は、main() 関数内に、ローカル変数として定義したとしても、プログラムが終了するまではデータが維持されるため、問題はありません。
しかし、サブ関数内で、ローカル変数として定義し、*_add_listener() 関数に渡した場合は、サブ関数が終了した時点で構造体のデータが参照できなくなるので、正しく動作しません。
/* wl_registry_add_listener() */ int wl_proxy_add_listener(struct wl_proxy *proxy, void(**implementation)(void), void *data); static inline int wl_registry_add_listener(struct wl_registry *wl_registry, const struct wl_registry_listener *listener, void *data) { return wl_proxy_add_listener((struct wl_proxy *)wl_registry, (void (**)(void)) listener, data); } /* wl_registry_listener */ struct wl_registry_listener { //利用可能になった時 void (*global)(void *data,struct wl_registry *wl_registry, uint32_t name,const char *interface,uint32_t version); //利用できない状態になった時 void (*global_remove)(void *data,struct wl_registry *wl_registry,uint32_t name); };
まず、グローバル変数で、struct wl_registry_listener の構造体データを用意し、各メンバに、ハンドラ関数のポインタをセットします。
そして、その変数のポインタを wl_registry_add_listener() 関数に渡します。
第3引数の void *data は、ハンドラ関数に渡されるユーザー定義値です。
ハンドラのセット
Wayland では、基本的に、オブジェクトにハンドラ関数 (リスナー) をセットすることで、サーバーからのイベントに対応することができます。各オブジェクトでハンドラを設定する際は、それぞれのオブジェクトごとに、専用の構造体「struct <name>_listener」が定義されているので、その構造体をグローバル変数で定義し、関数のポインタをセットして、*_add_listener() 関数に渡します。
ローカル変数にしないこと
ここで、一つ注意が必要です。wl_proxy_add_listener() 関数の内部では、構造体のポインタである implementation の値を、そのままオブジェクトの内部メンバに代入しているだけなので、渡した構造体変数のポインタは、常に参照可能な状態でなければなりません。
つまり、構造体をローカル変数として定義して渡してはいけないということです。
今回のソースコードの場合は、main() 関数内に、ローカル変数として定義したとしても、プログラムが終了するまではデータが維持されるため、問題はありません。
しかし、サブ関数内で、ローカル変数として定義し、*_add_listener() 関数に渡した場合は、サブ関数が終了した時点で構造体のデータが参照できなくなるので、正しく動作しません。
イベントが処理されるのを待つ
wl_registry 関連の設定が終わったら、wl_display_roundtrip() 関数を呼び出して、クライアントが要求したリクエストや、発生したイベントが、サーバーによってすべて処理されるまで待ちます。
これをしないと、何も起こらずに (今回の場合は何も表示されずに) 終了してしまいます。
今回の場合は、wl_registry が作成された時点で、サーバーによって、現在利用可能な各グローバルオブジェクトの global イベントが生成されるので、そのすべてのイベントが、クライアント側のハンドラ関数で処理されるまで待つことになります。
これをしないと、何も起こらずに (今回の場合は何も表示されずに) 終了してしまいます。
int wl_display_roundtrip(struct wl_display *display); 戻り値: ディスパッチされたイベントの数。-1 で失敗。
今回の場合は、wl_registry が作成された時点で、サーバーによって、現在利用可能な各グローバルオブジェクトの global イベントが生成されるので、そのすべてのイベントが、クライアント側のハンドラ関数で処理されるまで待つことになります。
wl_registry : global イベント
各グローバルオブジェクトが利用可能になった時、サーバーから wl_registry の global イベントが送信され、設定されたハンドラ関数が実行されます。
今回のソースコード上では、_registry_global() が呼ばれます。
global イベント内では、各グローバルオブジェクトのバインドや、そのオブジェクトのハンドラ設定を行うことになります。
今回のソースコード上では、_registry_global() が呼ばれます。
global イベント内では、各グローバルオブジェクトのバインドや、そのオブジェクトのハンドラ設定を行うことになります。
static void _registry_global( void *data,struct wl_registry *registry, uint32_t id,const char *interface,uint32_t version) { printf("%s | id:%u | ver:%u\n", interface, id, version); //wl_compositor が使えるようにバインドする if(strcmp(interface, "wl_compositor") == 0) g_compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 1); }
data | wl_registry_add_listener() で渡された data |
---|---|
interface | インターフェイス名 (文字列) |
id | サーバー上での識別 ID (1〜) |
version | インターフェイスの最大バージョン (1〜) |
インターフェイスのバインド
クライアント内で任意のインターフェイスの機能を使いたい場合、
global イベントハンドラ内で「バインド」して、オブジェクトのポインタを取得する必要があります。
取得したポインタは、それらの機能を使う際に必要となるので、保存しておきます。
wl_registry_bind() 関数を使って、バインドできます。
実際に使う場合は、global イベントで渡されたインターフェース名の文字列 (interface) から、必要なインターフェイスを判別して、それをバインドしてください。
global イベントハンドラ内で「バインド」して、オブジェクトのポインタを取得する必要があります。
取得したポインタは、それらの機能を使う際に必要となるので、保存しておきます。
wl_registry_bind() 関数を使って、バインドできます。
void *wl_registry_bind( struct wl_registry *wl_registry, uint32_t name, const struct wl_interface *interface, uint32_t version);
name | global イベントで渡された、オブジェクトの識別 ID |
---|---|
interface | 各インターフェイスのヘッダファイルで定義されている「<name>_interface」の変数のポインタを渡します。 wl_compositor なら、&wl_compositor_interface。 |
version | クライアントが使用する、インターフェイスのバージョン |
戻り値 | インターフェイスのオブジェクトのポインタ |
実際に使う場合は、global イベントで渡されたインターフェース名の文字列 (interface) から、必要なインターフェイスを判別して、それをバインドしてください。
インターフェイスのバージョンについて
バインド時には、使用するインターフェイスのバージョンを指定する必要があります。
クライアントが必要になる、任意のバージョンを指定することができますが、注意点があります。
global イベントの引数として渡される version の数値を、バインド時にそのまま指定しないでください。
global イベントで渡される version の値は、サーバーが実装している最大バージョンの値です。
「クライアントが実際に対応しているバージョン < サーバーが実装している最大バージョン」の状態で、バインド時に、サーバーの最大バージョンを指定してしまうと、問題が起こる場合があります。
例えば、サーバー側の最大バージョンが「4」であった時に、クライアント側ではバージョン「3」までの対応しかしていない場合、バインド時のバージョンを「4」に指定してしまうと、クライアントが対応していないのに、ver 4 での処理を行ってしまうことになります。
なお、「クライアントが対応しているバージョン > サーバーの最大バージョン」となる場合もあるので、その場合は、サーバーが対応している最大バージョンを指定します。
例えば、ハンドラ関数をセットする時に使う構造体データは、インターフェイスのバージョンが上がると、ハンドラの種類が増えて、メンバが増える可能性があります。
その場合、クライアント側では2つのメンバしか設定していないのに、バージョンの高いサーバー側では、3つのメンバを参照する、といったことになってしまう可能性があります。
これにより、新しいバージョンで増えたハンドラ関数のポインタが正しく参照できず、プログラムが正常に動作しなくなります。
そのため、バインド時は、クライアントが実際に使用している最大バージョンか、それ以下の値を指定する必要があります。
例えば、wl_compositor の子として、wl_surface と wl_region が作成できますが、
現在の wl_compositor のバージョンが「3」だったとして、その子である wl_surface や wl_region で使用できる処理などが増えると、wl_compositor のバージョンは wl_surface・wl_region に合わせて「4」に上がります。
親のバージョンは、常にすべての子オブジェクトの最大バージョンとなります。
例えば、wl_surface 上で、wl_surface_set_buffer_scale() は、「ver 3 (wl_compositor の ver 3)」から使用できます。
「wayland-client-protocol.h」内を見てみると、以下のマクロが定義されています。
つまり、wl_surface_set_buffer_scale の機能は、ver 3 から使えるという意味です。
このマクロを使ってバージョンを比較すると、指定機能が使えるかどうかを判定できます。
クライアントが必要になる、任意のバージョンを指定することができますが、注意点があります。
global イベントの引数として渡される version の数値を、バインド時にそのまま指定しないでください。
global イベントで渡される version の値は、サーバーが実装している最大バージョンの値です。
「クライアントが実際に対応しているバージョン < サーバーが実装している最大バージョン」の状態で、バインド時に、サーバーの最大バージョンを指定してしまうと、問題が起こる場合があります。
例えば、サーバー側の最大バージョンが「4」であった時に、クライアント側ではバージョン「3」までの対応しかしていない場合、バインド時のバージョンを「4」に指定してしまうと、クライアントが対応していないのに、ver 4 での処理を行ってしまうことになります。
なお、「クライアントが対応しているバージョン > サーバーの最大バージョン」となる場合もあるので、その場合は、サーバーが対応している最大バージョンを指定します。
サーバーのバージョンの方が高い場合
バインド時に、クライアントが対応しているバージョンよりも、高いバージョンを指定した場合は、問題が起こる可能性があります。例えば、ハンドラ関数をセットする時に使う構造体データは、インターフェイスのバージョンが上がると、ハンドラの種類が増えて、メンバが増える可能性があります。
その場合、クライアント側では2つのメンバしか設定していないのに、バージョンの高いサーバー側では、3つのメンバを参照する、といったことになってしまう可能性があります。
これにより、新しいバージョンで増えたハンドラ関数のポインタが正しく参照できず、プログラムが正常に動作しなくなります。
そのため、バインド時は、クライアントが実際に使用している最大バージョンか、それ以下の値を指定する必要があります。
子オブジェクトとバージョンの関連性について
あるインターフェイスのオブジェクトを使って作成できるオブジェクト (子オブジェクト) で、処理やハンドラの種類が増えたりすると、その親のインターフェイスのバージョンも上がります。例えば、wl_compositor の子として、wl_surface と wl_region が作成できますが、
現在の wl_compositor のバージョンが「3」だったとして、その子である wl_surface や wl_region で使用できる処理などが増えると、wl_compositor のバージョンは wl_surface・wl_region に合わせて「4」に上がります。
親のバージョンは、常にすべての子オブジェクトの最大バージョンとなります。
使える機能の確認
どのバージョンで、どの機能やイベントが使えるかは、ヘッダファイル内に定義されているマクロで確認できます。例えば、wl_surface 上で、wl_surface_set_buffer_scale() は、「ver 3 (wl_compositor の ver 3)」から使用できます。
「wayland-client-protocol.h」内を見てみると、以下のマクロが定義されています。
#define WL_SURFACE_SET_BUFFER_SCALE_SINCE_VERSION 3
つまり、wl_surface_set_buffer_scale の機能は、ver 3 から使えるという意味です。
このマクロを使ってバージョンを比較すると、指定機能が使えるかどうかを判定できます。
オブジェクトの破棄
オブジェクトが不要になった場合は、*_destroy で破棄します。
ここでは、wl_registry と wl_compositor を取得しているので、以下の関数で破棄します。
なお、インターフェイスのバージョンによっては、*_release という関数が存在しますが、この関数が使用できる場合は、*_destroy の代わりに *_release で破棄する場合があります。
詳細については、実際に使用する時に説明します。
ここでは、wl_registry と wl_compositor を取得しているので、以下の関数で破棄します。
void wl_registry_destroy(struct wl_registry *wl_registry); void wl_compositor_destroy(struct wl_compositor *wl_compositor);
なお、インターフェイスのバージョンによっては、*_release という関数が存在しますが、この関数が使用できる場合は、*_destroy の代わりに *_release で破棄する場合があります。
詳細については、実際に使用する時に説明します。