Wayland - ウィンドウの表示 (xdg-shell)

ウィンドウの表示 (xdg-shell)
wl_shell_surface を使うと、基本的なウィンドウの操作はできますが、最小限の機能しかないため、実際のウィンドウとして使うには物足りない場合があります。

もう少しウィンドウとしての機能を増やしたい場合は、拡張プロトコルとして定義されている 「xdg-shell」 を使います。
※ デスクトップによっては、実装されていない場合があります。

wl_shell_surface とほぼ同じような機能を持ちますが、最小化したり、最大化/フルスクリーンを戻すなどの機能が追加されています。

拡張プロトコルに関しては、Wayland 本体側では、それを使うために必要なヘッダファイル等は、デフォルトで用意されていません。
それらの機能を使う場合、開発環境側で、wayland-scanner ツールを使って、プロトコル情報を定義した xml ファイルから、ヘッダファイルとソースコードを作成する必要があります。
wayland-scanner
Wayland では、各プロトコルの情報は xml ファイルで定義されています。
wayland-scanner ツールを使うと、xml から、開発に必要なヘッダファイル・ソースコードを作成できます。
wayland-scanner は、Wayland の開発パッケージ内に含まれています。

Wayland のソースファイルを見ると、"protocol/wayland.xml" ファイルがあり、そこにデフォルトのプロトコルの情報が記述されています。
それを scanner で変換したヘッダファイルが、wayland-client-protocol.h となっています。

生成したヘッダファイルでは、マクロや列挙型、インライン関数などが定義されます。
生成したソースファイルでは、<name>_interface など、必要なグローバル変数が定義されます。

wayland-protocols には、拡張プロトコル用の xml ファイルのソースがあります。
これらを実際に使う場合は、wayland-scanner で xml からファイルを作成する必要があります。
パッケージ
拡張プロトコルのファイルは、パッケージにもあります。

Debian/Ubuntu, Arch Linux : wayland-protocols
Red Hat : wayland-protocols-devel

サイトからダウンロードして使うよりは、環境に合わせたバージョンを使うためにも、パッケージからインストールしたものを使う方が良いでしょう。

また、拡張プロトコルを使う Wayland クライアントプログラムのソースコードを公開する場合も、
変換したファイルを同梱するのではなく、ビルド時に wayland-scanner を実行させて、インストールされている xml からファイルを生成する形になります。
使い方
$ wayland-scanner [option] <client-header|server-header|code> <input> <output>

client-headerクライアント用のヘッダファイルを作成します。
出力ファイル名は、「<name>-client-protocol.h」 にするのが一般的。
server-headerサーバー用のヘッダファイルを作成します。
codeクライアント/サーバーで必要なソースコードを作成します。
ファイル名は 「<name>-protocol.c」 にするのが一般的。

<使用例>
$ wayland-scanner client-header xdg-shell-unstable-v6.xml xdg-shell-unstable-v6-client-protocol.h
xdg-shell のファイルを変換
wayland-protocols パッケージでは、「/usr/share/wayland-protocols」 下に、各プロトコルの xml ファイルがインストールされています。

xdg-shell の xml ファイルは、unstable/xdg-shell ディレクトリ内にあります。
現在は v6 が最新バージョンとなるので、xdg-shell-unstable-v6.xml を変換します。
v6 がない場合は、存在するバージョンのものを使ってください。

クライアント用のヘッダファイルとソースファイルが必要なので、それらを作成します。
ここでソースファイルを作成せずにコンパイルすると、「<name>_interface が定義されていない」 というエラーが出ます。

$ wayland-scanner client-header /usr/share/wayland-protocols/unstable/xdg-shell/xdg-shell-unstable-v6.xml \
 xdg-shell-unstable-v6-client-protocol.h

$ wayland-scanner code /usr/share/wayland-protocols/unstable/xdg-shell/xdg-shell-unstable-v6.xml \
 xdg-shell-unstable-v6-protocol.c
xdg-shell でウィンドウ表示
赤いウィンドウを表示するだけです。

GNOME では、タスクバー上のアプリのメニューをクリックして出る「終了」のコマンドで終了できますが、weston 上ではアプリを終了する方法がないので、起動した端末を閉じるか、kill コマンドで終了させてください。

> 06sub.h
> 06sub.c

wayland-scanner で変換した "xdg-shell-unstable-v6-protocol.c" も一緒にコンパイルしてください。
また、"xdg-shell-unstable-v6-client-protocol.h" は、ソースファイルと同じディレクトリに置いておいてください。

$ cc -o test 06_xdgshell.c 06sub.c xdg-shell-unstable-v6-protocol.c -lwayland-client -lrt

<06_xdgshell.c>
#include <stdio.h>
#include <stdlib.h>
#include "06sub.h"

//-----------------

wayland *g_wl;

/* ウィンドウ */
typedef struct
{
    struct wl_surface *surface;
    struct zxdg_surface_v6 *xdg_surface;
    struct zxdg_toplevel_v6 *xdg_toplevel;
    imagebuf *img;
    int configure_flag,
        close_flag;
}window;

void window_update(window *p);

//-----------------


/* イメージ描画 */

static void _draw_image(imagebuf *p)
{
    uint8_t *pd = (uint8_t *)p->data;
    int i;

    for(i = p->width * p->height; i > 0; i--, pd += 4)
    {
        pd[0] = 0;
        pd[1] = 0;
        pd[2] = 255;
        pd[3] = 0;
    }
}


//-------- xdg_surface

static void _xdg_surface_configure(
    void *data,struct zxdg_surface_v6 *surface,uint32_t serial)
{
    window *win = (window *)data;

    printf("surface-configure: serial %u\n", serial);

    zxdg_surface_v6_ack_configure(surface, serial);

    if(win->configure_flag == 0)
    {
        win->configure_flag = 1;

        _draw_image(win->img);
        window_update(win);
    }
}

static const struct zxdg_surface_v6_listener g_xdg_surface_listener = {
    _xdg_surface_configure
};

//-------- xdg_toplevel

static void _xdg_toplevel_configure(
    void *data, struct zxdg_toplevel_v6 *toplevel,
    int32_t width, int32_t height, struct wl_array *states)
{
    uint32_t *ps;

    printf("toplevel-configure: w %d, h %d / states: ", width, height);

    wl_array_for_each(ps, states)
    {
        switch(*ps)
        {
            case ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED:
                printf("MAXIMIZED ");
                break;
            case ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN:
                printf("FULLSCREEN ");
                break;
            case ZXDG_TOPLEVEL_V6_STATE_RESIZING:
                printf("RESIZING ");
                break;
            case ZXDG_TOPLEVEL_V6_STATE_ACTIVATED:
                printf("ACTIVATED ");
                break;
        }
    }

    printf("\n");
}

static void _xdg_toplevel_close(void *data,struct zxdg_toplevel_v6 *toplevel)
{
    printf("close\n");

    ((window *)data)->close_flag = 1;
}

const struct zxdg_toplevel_v6_listener g_xdg_toplevel_listener = {
    _xdg_toplevel_configure, _xdg_toplevel_close
};

//--------- window

/* ウィンドウ作成 */

window *window_create(wayland *wl,int width,int height)
{
    window *p;

    p = (window *)calloc(1, sizeof(window));
    if(!p) return NULL;

    //wl_surface

    p->surface = wl_compositor_create_surface(wl->compositor);

    //xdg_surface

    p->xdg_surface = zxdg_shell_v6_get_xdg_surface(wl->xdg_shell, p->surface);

    zxdg_surface_v6_add_listener(p->xdg_surface, &g_xdg_surface_listener, p);

    //xdg_toplevel

    p->xdg_toplevel = zxdg_surface_v6_get_toplevel(p->xdg_surface);

    zxdg_toplevel_v6_add_listener(p->xdg_toplevel, &g_xdg_toplevel_listener, p);

    //適用

    wl_surface_commit(p->surface);

    //イメージ作成

    p->img = imagebuf_create(wl, width, height);

    return p;
}

/* ウィンドウ破棄 */

void window_destroy(window *p)
{
    if(p)
    {
        imagebuf_destroy(p->img);

        zxdg_toplevel_v6_destroy(p->xdg_toplevel);
        zxdg_surface_v6_destroy(p->xdg_surface);
        wl_surface_destroy(p->surface);

        free(p);
    }
}

/* ウィンドウ更新 */

void window_update(window *p)
{
    wl_surface_attach(p->surface, p->img->buffer, 0, 0);
    wl_surface_damage(p->surface, 0, 0, p->img->width, p->img->height);
    wl_surface_commit(p->surface);
}

//--------------

int main(void)
{
    window *win;

    g_wl = wayland_init();

    win = window_create(g_wl, 256, 256);

    //

    while(wl_display_dispatch(g_wl->display) != -1 && win->close_flag == 0);

    //

    window_destroy(win);

    wayland_finish(g_wl);

    return 0;
}
解説
xdg-shell の場合、オブジェクト名の先頭に z が付いていますが、これは unstable (不安定) であるという意味です。
正式版になる際には、z は取り除かれる予定です。

また、unstable な状態の場合は、バージョンが上がるとオブジェクト名が変わるため、バージョン管理がしづらいので注意してください。
zxdg_shell_v6 のバインド
まずは、wl_registry のハンドラ内で、"zxdg_shell_v6" をバインドします。
また、バインドと同時に、zxdg_shell_v6 にハンドラをセットしています。

static void _xdg_shell_ping(void *data,
    struct zxdg_shell_v6 *xdg_shell,uint32_t serial)
{
    zxdg_shell_v6_pong(xdg_shell, serial);
}

static const struct zxdg_shell_v6_listener g_xdg_shell_listener = {
    _xdg_shell_ping
};

...

else if(strcmp(itf, "zxdg_shell_v6") == 0)
{
    p->xdg_shell = wl_registry_bind(reg, id, &zxdg_shell_v6_interface, 1);

    zxdg_shell_v6_add_listener(p->xdg_shell, &g_xdg_shell_listener, NULL);
}

ping イベント時は、wl_shell_surface の時と同じで、クライアントが応答可能な状態かどうかを通知してくるので、zxdg_shell_v6_pong() 関数で応答します。
ウィンドウの作成
//wl_surface

p->surface = wl_compositor_create_surface(wl->compositor);

//xdg_surface

p->xdg_surface = zxdg_shell_v6_get_xdg_surface(wl->xdg_shell, p->surface);

zxdg_surface_v6_add_listener(p->xdg_surface, &g_xdg_surface_listener, p);

//xdg_toplevel

p->xdg_toplevel = zxdg_surface_v6_get_toplevel(p->xdg_surface);

zxdg_toplevel_v6_add_listener(p->xdg_toplevel, &g_xdg_toplevel_listener, p);

//適用

wl_surface_commit(p->surface);

まずは wl_surface が必要です。
次に、wl_surface から zxdg_surface_v6 を作成します。
さらに、zxdg_surface_v6 から、トップレベルウィンドウ用の zxdg_toplevel_v6 を作成します。

最後に、これらの要求を wl_surface_commit() で適用させます。
zxdg_surface_v6 のイベント
static void _xdg_surface_configure(
    void *data,struct zxdg_surface_v6 *surface,uint32_t serial)
{
    window *win = (window *)data;

    printf("surface-configure: serial %u\n", serial);

    zxdg_surface_v6_ack_configure(surface, serial);

    if(win->configure_flag == 0)
    {
        win->configure_flag = 1;

        _draw_image(win->img);
        window_update(win);
    }
}

zxdg_surface_v6 の configure イベントは、ウィンドウのサイズ変更などの状態変更が終了した時に呼ばれます。
クライアントは zxdg_surface_v6_ack_configure() で応答する必要があります。

なお、このイベントが呼ばれる前にウィンドウ内容を描画しようとすると、
zxdg_surface_v6@7: error 3: xdg_surface has never been configured
というプロトコルエラーが出て強制終了します。

一度 configure イベントが呼ばれた後でないと、ウィンドウ内容は描画できないようです。
なので、最初にこのハンドラが呼ばれた時に描画更新処理を行っています。
zxdg_toplevel_v6 のイベント
static void _xdg_toplevel_configure(
    void *data, struct zxdg_toplevel_v6 *toplevel,
    int32_t width, int32_t height, struct wl_array *states)
{
    uint32_t *ps;

    printf("toplevel-configure: w %d, h %d / states: ", width, height);

    wl_array_for_each(ps, states)
    {
        switch(*ps)
        {
            case ZXDG_TOPLEVEL_V6_STATE_MAXIMIZED:
                printf("MAXIMIZED ");
                break;
            case ZXDG_TOPLEVEL_V6_STATE_FULLSCREEN:
                printf("FULLSCREEN ");
                break;
            case ZXDG_TOPLEVEL_V6_STATE_RESIZING:
                printf("RESIZING ");
                break;
            case ZXDG_TOPLEVEL_V6_STATE_ACTIVATED:
                printf("ACTIVATED ");
                break;
        }
    }

    printf("\n");
}

static void _xdg_toplevel_close(void *data,struct zxdg_toplevel_v6 *toplevel)
{
    printf("close\n");

    ((window *)data)->close_flag = 1;
}

configure イベントは、サーバーが、クライアントに対して、ウィンドウのサイズ変更や状態変更を要求するときに通知されます。
ここで示された内容が実際に適用された時には、zxdg_surface_v6 の configure イベントが呼ばれます。

width, height はリサイズの幅と高さ。状態変化しかしない時は 0 となります。
states は、状態変化の配列データとなっています。

wl_array
wl_array は、wayland-util.h で定義されています。
配列のデータを扱います。

wl_array_for_each は、マクロとして以下のように定義されています。

#define wl_array_for_each(pos, array) \
    for (pos = (array)->data; \
        (const char *) pos < ((const char *) (array)->data + (array)->size); \
        (pos)++)

pos には、データのポインタ変数を指定します。
ポインタを加算していき、バッファの最後まで進んだ時に終了しています。

states 引数
states は、uint32_t 型の数値の配列となっていて、現在の状態が ON なら、対応する値が入っています。
値は、enum zxdg_toplevel_v6_state の列挙型で定義されています。

今回のプログラムの場合は、他のウィンドウをアクティブにした後、テストウィンドウ内をクリックでアクティブにすると、
states に ZXDG_TOPLEVEL_V6_STATE_ACTIVATED の値が入っています。

close イベント
close イベントは、サーバーからアプリの終了を通知された時に呼ばれます。

GNOME では、タスクバー上のアプリのメニューに 「終了」 のコマンドがあるので、それが選択されると、呼ばれます。
weston 上では、タスクバーがないので、close が呼ばれるのを確かめる方法はありません (多分)。