Wayland - 必要な機能のバインド

Wayland の構造
ヘッダファイルの構造
Wayland のクライアント用のヘッダファイル、
wayland-client-core.h, wayland-client-protocol.h の中身を見てみてください。
(wayland-client.h は、この2つのファイルをインクルードしているだけです)

wayland-client-core.h では関数定義が行われていますが、数は少ないです。
wayland-client-protocol.h では、多数の定義が行われていますが、主にインライン関数、列挙値、マクロなどの定義だけで、関数自体は定義されていません。

クライアントプログラムを作る場合、上記の2つのファイルをメインとして使います。
しかし、普通の関数は 40 個くらいしか定義されていません。

これでどうやって動作させるかというと、主にプログラム内で使うのは、wayland-client-protocol.h で定義されているインライン関数です。
wl_proxy
クライアントがサーバーとやりとりする場合は、wayland-client-core.h で定義されている wl_proxy_*() 関数を使います。

それぞれの各インターフェイスでは、実行する処理ごとに数値の ID が定義されていて、
(wayland-client-protocol.h 内でマクロとして定義されている)
クライアントが何かを要求する場合は、処理番号として、その ID と、関連するデータを渡すと、
サーバーがそれを判断して処理する、という形になっています。

実際には、それらの処理はすべてインライン関数として定義されているので、プログラム側ではその過程を意識する必要はなく、クライアント側で wl_proxy_*() 関数を直接呼ぶことはまずありません。
インターフェイス
Wayland では、ウィンドウやイメージ、デバイスの機能などは、それぞれ機能別にインターフェイスが分けられています。

クライアントでそれらを使う場合は、まずインターフェイスをバインドして、オブジェクトのポインタを取得する必要があります。
感覚的には、プラグインと同じようなものです。

ただし、クライアント側で使いたい機能が、サーバー側で実装されていない場合もあります。
Wayland 側は機能を定義するだけなので、それを実際に実装するのは、サーバー側です。
サーバーが未成熟であったり、そもそも対応するつもりがない機能の場合、実装されていなければクライアントが使うことは出来ないので、注意が必要です。

なお、サーバー側がそのデスクトップ用に独自に機能を追加して、それをクライアントが使うことも出来ます。
ソースコード
サーバー上で現在利用可能なインターフェイスのリストを出力します。
また、バインドの例として、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);

    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)
{

}

static const struct wl_registry_listener g_reg_listener = {
    _registry_global, _registry_global_remove
};

int main(void)
{
    struct wl_display *display;
    struct wl_registry *reg;

    display = wl_display_connect(NULL);
    if(!display) return 1;

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

2017/10 時点での実行結果は、以下の通りです。
デスクトップによっては、実装されている機能と、実装されていない機能があります。
また、各デスクトップが独自で追加している機能もあります。
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
wl_data_device_manager, id 5, ver 3
gtk_primary_selection_device_manager, id 6, ver 1
zxdg_shell_v6, id 7, ver 1
wl_shell, id 8, ver 1
gtk_shell1, id 9, ver 2
wl_subcompositor, id 10, ver 1
zwp_pointer_gestures_v1, id 11, ver 1
zwp_tablet_manager_v2, id 12, ver 1
wl_seat, id 13, ver 5
zwp_relative_pointer_manager_v1, id 14, ver 1
zwp_pointer_constraints_v1, id 15, ver 1
zxdg_exporter_v1, id 16, ver 1
zxdg_importer_v1, id 17, ver 1
zwp_linux_dmabuf_v1, id 18, ver 3
zwp_keyboard_shortcuts_inhibit_manager_v1, id 19, ver 1
KDE
wl_compositor, id 1, ver 3
wl_shell, id 2, ver 1
xdg_shell, id 3, ver 1
wl_shm, id 4, ver 1
wl_seat, id 5, ver 4
zwp_pointer_gestures_v1, id 6, ver 1
zwp_pointer_constraints_v1, id 7, ver 1
wl_data_device_manager, id 8, ver 2
org_kde_kwin_idle, id 9, ver 1
org_kde_plasma_shell, id 10, ver 4
qt_surface_extension, id 11, ver 1
org_kde_plasma_window_management, id 12, ver 7
org_kde_kwin_shadow_manager, id 13, ver 2
org_kde_kwin_dpms_manager, id 14, ver 1
org_kde_kwin_server_decoration_manager, id 15, ver 1
org_kde_kwin_outputmanagement, id 16, ver 1
wl_subcompositor, id 17, ver 1
zwp_relative_pointer_manager_v1, id 18, ver 1
wl_output, id 19, ver 2
org_kde_kwin_outputdevice, id 20, ver 1
org_kde_kwin_fake_input, id 21, ver 2
org_kde_kwin_blur_manager, id 22, ver 1
org_kde_kwin_contrast_manager, id 23, ver 1
org_kde_kwin_slide_manager, id 24, ver 1
解説
wl_display_get_registry()
利用可能なインターフェイスの取得&バインドをするために、wl_display_get_registry()wl_registry を作成します。

この関数は、wayland-client-protocol.h 内でインライン関数として定義されており、
実際には wl_proxy_marshal_constructor() 関数が呼ばれています。
WL_DISPLAY_GET_REGISTRY は処理番号です。1 として定義されています。

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_proxy_add_listener() 関数でハンドラをセットします。
wl_registry_add_listener()
次に、wl_registry 内で起こるイベントを取得するために、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);
}


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() に渡します。
data は、ハンドラ関数に渡されるユーザー定義値です。

ハンドラを設定する場合は、それぞれのオブジェクトごとに専用の構造体 (struct wl_<name>_listener) が定義されているので、その構造体にデータをセットし、その構造体変数のポインタを渡す形となります。

ローカル変数にしないこと
ここで、一つ注意が必要です。

wl_proxy_add_listener() 内部では、implementation のポインタの値を、そのままオブジェクトの内部データに代入しているだけなので、渡した構造体変数のポインタは常に参照可能な状態でなければなりません。
つまり、構造体をローカル変数として定義して渡してはいけないということです。

今回のソースコードの場合は、main() 関数内にローカル変数として定義した場合は、プログラムが終了するまでデータが維持されるので問題ないのですが、サブ関数内でローカル変数として定義して実行した場合は、関数が終了した時点で構造体のデータが参照できなくなるので、正しく動作しなくなります。
wl_display_roundtrip()
wl_registry 関連の設定が終わったら、wl_display_roundtrip() 関数を呼び出して、
サーバーがクライアントからの要求をすべて処理するまで待ちます。
これをしないと、何も起こらずに (今回の場合は何も表示されずに) 終了してしまいます。

int wl_display_roundtrip(struct wl_display *display);

戻り値: ディスパッチされたイベントの数。-1 で失敗。

今回の場合は、wl_registry が作成された時点で、現在利用可能な各インターフェイスの global イベントが生成されるので、そのすべてのイベントが、クライアント側のハンドラ関数で処理されるまで待つことになります。
wl_registry イベントハンドラ
各インターフェイスが利用可能になった時、サーバーから、wl_registry の global イベントが送信されます。
今回のソースコード上では、_registry_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);

    if(strcmp(interface, "wl_compositor") == 0)
        g_compositor = wl_registry_bind(registry, id, &wl_compositor_interface, version);
}

interface : インターフェース名
id : サーバー上での識別子 ID (1〜)
version : インターフェースのバージョン番号 (1〜)
バインド
クライアント内で任意のインターフェイスの機能を使いたい場合は、global イベントハンドラ内でバインドして、オブジェクトのポインタを取得する必要があります。
取得したポインタは、それらの機能を使う時に必要なので、グローバル変数として保存しておきます。

wl_registry_bind() 関数を使うと、バインドできます。
これも、インライン関数として以下のように定義されています。

static inline void *wl_registry_bind(
    struct wl_registry *wl_registry, uint32_t name,
    const struct wl_interface *interface, uint32_t version)
{
    struct wl_proxy *id;

    id = wl_proxy_marshal_constructor_versioned((struct wl_proxy *) wl_registry,
             WL_REGISTRY_BIND, interface, version, name, interface->name, version, NULL);

    return (void *) id;
}

実際に使う場合は、global イベントで渡されたインターフェース名の文字列から、必要なものを判別して、それをバインドしてください。

wl_registry_bind() に渡す const struct wl_interface *interface については、
&wl_compositor_interface というように、<name>_interface の変数のポインタを指定します。
インターフェイスのバージョンについて
バインド時には、使用するインターフェイスのバージョンを指定する必要があります。

この時、ハンドラ関数の引数として渡される version の数値をそのまま指定しないでください。
クライアント内で使うことを想定する最大バージョンの値か、それ以下の値にしてください。

例えば、wl_compositor を使う場合、
クライアント内では ver 3 までの機能しか使わないなら、3 か、それ以下の値を指定します。
サーバー上のバージョンが 3 より低い場合もあるので、その時はサーバー上の最大バージョン値を指定します。

クライアントよりサーバーのバージョンの方が高い場合
インターフェイスのバージョンが、クライアントが対応しているバージョンよりもサーバーのバージョンの方が高い場合、問題が起こる可能性があります。

ハンドラ関数をセットする時に使う構造体データは、インターフェイスのバージョンが上がると、ハンドラの種類が増えて、メンバが増える可能性があります。
その場合、クライアントでは2つのメンバしか設定していないのに、サーバーでは3つのメンバを参照する、ということになってしまうため、新しいバージョンで増えたハンドラ関数のポインタが正しく参照できず、プログラムが正常に動作しなくなります。

子オブジェクトとのバージョンの関連性について
あるインターフェイスのオブジェクトを使って作成できるオブジェクト (子オブジェクト) で、処理やハンドラの種類が増えたりすると、その親のインターフェイスのバージョンも上がります。

例えば、wl_compositor の子として、wl_surfacewl_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 から使えるという意味です。

このマクロを使ってバージョンを比較すると、指定機能が使えるかどうかを判定できます。