Wayland - ウィンドウの移動とリサイズ

ウィンドウの移動とリサイズ
ウィンドウの移動とリサイズを行います。
ウィンドウの端にポインタを乗せたらカーソルが変わり、ドラッグでリサイズが出来ます。
それ以外の部分でドラッグすると、ウィンドウ位置を移動できます。
中ボタン押しで終了します。

> common1.h
> common1.c

$ cc -o test 11_resize.c -lwayland-client -lwayland-cursor -lrt

<11_resize.c>
#include <stdio.h>
#include <string.h>
#include <linux/input.h>
#include <wayland-cursor.h>

#include "common1.h"

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

typedef struct
{
    wayland base;

    int quit_flag;
    window *win;

    //ポインタデータ
    uint32_t serial_enter;
    int lastx,lasty,
        last_area;

    //カーソルデータ
    struct wl_surface *cursor_surface;
    struct wl_cursor_theme *cursor_theme;
    int cursor_current;
}wayland_ex;

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

#define FRAME_WIDTH  5

enum
{
    AREA_MOVE = 0,

    //wl_shell_surface_resize() の edges に対応
    AREA_RESIZE_TOP = 1,
    AREA_RESIZE_BOTTOM = 2,
    AREA_RESIZE_LEFT = 4,
    AREA_RESIZE_RIGHT = 8,
    AREA_RESIZE_TOP_LEFT = AREA_RESIZE_TOP | AREA_RESIZE_LEFT, //5
    AREA_RESIZE_BOTTOM_LEFT = AREA_RESIZE_BOTTOM | AREA_RESIZE_LEFT, //6
    AREA_RESIZE_TOP_RIGHT = AREA_RESIZE_TOP | AREA_RESIZE_RIGHT, //9
    AREA_RESIZE_BOTTOM_RIGHT = AREA_RESIZE_BOTTOM | AREA_RESIZE_RIGHT //10
};

enum
{
    CURSOR_MOVE,
    CURSOR_RESIZE_LEFT,
    CURSOR_RESIZE_RIGHT,
    CURSOR_RESIZE_TOP,
    CURSOR_RESIZE_BOTTOM,
    CURSOR_RESIZE_TOP_LEFT,
    CURSOR_RESIZE_TOP_RIGHT,
    CURSOR_RESIZE_BOTTOM_LEFT,
    CURSOR_RESIZE_BOTTOM_RIGHT
};

static const char *g_cursor_name[] = {
    "move",
    "left_side", "right_side", "top_side", "bottom_side",
    "top_left_corner", "top_right_corner",
    "bottom_left_corner", "bottom_right_corner"
};

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


//---------- sub

/* カーソル変更 */

static void _set_cursor(wayland_ex *p,int no)
{
    struct wl_cursor *cursor;
    struct wl_cursor_image *img;
    struct wl_buffer *buffer;

    //現在と同じ画像ならそのまま

    if(p->cursor_current == no) return;

    p->cursor_current = no;

    printf("change cursor: '%s'\n", g_cursor_name[no]);

    //変更

    cursor = wl_cursor_theme_get_cursor(p->cursor_theme, g_cursor_name[no]);
    if(!cursor) return;

    img = cursor->images[0];

    buffer = wl_cursor_image_get_buffer(img);
    if(!buffer) return;

    wl_surface_attach(p->cursor_surface, buffer, 0, 0);
    wl_surface_damage(p->cursor_surface, 0, 0, img->width, img->height);
    wl_surface_commit(p->cursor_surface);

    wl_pointer_set_cursor(p->base.pointer, p->serial_enter, p->cursor_surface,
        img->hotspot_x, img->hotspot_y);
}

/* 指定位置がどの範囲内か */

static int _get_point_area(window *win,int x,int y)
{
    int f = 0;

    if(x < FRAME_WIDTH)
        f |= AREA_RESIZE_LEFT;
    else if(x >= win->width - FRAME_WIDTH)
        f |= AREA_RESIZE_RIGHT;
    
    if(y < FRAME_WIDTH)
        f |= AREA_RESIZE_TOP;
    else if(y >= win->height - FRAME_WIDTH)
        f |= AREA_RESIZE_BOTTOM;

    return f;
}

/* enter/motion 時 */

static void _enter_motion(wayland_ex *p,wl_fixed_t x,wl_fixed_t y)
{
    int area;
    int area_to_cursor[] = {
        CURSOR_MOVE, CURSOR_RESIZE_TOP, CURSOR_RESIZE_BOTTOM, 0,
        CURSOR_RESIZE_LEFT, CURSOR_RESIZE_TOP_LEFT,
        CURSOR_RESIZE_BOTTOM_LEFT, 0, CURSOR_RESIZE_RIGHT,
        CURSOR_RESIZE_TOP_RIGHT, CURSOR_RESIZE_BOTTOM_RIGHT
    };

    x >>= 8;
    y >>= 8;

    area = _get_point_area(p->win, x, y);

    p->lastx = x;
    p->lasty = y;
    p->last_area = area;

    //カーソル変更

    _set_cursor(p, area_to_cursor[area]);
}

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

/* ウィンドウのサイズ変更が要求された時 */

static void _window_configure(window *p,int width,int height)
{
    printf("configure: %dx%d\n", width, height);

    if(width < 50) width = 50;
    if(height < 50) height = 50;

    if(width != p->width || height != p->height)
    {
        p->width = width;
        p->height = height;

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

        imagebuf_fill(p->img, 0xff0000);
        window_update(p);
    }
}


//-----------  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)
{
    wayland_ex *p = (wayland_ex *)data;

    printf("enter\n");

    _enter_motion(p, x, y);

    p->serial_enter = serial;
}

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

    //次の enter 時、常にカーソルセットさせる
    ((wayland_ex *)data)->cursor_current = -1;
}

static void _pointer_motion(void *data, struct wl_pointer *pointer,
    uint32_t time, wl_fixed_t x, wl_fixed_t y)
{
    _enter_motion((wayland_ex *)data, x, y);
}

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

    if(state == WL_POINTER_BUTTON_STATE_PRESSED)
    {
        if(button == BTN_MIDDLE)
            p->quit_flag = 1;
        else if(button == BTN_LEFT)
        {
            if(p->last_area == AREA_MOVE)
                //位置移動
                wl_shell_surface_move(p->win->shell_surface, p->base.seat, serial);
            else
            {
                //リサイズ
                wl_shell_surface_resize(p->win->shell_surface, p->base.seat,
                    serial, p->last_area);
            }
        }
    }
}

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)
{
    wayland_ex *wl;
    window *win;

    wl = (wayland_ex *)wayland_new(sizeof(wayland_ex));

    wl->base.init_flags = INIT_FLAGS_SEAT | INIT_FLAGS_POINTER;
    wl->base.pointer_listener = &g_pointer_listener;

    wayland_init(WAYLAND_PTR(wl));

    //カーソル

    wl->cursor_current = -1;

    wl->cursor_surface = wl_compositor_create_surface(wl->base.compositor);

    wl->cursor_theme = wl_cursor_theme_load(NULL, 32, wl->base.shm);

    //ウィンドウ

    wl->win = win = window_create(WAYLAND_PTR(wl), 256, 256);

    win->configure = _window_configure;

    imagebuf_fill(win->img, 0xff0000);
    window_update(win);

    //

    while(wl_display_dispatch(wl->base.display) != -1
        && wl->quit_flag == 0);

    //解放

    window_destroy(win);

    wl_cursor_theme_destroy(wl->cursor_theme);
    wl_surface_destroy(wl->cursor_surface);

    wayland_destroy(WAYLAND_PTR(wl));

    return 0;
}
解説
ウィンドウ枠やタイトルバーはサーバー側で作ってくれないので、自分で作成&処理する必要があります。
今回は、ウィンドウの移動とリサイズを行っていきます。
wl_pointer : enter/motion イベント
button イベント時は現在のカーソル位置が送られてこないので、最後に移動した位置として、enter と motion イベント時に、現在のカーソル位置を記録しておきます。
また、現在位置がどの範囲の上にあるかも判定して、記録しておきます。

カーソルが乗っている部分が変わった時は、カーソルを変更しています。
ウィンドウの端の場合は、8方向それぞれのカーソルに変更し、それ以外では移動用のカーソルに変更しています。
ただし、今回、カーソルのアニメーションは処理していません。
wl_pointer : button イベント
ウィンドウ内で左ボタンが押された時、ウィンドウの端ならリサイズ、それ以外はウィンドウ位置の移動を行います。

## 移動を開始

void wl_shell_surface_move(struct wl_shell_surface *wl_shell_surface,
    struct wl_seat *seat, uint32_t serial);

## リサイズを開始

void wl_shell_surface_resize(struct wl_shell_surface *wl_shell_surface,
    struct wl_seat *seat, uint32_t serial, uint32_t edges);

これらの関数を呼ぶと、サーバー側が、渡された wl_seat を使って、移動/リサイズの動作を行ってくれます。

ボタンが押されている間、カーソル形状はサーバー側で自動的に各動作の形に変更されるので、クライアント側で設定する必要はありません。
なお、これらの処理が開始された時は leave イベントが送信され、終わった時にポインタがウィンドウ上にあれば enter イベントが送信されます。

wl_shell_surface_resize()
edges は、enum wl_shell_surface_resize の列挙値です。
どの端を操作するかを指定します。

WL_SHELL_SURFACE_RESIZE_NONE = 0
WL_SHELL_SURFACE_RESIZE_TOP = 1
WL_SHELL_SURFACE_RESIZE_BOTTOM = 2
WL_SHELL_SURFACE_RESIZE_LEFT = 4
WL_SHELL_SURFACE_RESIZE_TOP_LEFT = 5
WL_SHELL_SURFACE_RESIZE_BOTTOM_LEFT = 6
WL_SHELL_SURFACE_RESIZE_RIGHT = 8
WL_SHELL_SURFACE_RESIZE_TOP_RIGHT = 9
WL_SHELL_SURFACE_RESIZE_BOTTOM_RIGHT = 10

値を見ればわかると思いますが、TOP, BOTTOM, LEFT, RIGHT がフラグの値になっていて、他は、TOP + LEFT というように、2つのフラグを組み合わせた値となっています。

なお、この関数だけでは、実際のウィンドウはリサイズされません。
リサイズ
wl_shell_surface_resize() のリサイズ動作では、実際にはウィンドウのサイズは変更されません。

サイズを変えるタイミングが来た時に、wl_shell_surface の configure イベントが送信され、クライアントに対してウィンドウサイズの変更が要求されます。
このイベントが来た時に、イメージバッファのサイズ変更 (再作成) をして、ウィンドウの再描画を行う必要があります。

サーバー側は、ポインタ移動によって変化するサイズの値を提示しているだけなので、実際にはクライアント側で好きなサイズに調整することができます。
例えば、ウィンドウの最小サイズを決めて、それ以下のサイズにはならないようにするなど。
今回の場合は、50x50 px を最小サイズとしています。

なお、ウィンドウ位置はサーバー側で調整してくれるので、クライアントはサイズを変更するだけで OK です。
リサイズの方向を意識する必要はありません。

weston (ver 3.0.0) 上で実行した場合、最初の左 or 上方向のリサイズ時だけ動作がおかしく、以降は問題なく動作しますが、GNOME 上だと特に問題はないので、おそらく weston 側のバグです。