Wayland: ウィンドウ用イメージの作成について

ウィンドウを表示する手順
Wayland には「ウィンドウを作成する」という関数はありません。

ウィンドウを表示する場合は、ウィンドウのイメージを用意して、その中にウィンドウのイメージ内容を書き込み、それをウィンドウとして表示させる、という手順が必要になります。
イメージの種類
Wayland では、画面上に表示できるイメージは、2種類あります。

「共有メモリ」を使って作る、直接アクセス出来るバッファと、「OpenGL ES (EGL)」のイメージです。

OpenGL を使って描画するなら EGL を使った方が良いですが、直接バッファにアクセスして描画したいなら、共有メモリのバッファを使います。

ただし、両方とも、イメージを作成するまでの手順は長いです。

ここでは、共有メモリを使ったイメージバッファを使用します。
共有メモリのイメージを使う手順
  1. wl_shellwl_compositorwl_shm をバインド。
  2. wl_compositor から wl_surface を作成 (ウィンドウに表示するイメージの操作)
  3. wl_shell から wl_shell_surface を作成 (ウィンドウの操作)
  4. wl_shm から wl_shm_pool を作成。
  5. wl_shm_pool から wl_buffer を作成 (イメージのバッファ)
  6. ウィンドウの内容をイメージバッファに書き込んで描画し、その内容を画面上に表示するように、wl_surface に要求する。
  7. その後、サーバーがイメージバッファにアクセスし、デスクトップ画面に合成する。
プログラム
まずは、サーバーが対応している、共有メモリイメージのフォーマットを列挙してみます。

$ cc -o test 03-shmformat.c -lwayland-client

<03-shmformat.c>
/******************************
 * 共有メモリイメージのフォーマットを列挙
 ******************************/

#include <stdio.h>
#include <string.h>
#include <wayland-client.h>

struct wl_shm *g_shm;


//---------------------
// wl_shm
//---------------------


static void _shm_format(void *data,struct wl_shm *shm,uint32_t format)
{
    if(format == WL_SHM_FORMAT_ARGB8888)
        printf("%u: ARGB8888\n", format);
    else if(format == WL_SHM_FORMAT_XRGB8888)
        printf("%u: XRGB8888\n", format);
    else
    {
        printf("0x%08X: '%c%c%c%c'\n",
            format,
            (char)format,
            (char)(format >> 8),
            (char)(format >> 16),
            (char)(format >> 24));
    }
}

static const struct wl_shm_listener g_shm_listener = { _shm_format };


//---------------------
// wl_registry
//---------------------


static void _registry_global(
    void *data,struct wl_registry *registry,
    uint32_t id,const char *interface,uint32_t version)
{
    if(strcmp(interface, "wl_shm") == 0)
    {
        //wl_shm をバインド
        
        g_shm = wl_registry_bind(registry, id, &wl_shm_interface, 1);

        wl_shm_add_listener(g_shm, &g_shm_listener, NULL);
    }
}

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;

    //wl_registry

    reg = wl_display_get_registry(display);
    wl_registry_add_listener(reg, &g_reg_listener, NULL);

    //wl_registry のイベントを待つ

    wl_display_roundtrip(display);

    //wl_shm のイベントを待つ

    wl_display_roundtrip(display);

    //

    wl_shm_destroy(g_shm);
    wl_registry_destroy(reg);

    wl_display_disconnect(display);

    return 0;
}

実行結果
2025年1月時点。

## GNOME

0: ARGB8888
1: XRGB8888
0x34324241: 'AB24'
0x34324258: 'XB24'
0x36314752: 'RG16'
0x30335241: 'AR30'
0x30335258: 'XR30'
0x30334241: 'AB30'
0x30334258: 'XB30'
0x48345241: 'AR4H'
0x48345258: 'XR4H'
0x48344241: 'AB4H'
0x48344258: 'XB4H'
0x56595559: 'YUYV'
0x3231564E: 'NV12'
0x30313050: 'P010'
0x32315559: 'YU12'
解説
wl_shm
まず、共有メモリを使うために、wl_shm をバインドします。

2025年1月時点での最大バージョンは「2」ですが、フォーマットを列挙するだけなら 1 でいいので、ここでは 1 で固定しています。

そして、バインドと同時に、wl_shm_add_listener() で、フォーマット列挙のためのハンドラをセットしています。

wl_shm : format イベント
static void _shm_format(void *data,struct wl_shm *shm,uint32_t format)
{
...
}

サーバーで対応しているイメージフォーマットの識別子が、一つずつ送られてきます。

識別子は wayland-client-protocol.h にて、enum wl_shm_format の列挙型で定義されており、色々なフォーマットがあります。

enum wl_shm_format {
    /**
     * 32-bit ARGB format, [31:0] A:R:G:B 8:8:8:8 little endian
     */
    WL_SHM_FORMAT_ARGB8888 = 0,
    /**
     * 32-bit RGB format, [31:0] x:R:G:B 8:8:8:8 little endian
     */
    WL_SHM_FORMAT_XRGB8888 = 1,
    /**
     * 8-bit color index format, [7:0] C
     */
    WL_SHM_FORMAT_C8 = 0x20203843,
...

値を見てみると、WL_SHM_FORMAT_ARGB8888WL_SHM_FORMAT_XRGB8888 以外は、下位バイトから順に「ASCII 文字 x 4」で構成されていることがわかります。

WL_SHM_FORMAT_ARGB8888WL_SHM_FORMAT_XRGB8888 については、すべての Wayland サーバーで対応していることが保証されています。
ただし、それ以外のフォーマットに関しては、各サーバーによって、対応しているかどうかが異なります。
フォーマット
ウィンドウ用のイメージとして使う場合、基本的に XRGB8888ARGB8888 を使います。

いずれも 1 px = 4 byte、4 byte の整数値で、上位から順に XRGB, ARGB という順で並んでいます。

ARGB を使うと、アルファ値を使って、ウィンドウを半透明にすることができます。
XRGB では、X の値は使われることなく、完全不透明 (A = 255) となります。

リトルエンディアンなので、1 byte 単位のバッファ上では「B - G - R - A(X)」の順で並びます。

イメージバッファの先頭は (0, 0) の位置で、X は右方向、Y は下方向に進みます。
wl_display_roundtrip()
wl_display_roundtrip() は、クライアントからの要求を、サーバーが処理するまで待つ関数です。

ここでは、この関数を2回実行していますが、
1回目は wl_registry のイベントが処理され、2回目は wl_shm のイベントが処理されています。

wl_registry の global イベント内で、wl_shm をバインドしていますが、ここで生成された wl_shm のイベントは、1回目の wl_display_roundtrip() では実行されないので、もう一度実行しています。

初期化処理に関して
Wayland におけるクライアントプログラムでは、初期化処理として、まず、必要な各グローバルオブジェクトをバインドして、ポインタを取得する必要があります。

そのため、wl_registry を使って行う、バインドなどの初期化処理に関しては、イベントループ内で実行させるのではなく、wl_display_roundtrip() を使って、すべてのイベントなどの処理が終わるまで待ち、必要な情報をすべて取得してからイベントループへ移行するのが、一番良い方法です。

簡単なプログラムであれば、wl_display_roundtrip() を数回実行するだけで十分ですが、もう少し複雑になってきたら、それらの方法を解説します。