xdg-shell : ポップアップウィンドウ

xdg-shell ポップアップウィンドウ
xdg-shell (stable) で、xdg_popup を使って、ポップアップウィンドウを表示してみます。

$ cc -o test p01_xdgpopup.c client.c client_xdg.c imagebuf.c \
 xdg-shell-protocol.c -lwayland-client -lwayland-cursor -lrt

  • xdg-shell-client-protocol.h、xdg-shell-protocol.c が必要です。
  • client_xdg.c に、xdg-shell 用のクライアント処理をまとめています。
  • client_xdg.c でカーソル処理をしているので、libwayland-client をリンクしてください。

p01_xdgpopup.c
/******************************
 * xdg-shell ポップアップウィンドウ
 *
 * - 左ボタンドラッグで、ウィンドウ位置移動。
 * - 右ボタンクリックで、ポップアップウィンドウ表示。
 *   (クライアントウィンドウ外をクリック、または
 *    ポップアップウィンドウ内を右クリックで閉じる)
 * - 中ボタンで終了。
 ******************************/

#include <stdio.h>
#include <stdlib.h>
#include <linux/input.h>

#include <wayland-client.h>

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


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

//ポップアップウィンドウ

typedef struct
{
    ClientXdg *client;
    struct wl_surface *surface;
    struct xdg_surface *xdg_surface;
    struct xdg_popup *xdg_popup;

    ImageBuf *img;
    WindowXdg *parent;
    int config_w,
        config_h;
    uint8_t flag_configure;
}PopupWindow;


void PopupWindow_destroy(PopupWindow *p);
void PopupWindow_resize(PopupWindow *p,int width,int height);
void PopupWindow_update(PopupWindow *p);

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

//トップレベルウィンドウ
WindowXdg *g_wintop;
//表示中のポップアップウィンドウ
PopupWindow *g_winpop = NULL;
//現在 enter 状態のサーフェス
struct wl_surface *g_on_surface = NULL;
//ポインタ位置
wl_fixed_t g_curx,g_cury;

#define ANCHOR_X  100
#define ANCHOR_Y  100
#define ANCHOR_W  100
#define ANCHOR_H  80

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



//-------------------------
// xdg_surface (popup)
//-------------------------


/* 状態変更が確定 */

static void _xdg_surface_configure(
    void *data,struct xdg_surface *surface,uint32_t serial)
{
    PopupWindow *p = (PopupWindow *)data;

    printf("[popup] xdg_surface # configure | surface:%p\n", surface);

    xdg_surface_ack_configure(surface, serial);

    //ポップアップ時は xdg_popup:configure で渡されたサイズにする

    PopupWindow_resize(p, p->config_w, p->config_h);

    ImageBuf_fill(p->img, 0xff00ff00);

    //最初の configure イベント時に更新する

    if(p->flag_configure == 0)
    {
        p->flag_configure = 1;

        PopupWindow_update(p);
    }
}

static const struct xdg_surface_listener g_xdg_surface_listener = {
    _xdg_surface_configure
};


//-------------------------
// xdg_popup
//-------------------------


/* 位置やサイズ変更時
 *
 * 設定した位置やサイズがサーバーによって調整される場合がある */

static void _popup_configure(void *data, struct xdg_popup *popup,
    int32_t x, int32_t y, int32_t width, int32_t height)
{
    PopupWindow *p = (PopupWindow *)data;

    printf("xdg_popup # configure | popup:%p, (%d,%d)-(%dx%d)\n",
        popup, x, y, width, height);

    p->config_w = width;
    p->config_h = height;
}

/* ポップアップ終了時 */

static void _popup_done(void *data, struct xdg_popup *popup)
{
    PopupWindow *p = (PopupWindow *)data;

    printf("xdg_popup # done | popup:%p\n", popup);

    //

    PopupWindow_destroy(p);

    g_winpop = NULL;
}

static const struct xdg_popup_listener g_popup_listener = {
    _popup_configure, _popup_done
};


//----------------------
// PopupWindow
//----------------------


/* ポップアップ用 xdg_positioner 作成 */

static struct xdg_positioner *_create_popup_positioner(
    PopupWindow *p,int w,int h)
{
    struct xdg_positioner *xdgpos;

    //xdg_positioner 作成

    xdgpos = xdg_wm_base_create_positioner(p->client->xdg.xdg_wm_base);

    //アンカー矩形

    xdg_positioner_set_anchor_rect(xdgpos,
        ANCHOR_X, ANCHOR_Y, ANCHOR_W, ANCHOR_H);

    //ウィンドウサイズ

    xdg_positioner_set_size(xdgpos, w, h);

    //位置をずらす

#if 0
    xdg_positioner_set_offset(xdgpos, 10, 10);
#endif

    //配置

    xdg_positioner_set_anchor(xdgpos, XDG_POSITIONER_ANCHOR_TOP_RIGHT);
    xdg_positioner_set_gravity(xdgpos, XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT);

    //サーバー側で位置やサイズを調整できるようにする

    xdg_positioner_set_constraint_adjustment(xdgpos,
        XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X
        | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y
        | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X
        | XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y);

    return xdgpos;
}

/* ポップアップウィンドウ作成 */

PopupWindow *PopupWindow_create(ClientXdg *cl,WindowXdg *parent,
    int x,int y,int width,int height,uint32_t serial)
{
    PopupWindow *p;
    struct xdg_positioner *xdgpos;

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

    p->client = cl;
    p->parent = parent;

    //wl_surface

    p->surface = wl_compositor_create_surface(cl->b.compositor);

    //xdg_surface

    p->xdg_surface = xdg_wm_base_get_xdg_surface(cl->xdg.xdg_wm_base, p->surface);

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

    //xdg_positioner 作成

    xdgpos = _create_popup_positioner(p, width, height);

    //xdg_popup 作成

    p->xdg_popup = xdg_surface_get_popup(p->xdg_surface,
        parent->xdg_surface, xdgpos);

    xdg_positioner_destroy(xdgpos);

    xdg_popup_add_listener(p->xdg_popup, &g_popup_listener, p);

    //グラブ

    xdg_popup_grab(p->xdg_popup, p->client->b.seat, serial);

    //適用

    wl_surface_commit(p->surface);

    return p;
}

/* ウィンドウ破棄 */

void PopupWindow_destroy(PopupWindow *p)
{
    if(p)
    {
        ImageBuf_destroy(p->img);

        xdg_popup_destroy(p->xdg_popup);

        xdg_surface_destroy(p->xdg_surface);

        wl_surface_destroy(p->surface);
    
        free(p);
    }
}

/* リサイズ */

void PopupWindow_resize(PopupWindow *p,int width,int height)
{
    ImageBuf_destroy(p->img);

    p->img = ImageBuf_create(p->client->b.shm, width, height);
}

/* ウィンドウ更新 */

void PopupWindow_update(PopupWindow *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);
}


//----------------------
// wl_pointer
//----------------------


/* enter */

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)
{
    printf("wl_pointer # enter | surface:%p\n", surface);

    g_curx = x;
    g_cury = y;
    g_on_surface = surface;

    ClientXdg_setCursor(CLIENT_XDG(data), serial);
}

static void _pointer_leave(void *data, struct wl_pointer *pointer,
    uint32_t serial, struct wl_surface *surface)
{
    printf("wl_pointer # leave | surface:%p\n", surface);

    g_on_surface = NULL;
}

static void _pointer_motion(void *data, struct wl_pointer *pointer,
    uint32_t time, wl_fixed_t x, wl_fixed_t y)
{
    printf("wl_pointer # motion | (%d,%d)\n", x >> 8, y >> 8);

    g_curx = x;
    g_cury = y;
}

/* ボタン */

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

    printf("wl_pointer # button | %s, serial:%u\n",
        (state == WL_POINTER_BUTTON_STATE_PRESSED)? "press":"release",
        serial);

    if(state != WL_POINTER_BUTTON_STATE_PRESSED)
        return;

    //トップレベル上

    if(g_on_surface == g_wintop->surface
        && !g_winpop)
    {
        switch(button)
        {
            //左ボタンでウィンドウ位置移動
            case BTN_LEFT:
                xdg_toplevel_move(g_wintop->xdg_toplevel, p->b.seat, serial);
                break;
            //右ボタンでポップアップ表示
            case BTN_RIGHT:
                printf("-- create popup | (%d,%d)\n", g_curx >> 8, g_cury >> 8);

                g_winpop = PopupWindow_create(p, g_wintop,
                    g_curx >> 8, g_cury >> 8, 200, 300, serial);
                break;
            //中ボタンで終了
            case BTN_MIDDLE:
                p->b.finish_loop = 1;
                break;
        }
    }

    //ポップアップ上

    if(g_winpop && g_on_surface == g_winpop->surface)
    {
        //右ボタンで閉じる
        
        if(button == BTN_RIGHT)
        {
            PopupWindow_destroy(g_winpop);
            g_winpop = NULL;
        }
    }
}

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

}

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


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


int main(void)
{
    ClientXdg *p;
    WindowXdg *win;

    p = ClientXdg_new(0);

    p->b.init_flags = INIT_FLAGS_SEAT | INIT_FLAGS_POINTER;
    p->b.pointer_listener = &g_pointer_listener;

    ClientXdg_init(p, NULL);

    //ウィンドウ

    g_wintop = win = WindowXdg_createToplevel(p, 256, 256);

    ImageBuf_fill(win->img, 0xffff0000);
    ImageBuf_box(win->img, ANCHOR_X, ANCHOR_Y, ANCHOR_W, ANCHOR_H, 0xff000000);

    //イベントループ

    Client_loop_simple(CLIENT(p));

    //解放

    WindowXdg_destroy(win);

    ClientXdg_destroy(p);

    return 0;
}

操作方法
  • 左ボタンドラッグで、ウィンドウ位置を移動します。
  • 右ボタンクリックで、ポップアップウィンドウを表示します。
  • ポップアップウィンドウ内で右クリックすると、ポップアップウィンドウを閉じます。
  • トップレベルウィンドウを中ボタン押しで、終了します。

赤いウィンドウの黒い枠をアンカー矩形として、右上の位置に合わせてポップアップウィンドウを表示しています。

ウィンドウを画面端に移動させた後、ポップアップウィンドウを表示してみると、サーバーによるウィンドウ位置の調整が確認できます。
ポップアップウィンドウの作成と処理
xdg_popup を使うと、ポップアップウィンドウや、ツールチップなどを表示できます。

トップレベルウィンドウと異なる点は、親ウィンドウを元に表示位置を指定できることと、表示した後は、ウィンドウを削除する以外の機能はないということです。

作成手順は以下の通りです。

  1. wl_surface を作成。
  2. xdg_surface を作成。
  3. 親ウィンドウに配置するための xdg_positioner を作成。
  4. xdg_positioner を指定して、xdg_popup を作成。
  5. xdg_positioner を破棄。
  6. xdg_popup にハンドラを設定。
  7. グラブする場合は、xdg_popup_grab() を実行する。
    (ツールチップなどでは必要ない)
  8. wl_surface_commit() でこれらを適用。

その後は、以下のように処理を行っていきます。

  • ポップアップウィンドウが表示される前に xdg_popup:configure イベントが来るので、渡されたウィンドウサイズを保持しておく。
    ここでは、サーバーによって調整された、最終的な位置やウィンドウサイズが渡される。
  • 位置とサイズが確定したら、xdg_surface:configure イベントが来るので、xdg_surface_ack_configure() を実行した後、サーバーによって提示されたサイズを元にウィンドウサイズを決めて、イメージバッファの作成や更新処理を行う。
  • ポップアップが終了した時に xdg_popup:popup_done イベントが来るので、ウィンドウの破棄などの処理を行う。
xdg_positioner
xdg_popup を作成する前に、xdg_positioner が必要になるので、まずはそちらを作成しておきます。

xdg_positioner は、子ウィンドウを親ウィンドウ内に配置するために、ウィンドウの位置やサイズ、配置の情報などを設定するためのものです。

作成
## 作成

struct xdg_positioner *xdg_wm_base_create_positioner(
    struct xdg_wm_base *xdg_wm_base);

## 破棄

void xdg_positioner_destroy(struct xdg_positioner *xdg_positioner);

xdg_wm_base から、xdg_positioner を作成します。

ウィンドウサイズの設定
void xdg_positioner_set_size(struct xdg_positioner *xdg_positioner,
    int32_t width, int32_t height);

子ウィンドウのサイズを設定します。

このサイズは、positioner の設定によって、サーバー側で調整される場合があります。

アンカー矩形の設定
void xdg_positioner_set_anchor_rect(
    struct xdg_positioner *xdg_positioner,
    int32_t x, int32_t y, int32_t width, int32_t height);

アンカー矩形は、親ウィンドウの座標で指定します。

※ xdg_popup では、クライアント内のウィンドウを親ウィンドウとして指定する必要があるため、デスクトップ画面の任意の位置に表示することはできません。

アンカー矩形は、子ウィンドウを配置する時の基準となる矩形です。
この矩形の任意の辺に合わせる形で、ウィンドウを配置できます。

例えば、親ウィンドウ内にボタンがあって、その形に合わせてポップアップウィンドウを表示したい場合、親ウィンドウ内でのボタンの位置とサイズを指定します。

アンカー矩形の辺を設定
void xdg_positioner_set_anchor(
    struct xdg_positioner *xdg_positioner, uint32_t anchor);

子ウィンドウを配置する時に、アンカー矩形のどの辺を基準にするかを指定します。

以下の値を一つ指定します。

XDG_POSITIONER_ANCHOR_NONE = 0
XDG_POSITIONER_ANCHOR_TOP = 1
XDG_POSITIONER_ANCHOR_BOTTOM = 2
XDG_POSITIONER_ANCHOR_LEFT = 3
XDG_POSITIONER_ANCHOR_RIGHT = 4
XDG_POSITIONER_ANCHOR_TOP_LEFT = 5
XDG_POSITIONER_ANCHOR_BOTTOM_LEFT = 6
XDG_POSITIONER_ANCHOR_TOP_RIGHT = 7
XDG_POSITIONER_ANCHOR_BOTTOM_RIGHT = 8

NONE の場合は、矩形の中央に配置されます。
TOP, BOTTOM の場合、X 軸は中央に配置されます。
LEFT, RIGHT の場合、Y 軸は中央に配置されます。

TOP_RIGHT に設定した場合、アンカー矩形の右上の位置を基準にして配置されることになります。

ただし、後述する設定で、位置の反転を許可した場合は、サーバーの調整によって、反対側の辺に合わせて配置される場合があります。

配置方向を設定
void xdg_positioner_set_gravity(
    struct xdg_positioner *xdg_positioner, uint32_t gravity);

子ウィンドウの配置方向を指定します。

XDG_POSITIONER_GRAVITY_NONE = 0
XDG_POSITIONER_GRAVITY_TOP = 1
XDG_POSITIONER_GRAVITY_BOTTOM = 2
XDG_POSITIONER_GRAVITY_LEFT = 3
XDG_POSITIONER_GRAVITY_RIGHT = 4
XDG_POSITIONER_GRAVITY_TOP_LEFT = 5
XDG_POSITIONER_GRAVITY_BOTTOM_LEFT = 6
XDG_POSITIONER_GRAVITY_TOP_RIGHT = 7
XDG_POSITIONER_GRAVITY_BOTTOM_RIGHT = 8

指定されていない軸は、中央揃えされます。

BOTTOM_RIGHT であれば、xdg_positioner_set_anchor() で指定した位置から、右下方向に配置します。

サーバーによる調整を設定
void xdg_positioner_set_constraint_adjustment(
    struct xdg_positioner *xdg_positioner,
    uint32_t constraint_adjustment);

設定した位置やサイズで子ウィンドウを配置した時に、子ウィンドウが画面をはみ出したりする場合、サーバー側で位置やサイズを調整させるように指定することができます。

値はフラグになっているので、複数指定してください。

XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_NONE = 0
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_X = 1
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_SLIDE_Y = 2
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_X = 4
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_FLIP_Y = 8
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_X = 16
XDG_POSITIONER_CONSTRAINT_ADJUSTMENT_RESIZE_Y = 32

デフォルトは NONE です。
指定されていない軸は、調整を許可しないことになります。
同じ軸で複数指定されている場合は、優先順により適切なものが一つ使われます。

SLIDE位置を移動します。
画面端をはみ出している場合は、画面内に収まるようにします。
FLIP位置を反転します。
アンカー矩形の反対側の辺に合わせて配置します。
RESIZEサイズを変更します。
幅や高さが画面サイズを超える場合、画面サイズに合わせます。

※ サーバーによって調整方法は異なります。
weston は現時点で調整に対応していませんが、GNOME ではきちんと調整されます。

オフセット位置を設定
void xdg_positioner_set_offset(
    struct xdg_positioner *xdg_positioner, int32_t x, int32_t y);

子ウィンドウの表示位置を、指定値分ずらします。
xdg_popup
作成
xdg_positioner が作成できたら、xdg_popup を作成します。

## 作成

struct xdg_popup *xdg_surface_get_popup(
    struct xdg_surface *xdg_surface,
    struct xdg_surface *parent, struct xdg_positioner *positioner);

## 破棄

void xdg_popup_destroy(struct xdg_popup *xdg_popup);

## ハンドラ設定

int xdg_popup_add_listener(struct xdg_popup *xdg_popup,
    const struct xdg_popup_listener *listener, void *data);

parent には、親ウィンドウを指定します。
xdg の トップレベルウィンドウか、ポップアップウィンドウを指定できます。

任意のタイミングでポップアップを閉じたい場合は、xdg_popup_destroy() で破棄してください。

また、作成後は、ハンドラ設定も行います。
グラブ
通常のポップアップウィンドウでは、ウィンドウ外がクリックされたらポップアップを終了させるようにするために、ポインタのグラブを行う必要があります。

xdg_popup のポップアップウィンドウ開始時は、明示的にグラブを行う必要があります。
(ツールチップウィンドウなど、表示するだけのポップアップであれば必要ありません)

void xdg_popup_grab(struct xdg_popup *xdg_popup,
    struct wl_seat *seat, uint32_t serial);

serial には、ポインタのボタン押しや、キー押し時のシリアル値を指定します。

グラブが行えなかった場合などは、すぐに xdg_popup:popup_done イベントが来て、キャンセル扱いになります。

ポップアップ上でさらにポップアップを表示する場合は、ポップアップウィンドウごとに実行できます。
その場合は、最後に作成したポップアップから順に破棄する必要があります。

なお、グラブ中は、ポップアップウィンドウ上だけでなく、クライアントのすべてのウィンドウのポインタイベントが取得できるため、ポップアップ以外のクライアントのウィンドウがクリックされた時は、クライアント側でポップアップを閉じる処理を行う必要があります。

グラブ中に、クライアントのウィンドウ以外の部分がクリックされた時は、xdg_popup:popup_done イベントが来ます。
xdg_popup のイベント
void (*configure)(void *data, struct xdg_popup *xdg_popup,
    int32_t x, int32_t y, int32_t width, int32_t height);

void (*popup_done)(void *data, struct xdg_popup *xdg_popup);

configure
サーバーによって、ポップアップウィンドウの位置やサイズが提示された時に来ます。

xdg_positioner_set_constraint_adjustment() で、サーバーによる調整を有効にした場合は、ここで調整された値が送られてきます。

この時点ではまだ位置やサイズは確定ではないので、一旦値を保存しておきます。

popup_done
ポップアップ表示後、(グラブされている場合に) クライアントのウィンドウ以外の部分がクリックされた時など、ポップアップを終了するタイミングの時に来ます。

グラブ時に、サーバーによってグラブが拒否された場合などにも来ます。

明示的に xdg_popup_destroy() で破棄した時は来ません。
xdg_surface:configure イベント
ポップアップウィンドウの位置とサイズが確定して、表示できる状態になったら、xdg_surface:configure イベントが来ます。

xdg_popup:configure で渡された width, height の値でイメージバッファを作成し、ウィンドウ内容を更新します。