Wayland の構造
クライアント用のヘッダファイル
Wayland クライアントのプログラムでは、「wayland-client.h」のヘッダファイルを使います。
このヘッダファイル内では、以下の2つのヘッダファイルが、内部でインクルードされています。
まずは、この2つをテキストエディタで開いてみてください。
クライアントプログラムを作る場合は、上記の2つのヘッダが使われますが、通常の C 関数は 40 個くらいしか定義されていません。
実際には、「wayland-client-protocol.h」で定義されているインライン関数を主に使用していきます。
このヘッダファイル内では、以下の2つのヘッダファイルが、内部でインクルードされています。
wayland-client-core.h wayland-client-protocol.h
まずは、この2つをテキストエディタで開いてみてください。
wayland-client-core.h | 関数の定義 |
---|---|
wayland-client-protocol.h | プロトコルのクライアント用定義。 インライン関数・列挙型・マクロなどが定義されている。 |
クライアントプログラムを作る場合は、上記の2つのヘッダが使われますが、通常の C 関数は 40 個くらいしか定義されていません。
実際には、「wayland-client-protocol.h」で定義されているインライン関数を主に使用していきます。
クライアントとサーバーのやりとり
Wayland クライアントが Wayland サーバーとやりとりする場合、
「wayland-client-core.h」で定義されている「wl_proxy_*()」関数を使います。
ただし、クライアント側で wl_proxy_*() 関数を直接呼ぶことは、まずありません。
Wayland では、各機能ごとにインターフェイスが分けられています。
基本的なウィンドウの処理を行うのは「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 のこのような構造を利用して、各デスクトップやツールキットが、独自の機能を定義・実装して、それをクライアントが使うといったことも可能になります。
プログラム
サーバーで使用できるインターフェイスの一覧表示と、バインドの例。
<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 をバインドしています。
実行結果
2019年12月時点での実行結果は、以下の通りです。
weston_* は、weston が独自に追加している機能です。
gtk_* は、GTK+ で使われる機能です。
weston
wl_compositor | id:1 | ver:4 wl_subcompositor | id:2 | ver:1 wp_viewporter | id:3 | ver:1 zxdg_output_manager_v1 | id:4 | ver:2 wp_presentation | id:5 | ver:1 zwp_relative_pointer_manager_v1 | id:6 | ver:1 zwp_pointer_constraints_v1 | id:7 | ver:1 zwp_input_timestamps_manager_v1 | id:8 | ver:1 wl_data_device_manager | id:9 | ver:3 wl_shm | id:10 | ver:1 wl_drm | id:11 | ver:2 wl_seat | id:12 | ver:5 zwp_linux_dmabuf_v1 | id:13 | ver:3 zwp_linux_explicit_synchronization_v1 | id:14 | ver:2 wl_output | id:15 | ver:3 zwp_input_panel_v1 | id:16 | ver:1 zwp_input_method_v1 | id:17 | ver:1 zwp_text_input_manager_v1 | id:18 | ver:1 xdg_wm_base | id:19 | ver:1 zxdg_shell_v6 | id:20 | ver:1 wl_shell | id:21 | ver:1 weston_desktop_shell | id:22 | ver:1 weston_screenshooter | id:23 | ver:1
weston_* は、weston が独自に追加している機能です。
GNOME
wl_drm | id:1 | ver:2 wl_compositor | id:2 | ver:4 wl_shm | id:3 | ver:1 wl_output | id:4 | ver:2 zxdg_output_manager_v1 | id:5 | ver:3 wl_data_device_manager | id:6 | ver:3 gtk_primary_selection_device_manager | id:7 | ver:1 wl_subcompositor | id:8 | ver:1 xdg_wm_base | id:9 | ver:2 zxdg_shell_v6 | id:10 | ver:1 wl_shell | id:11 | ver:1 gtk_shell1 | id:12 | ver:3 wp_viewporter | id:13 | ver:1 zwp_pointer_gestures_v1 | id:14 | ver:1 zwp_tablet_manager_v2 | id:15 | ver:1 wl_seat | id:16 | ver:5 zwp_relative_pointer_manager_v1 | id:17 | ver:1 zwp_pointer_constraints_v1 | id:18 | ver:1 zxdg_exporter_v1 | id:19 | ver:1 zxdg_importer_v1 | id:20 | ver:1 zwp_linux_dmabuf_v1 | id:21 | ver:3 zwp_keyboard_shortcuts_inhibit_manager_v1 | id:22 | ver:1 zwp_text_input_manager_v3 | id:23 | ver:1 gtk_text_input_manager | id:24 | 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() 関数で、ハンドラをセットします。
▼ Wayland での定義
グローバル変数で struct wl_registry_listener の構造体データを用意して、各メンバにハンドラ関数のポインタをセットしたら、それを wl_registry_add_listener() 関数に渡します。
第3引数の void *data は、ハンドラ関数に渡されるユーザー定義値です。
ハンドラを設定する場合は、それぞれのオブジェクトごとに専用の構造体「struct <name>_listener」が定義されているので、その構造体をグローバル変数で定義し、値をセットして、*_add_listener() 関数に渡します。
wl_proxy_add_listener() 関数の内部では、構造体のポインタである implementation の値を、そのままオブジェクトの内部メンバに代入しているだけなので、渡した構造体変数のポインタは、常に参照可能な状態でなければなりません。
つまり、構造体をローカル変数として定義して渡してはいけないということです。
今回のソースコードの場合、main() 関数内に構造体をローカル変数として定義したとしても、プログラムが終了するまではデータが維持されるため、問題はありません。
しかし、サブ関数内で、ローカル変数として定義し、*_add_listener() 関数に渡した場合は、サブ関数が終了した時点で構造体のデータが参照できなくなるので、正しく動作しません。
▼ Wayland での定義
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); } ---------- 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 は、ハンドラ関数に渡されるユーザー定義値です。
ハンドラを設定する場合は、それぞれのオブジェクトごとに専用の構造体「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); }
interface | インターフェイス名 (文字列) |
---|---|
id | サーバー上での識別子 ID (1〜) |
version | インターフェイスの最大バージョン (1〜) |
インターフェイスのバインド
クライアント内で任意のインターフェイスの機能を使いたい場合、
global イベントハンドラ内で「バインド」して、オブジェクトのポインタを取得する必要があります。
取得したポインタは、それらの機能を使う際に必要となるので、保存しておきます。
wl_registry_bind() 関数を使って、バインドできます。
実際に使う場合は、global イベントで渡されたインターフェース名の文字列から、必要なインターフェイスを判別して、それをバインドしてください。
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 イベントで渡されたインターフェース名の文字列から、必要なインターフェイスを判別して、それをバインドしてください。
インターフェイスのバージョンについて
バインド時には、使用するインターフェイスのバージョンを指定する必要があります。
クライアントが必要になる、任意のバージョンを指定することができますが、注意点があります。
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 から使えるという意味です。
このマクロを使ってバージョンを比較すると、指定機能が使えるかどうかを判定できます。