Wayland: wl_subsurface

サブサーフェス
ウィンドウの中に、子ウィンドウ (サブサーフェス) を作りたい場合は、wl_subsurface を使います。
ウィンドウに、タイトルバーや枠などの装飾を付けたりする時などに使います。

wl_subsurface は、wl_subcompositor の子インターフェイスなので、wl_subcompositor をバインドして使います。
プログラム
$ cc -o test 12-subsurface.c client.c imagebuf.c xdg-shell-protocol.c -lwayland-client -lrt

緑色と青色の2つのサブサーフェスを作成して、親ウィンドウに配置しています。

左クリックで、緑色 (上側) のサブサーフェスを、親の背面に移動します。
右クリックで、青色 (下側) のサブサーフェスの位置を移動します。
中ボタンクリックで、終了します。

<12-subsurface.c>
#include <stdio.h>
#include <string.h>
#include <linux/input-event-codes.h>

#include <wayland-client.h>
#include "xdg-shell-client-protocol.h"

#include "client.h"
#include "imagebuf.h"


typedef struct
{
    Surface sf;
    struct wl_subsurface *subsurface;
}subwindow;

typedef struct
{
    client b;

    struct wl_subcompositor *subcompositor;
    
    Toplevel *win;
    subwindow subwin[2];

    int enter_win;
}client_ex;


//=======================
// subwindow
//=======================


/* 破棄 */

static void subwindow_destroy(subwindow *p)
{
    wl_subsurface_destroy(p->subsurface);
    surface_destroy(&p->sf);
}

/* 作成 */

static void subwindow_create(client_ex *cl,subwindow *p,
    Toplevel *parent,int x,int y,int width,int height,uint32_t col)
{
    surface_create(&p->sf, CLIENT(cl), width, height);

    p->subsurface = wl_subcompositor_get_subsurface(cl->subcompositor,
        p->sf.surface, parent->sf.surface);

    wl_subsurface_set_position(p->subsurface, x, y);

    wl_surface_commit(parent->sf.surface);

    //サブサーフェイス更新

    imagebuf_fill(p->sf.img, col);

    surface_update(&p->sf);
}

/* wl_surface からウィンドウ番号取得 */

static int _get_window_no(client_ex *p,struct wl_surface *surface)
{
    int i;

    for(i = 0; i < 2; i++)
    {
        if(p->subwin[i].sf.surface == surface)
            break;
    }

    return (i == 2)? -1: i;
}


//=======================
// wl_pointer
//=======================


static void _pointer_enter(void *data, struct wl_pointer *pointer,
    uint32_t serial, struct wl_surface *surface, wl_fixed_t x, wl_fixed_t y)
{
    client_ex *p = (client_ex *)data;

    p->enter_win = _get_window_no(p, surface);

    printf("<enter> win:%d, (%d, %d)\n",
        p->enter_win, x >> 8, y >> 8);

    //カーソルセット
    client_set_cursor(CLIENT(p), serial);
}

static void _pointer_leave(void *data, struct wl_pointer *pointer,
    uint32_t serial, struct wl_surface *surface)
{
    printf("<leave> win:%d\n", ((client_ex *)data)->enter_win);
}

static void _pointer_motion(void *data, struct wl_pointer *pointer,
    uint32_t time, wl_fixed_t x, wl_fixed_t y)
{
    printf("<motion> win:%d, (%d, %d)\n",
        ((client_ex *)data)->enter_win, x >> 8, y >> 8);
}

static void _pointer_button(void *data, struct wl_pointer *pointer,
    uint32_t serial, uint32_t time, uint32_t button, uint32_t state)
{
    client_ex *p = (client_ex *)data;

    if(state != WL_POINTER_BUTTON_STATE_PRESSED)
        return;

    switch(button)
    {
        //左ボタンで、1番目のサブサーフェスを親の背面に
        case BTN_LEFT:
            wl_subsurface_place_below(p->subwin[0].subsurface, p->win->sf.surface);
            wl_surface_commit(p->win->sf.surface);
            break;
        //右ボタンで、2番目のサブサーフェスの位置を変更
        case BTN_RIGHT:
            wl_subsurface_set_position(p->subwin[1].subsurface, 50, 50);
            wl_surface_commit(p->win->sf.surface);
            break;
        //中ボタンで終了
        case BTN_MIDDLE:
            p->b.finish_loop = 1;
            break;
    }
}

static void _pointer_axis(void *data, struct wl_pointer *pointer,
    uint32_t time, uint32_t axis, wl_fixed_t value)
{
}

static void _pointer_frame(void *data, struct wl_pointer *pointer)
{
}

static void _pointer_axis_source(void *data, struct wl_pointer *pointer, uint32_t axis_source)
{
}

static void _pointer_axis_stop(void *data, struct wl_pointer *pointer, uint32_t time, uint32_t axis)
{
}

static void _pointer_axis_discrete(void *data, struct wl_pointer *pointer, uint32_t axis, int32_t discrete)
{
}

//ver 5
static const struct wl_pointer_listener g_pointer_listener = {
    _pointer_enter, _pointer_leave, _pointer_motion, _pointer_button,
    _pointer_axis, _pointer_frame, _pointer_axis_source,
    _pointer_axis_stop, _pointer_axis_discrete
};


//=======================


static void _client_registry_global(client *p,
    struct wl_registry *reg,uint32_t id,const char *name,uint32_t ver)
{
    if(strcmp(name, "wl_subcompositor") == 0)
        ((client_ex *)p)->subcompositor = wl_registry_bind(reg, id, &wl_subcompositor_interface, 1);
}

static void _client_destroy(client *cl)
{
    client_ex *p = (client_ex *)cl;
    int i;

    for(i = 0; i < 2; i++)
        subwindow_destroy(p->subwin + i);

    wl_subcompositor_destroy(p->subcompositor);
}

/* main */

int main(void)
{
    client_ex *p;
    Toplevel *win;

    p = (client_ex *)client_new(sizeof(client_ex));

    p->b.init_flags = CLIENT_INIT_FLAGS_SEAT | CLIENT_INIT_FLAGS_POINTER
        | CLIENT_INIT_FLAGS_CREATE_CURSOR;

    p->b.destroy = _client_destroy;
    p->b.registry_global = _client_registry_global;
    p->b.pointer_listener = &g_pointer_listener;

    client_init(CLIENT(p));

    //ウィンドウ

    win = toplevel_create(CLIENT(p), 300, 300, NULL);

    imagebuf_fill(win->sf.img, 0xffff0000);

    p->win = win;

    //サブウィンドウ

    subwindow_create(p, p->subwin, win, -10, -10, 200, 200, 0xff00ff00);
    subwindow_create(p, p->subwin + 1, win, 150, 150, 200, 200, 0xff0000ff);

    wl_surface_commit(win->sf.surface);

    //

    client_loop_simple(CLIENT(p));

    //解放

    toplevel_destroy(win);

    client_destroy(CLIENT(p));

    return 0;
}
サブサーフェス
まずは、wl_subcompositor をバインドします。
2025年2月時点での最大バージョンは「1」です。
wl_subcompositor
//破棄

void wl_subcompositor_destroy(struct wl_subcompositor *wl_subcompositor);

//wl_subsurface の作成

struct wl_subsurface *wl_subcompositor_get_subsurface(
    struct wl_subcompositor *wl_subcompositor,
    struct wl_surface *surface, struct wl_surface *parent);

破棄
wl_subcompositor_destroy() の場合は、他の release 関数と同じで、直接破棄するのではなく、クライアントが今後このオブジェクトを使用しないことを通知します。
この wl_subcompositor によって作成された wl_subsurface には影響しません。

wl_subsurface 作成
wl_subcompositor_get_subsurface() で、作成済みの wl_surface をサブサーフェスにして、wl_subsurface を作成します。
この時、parent 引数で、親となる wl_surface を指定します。

※親にサブサーフェスを追加する操作は、親のサーフェスで wl_surface_commit() が実行された時に適用されます。
サブサーフェスについて
サブサーフェスを扱う場合、トップレベルウィンドウの場合と同じように、
「wl_surface, wl_subsurface, wl_buffer のイメージ」が必要になります。

サブサーフェスの内容は wl_surface で更新し、サブサーフェスに固有の操作は wl_subsurface で行います。

なお、サブサーフェスの状態の変更 (wl_subsurface の関数で設定したもの) は、基本的に、親サーフェスで wl_surface_commit() が実行された時に適用されます。

これはサブサーフェスの同期モードに関わってきますが、デフォルトでは親と同期するモードになっているので、サブサーフェスの状態変更後は、親サーフェスで commit する必要があることを覚えておいてください。

  • サブサーフェスは、親サーフェスの領域でクリッピングされません。
    位置やサイズによって、親サーフェスを超える部分は、そのままはみ出して表示されます。
    これは、ウィンドウ装飾や影などを付ける際に有効となります。
  • サブサーフェスは、wl_buffer のイメージがセットされて、親が表示された時に、表示されます。
    親が非表示になるか、wl_buffer = NULL がセットされた時に非表示になります。
  • サーフェスはツリー構造になっているので、親の状態が子孫に適用されます。
  • サブサーフェスは、キーボードフォーカスを持てません。
  • wl_surface_offset() によるオフセット位置は無視されます。
  • サブサーフェスが親に追加された時、最初は兄弟の最上位に表示されます。
wl_subsurface
破棄
void wl_subsurface_destroy(struct wl_subsurface *wl_subsurface);

親への関連付けは削除され、サブサーフェスは直ちに非表示になります。
位置のセット
void wl_subsurface_set_position(struct wl_subsurface *wl_subsurface, int32_t x, int32_t y);

親の座標で、サブサーフェスの位置を指定します。
負の値も有効です。
表示順の変更
//sibling の上に
void wl_subsurface_place_above(struct wl_subsurface *wl_subsurface, struct wl_surface *sibling);

//sibling の下に
void wl_subsurface_place_below(struct wl_subsurface *wl_subsurface, struct wl_surface *sibling);

サブサーフェイスの表示順を変更します。

sibling は、兄弟のサブサーフェスまたは親サーフェスを指定します。

wl_subsurface_place_below() で、sibling に親サーフェスを指定すると、親ウィンドウの一つ下に表示することができるので、サブサーフェスでウィンドウ装飾用のサーフェスを作ることが可能になります。
同期モード
void wl_subsurface_set_sync(struct wl_subsurface *wl_subsurface);
void wl_subsurface_set_desync(struct wl_subsurface *wl_subsurface);

サブサーフェスの commit 時の同期モードを変更します。

set_sync の場合は同期モードとなり、サブサーフェスの状態は、親サーフェスが commit された時に適用されます。

set_desync の場合は非同期モードとなり、サブサーフェスの状態は、サブサーフェスで個別に commit された時に適用されます。
親サーフェスで commit しても、サブサーフェスには影響しません。

デフォルトは同期モードです。
ツリー構造によって、親の同期モードが適用されます。
プログラムの解説
今回の場合は、2つのサブサーフェスを作成しています。

緑のサブサーフェスは負の位置に指定しているので、親サーフェスの左上側にはみ出しています。
青のサブサーフェスは、親サーフェスの右下側にはみ出しています。

新しく追加されたサブサーフェスは、親または元々の兄弟よりも前面に表示されるので、「親→緑→青」の順に表示されています。

左クリックすると、wl_subsurface_place_below() で、緑のサブサーフェスを親の下に表示します。
wl_pointer のイベント
wl_pointer のイベントは、各サーフェスごとに送られてきます。

まず、enter イベントが来た時に、ポインタがどのサーフェス上にあるかを記録しておきます。
motion イベントでは surface 引数がないので、このイベント単体では、どのサーフェス上のイベントなのか判断できません。

各サーフェス上でポインタを移動した時の座標値を確認してください。
win は、-1 で親、0 で緑、1 で青のサーフェスです。