美文网首页
7. XDG shell 基础

7. XDG shell 基础

作者: 夕月风 | 来源:发表于2023-12-08 17:38 被阅读0次

    [TOC]
    XDG(跨桌面组)shell是Wayland的标准协议扩展,它描述了应用程序窗口的语义。它定义了两个wl_surface角色:“toplevel”,用于顶层应用程序窗口,以及“popup”,用于上下文菜单、下拉菜单、工具提示等顶级窗口的子项。将它们放在一起,您可以形成surface的树,顶级位于顶部,弹出窗口或额外的顶级位于叶子上。该协议还定义了一个定位器接口,用于帮助定位弹出窗口,并限制有关窗口周围事物的信息。

    xdg-shell作为协议扩展,不在wayland.xml中定义。相反,您可以在wayland-protocols软件包中找到它。它可能安装在系统上的/usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml之类的路径上。

    xdg_wm_base

    xdg_wm_base是规范中定义的唯一全局变量,它提供了创建所需的其他对象的请求。最基本的实现是从处理“ping”事件开始的,当合成器发送它时,您应该及时使用“pong”请求进行响应,以提示您没有陷入死锁。另一个请求处理定位器的创建,这是前面提到的对象,我们将在第10章中详细介绍它们。我们首先要查看的请求是get_xdg_surface。

    7.1 XDG surfaces

    在xdg-shell领域中的surface被称为xdg_surface,此接口带来了两种类型的XDG surface(顶级和弹出窗口)共同使用的一小部分功能。每种类型的XDG surface的语义仍然存在很大差异,因此必须通过额外的角色明确指定。

    xdg_surface接口提供了其他请求,用于分配更具体的顶级和弹出窗口角色。一旦我们将对象绑定到xdg_wm_base全局,我们就可以使用get_xdg_surface请求获取一个wl_surface的id。

    <request name="get_xdg_surface">
      <arg name="id" type="new_id" interface="xdg_surface"/>
      <arg name="surface" type="object" interface="wl_surface"/>
    </request>
    

    xdg_surface接口除了包含请求为surface分配更具体的顶级或弹出窗口角色外,还包括一些对两种角色都重要的功能。在继续学习顶级和弹出窗口特定的语义之前,让我们回顾一下这些功能。

    <event name="configure">
      <arg name="serial" type="uint" summary="serial of the configure event"/>
    </event>
    
    <request name="ack_configure">
      <arg name="serial" type="uint" summary="the serial from the configure event"/>
    </request>
    

    xdg_surface最重要的API是一对configure和ack_configure。您可能还记得Wayland的目标是使每一帧都完美无缺。这意味着不会显示一半应用状态更改的帧,为了实现这一点,必须在客户端和服务器之间同步这些更改。对于XDG surface,这对消息是支持此目标的机制。

    现在我们只介绍基础知识,因此我们将总结这两个事件的重要性如下:来自服务器的事件会通知您surface的配置(或重新配置),并将其应用于挂起状态。当配置事件到达时,应用挂起的更改,使用ack_configure来确认您已完成操作,并呈现和提交新的一帧。我们将在下一章中展示这一点,并在第8.1章中详细解释。

    <request name="set_window_geometry">
      <arg name="x" type="int"/>
      <arg name="y" type="int"/>
      <arg name="width" type="int"/>
      <arg name="height" type="int"/>
    </request>
    

    set_window_geometry请求主要用于使用客户端装饰的应用程序,以区分其surface中被视为窗口的一部分和不被视为窗口的部分。最常见的是,这用于排除被认为是窗口一部分的客户端阴影。合成器可以使用此信息来管理自己的行为,以安排和与窗口进行交互。

    7.2 应用窗口

    We have shaved many yaks to get here(俚语),但现在是时候了:XDG顶级层是用于显示应用程序窗口的接口。 XDG顶级层接口包含许多请求和事件,用于管理应用程序窗口,包括处理最小化和最大化状态、设置窗口标题等。 我们将在以后的章节中详细讨论它的每个部分,所以现在只关心基础知识。

    根据上一章的知识,我们知道可以从wl_surface获取xdg_surface,但这只是第一步:将surface带入XDG shell的怀抱。 下一步是将该XDG surface变成XDG顶级层-“顶级”应用程序窗口,之所以这样命名,是因为它在最终使用XDG shell创建的窗口和弹出菜单层次结构中的顶级位置。 要创建其中一个,我们可以使用xdg_surface接口的适当请求:

    <request name="get_toplevel">
      <arg name="id" type="new_id" interface="xdg_toplevel"/>
    </request>
    

    这个新的xdg_toplevel接口为我们提供了许多请求和事件,用于管理应用程序窗口的生命周期。第10章深入探讨了这些,但我知道你迫不及待地想在屏幕上看到一些东西。如果你按照以下步骤操作,处理前一章讨论的XDG surface的configure和ack_configure装备,并将wl_buffer附加到我们的wl_surface并提交,应用程序窗口将出现并将缓冲区内容呈现给用户。下一章提供了示例代码,实现了上述操作。此外,它还利用了我们尚未介绍的一个额外的XDG顶级请求:

    <request name="set_title">
      <arg name="title" type="string"/>
    </request>
    

    这应该是相当显而易见的。还有一个类似的,我们没有在示例代码中使用,但可能适合你的应用程序:

    <request name="set_app_id">
      <arg name="app_id" type="string"/>
    </request>
    

    标题通常显示在窗口装饰、任务栏等中,而应用程序ID用于标识你的应用程序或将你的窗口组合在一起。你可以通过将窗口标题设置为“应用程序窗口-Wayland协议-Firefox”,并将应用程序ID设置为“firefox”来利用这些功能。

    总之,以下步骤将帮助你从零开始在屏幕上创建一个窗口:

    • 绑定到wl_compositor并使用它创建一个wl_surface。
    • 绑定到xdg_wm_base并使用它创建一个带有你的wl_surface的xdg_surface。
    • 从xdg_surface使用xdg_surface.get_toplevel创建一个xdg_顶级层。
    • 为xdg_surface配置一个监听器并等待configure事件。
    • 绑定到你选择的缓冲区分配机制(例如wl_shm)并分配一个共享缓冲区,然后将你* 的内容渲染到其中。
    • 使用wl_surface.attach将wl_buffer附加到wl_surface。
    • 使用xdg_surface.ack_configure,传递来自configure的序列号,确认你已经准备好了一帧合适的画面。
    • 发送一个wl_surface.commit请求。

    翻到下一页以查看这些步骤的演示。

    7.3 扩展示例代码

    使用到目前为止我们所学的知识,我们现在可以编写一个Wayland客户端,以便在屏幕上显示一些内容。以下代码是一个完整的Wayland应用程序,它打开一个XDG顶级窗口,并在其上显示一个640x480的方块网格。像这样编译它:

    wayland-scanner private-code \
      < /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml \
      > xdg-shell-protocol.c
    wayland-scanner client-header \
      < /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml \
      > xdg-shell-client-protocol.h
    cc -o client client.c xdg-shell-protocol.c -lwayland-client -lrt
    

    然后运行./client以查看其运行情况,或使用WAYLAND_DEBUG=1 ./client以包含大量有用的调试信息。Tada!在以后的章节中,我们将在此基础上构建客户端,因此请将此代码保存在安全的地方。

    #define _POSIX_C_SOURCE 200112L
    #include <errno.h>
    #include <fcntl.h>
    #include <limits.h>
    #include <stdbool.h>
    #include <string.h>
    #include <sys/mman.h>
    #include <time.h>
    #include <unistd.h>
    #include <wayland-client.h>
    #include "xdg-shell-client-protocol.h"
    
    /* Shared memory support code */
    static void
    randname(char *buf)
    {
        struct timespec ts;
        clock_gettime(CLOCK_REALTIME, &ts);
        long r = ts.tv_nsec;
        for (int i = 0; i < 6; ++i) {
            buf[i] = 'A'+(r&15)+(r&16)*2;
            r >>= 5;
        }
    }
    
    static int
    create_shm_file(void)
    {
        int retries = 100;
        do {
            char name[] = "/wl_shm-XXXXXX";
            randname(name + sizeof(name) - 7);
            --retries;
            int fd = shm_open(name, O_RDWR | O_CREAT | O_EXCL, 0600);
            if (fd >= 0) {
                shm_unlink(name);
                return fd;
            }
        } while (retries > 0 && errno == EEXIST);
        return -1;
    }
    
    static int
    allocate_shm_file(size_t size)
    {
        int fd = create_shm_file();
        if (fd < 0)
            return -1;
        int ret;
        do {
            ret = ftruncate(fd, size);
        } while (ret < 0 && errno == EINTR);
        if (ret < 0) {
            close(fd);
            return -1;
        }
        return fd;
    }
    
    /* Wayland code */
    struct client_state {
        /* Globals */
        struct wl_display *wl_display;
        struct wl_registry *wl_registry;
        struct wl_shm *wl_shm;
        struct wl_compositor *wl_compositor;
        struct xdg_wm_base *xdg_wm_base;
        /* Objects */
        struct wl_surface *wl_surface;
        struct xdg_surface *xdg_surface;
        struct xdg_toplevel *xdg_toplevel;
    };
    
    static void
    wl_buffer_release(void *data, struct wl_buffer *wl_buffer)
    {
        /* Sent by the compositor when it's no longer using this buffer */
        wl_buffer_destroy(wl_buffer);
    }
    
    static const struct wl_buffer_listener wl_buffer_listener = {
        .release = wl_buffer_release,
    };
    
    static struct wl_buffer *
    draw_frame(struct client_state *state)
    {
        const int width = 640, height = 480;
        int stride = width * 4;
        int size = stride * height;
    
        int fd = allocate_shm_file(size);
        if (fd == -1) {
            return NULL;
        }
    
        uint32_t *data = mmap(NULL, size,
                PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        if (data == MAP_FAILED) {
            close(fd);
            return NULL;
        }
    
        struct wl_shm_pool *pool = wl_shm_create_pool(state->wl_shm, fd, size);
        struct wl_buffer *buffer = wl_shm_pool_create_buffer(pool, 0,
                width, height, stride, WL_SHM_FORMAT_XRGB8888);
        wl_shm_pool_destroy(pool);
        close(fd);
    
        /* Draw checkerboxed background */
        for (int y = 0; y < height; ++y) {
            for (int x = 0; x < width; ++x) {
                if ((x + y / 8 * 8) % 16 < 8)
                    data[y * width + x] = 0xFF666666;
                else
                    data[y * width + x] = 0xFFEEEEEE;
            }
        }
    
        munmap(data, size);
        wl_buffer_add_listener(buffer, &wl_buffer_listener, NULL);
        return buffer;
    }
    
    static void
    xdg_surface_configure(void *data,
            struct xdg_surface *xdg_surface, uint32_t serial)
    {
        struct client_state *state = data;
        xdg_surface_ack_configure(xdg_surface, serial);
    
        struct wl_buffer *buffer = draw_frame(state);
        wl_surface_attach(state->wl_surface, buffer, 0, 0);
        wl_surface_commit(state->wl_surface);
    }
    
    static const struct xdg_surface_listener xdg_surface_listener = {
        .configure = xdg_surface_configure,
    };
    
    static void
    xdg_wm_base_ping(void *data, struct xdg_wm_base *xdg_wm_base, uint32_t serial)
    {
        xdg_wm_base_pong(xdg_wm_base, serial);
    }
    
    static const struct xdg_wm_base_listener xdg_wm_base_listener = {
        .ping = xdg_wm_base_ping,
    };
    
    static void
    registry_global(void *data, struct wl_registry *wl_registry,
            uint32_t name, const char *interface, uint32_t version)
    {
        struct client_state *state = data;
        if (strcmp(interface, wl_shm_interface.name) == 0) {
            state->wl_shm = wl_registry_bind(
                    wl_registry, name, &wl_shm_interface, 1);
        } else if (strcmp(interface, wl_compositor_interface.name) == 0) {
            state->wl_compositor = wl_registry_bind(
                    wl_registry, name, &wl_compositor_interface, 4);
        } else if (strcmp(interface, xdg_wm_base_interface.name) == 0) {
            state->xdg_wm_base = wl_registry_bind(
                    wl_registry, name, &xdg_wm_base_interface, 1);
            xdg_wm_base_add_listener(state->xdg_wm_base,
                    &xdg_wm_base_listener, state);
        }
    }
    
    static void
    registry_global_remove(void *data,
            struct wl_registry *wl_registry, uint32_t name)
    {
        /* This space deliberately left blank */
    }
    
    static const struct wl_registry_listener wl_registry_listener = {
        .global = registry_global,
        .global_remove = registry_global_remove,
    };
    
    int
    main(int argc, char *argv[])
    {
        struct client_state state = { 0 };
        state.wl_display = wl_display_connect(NULL);
        state.wl_registry = wl_display_get_registry(state.wl_display);
        wl_registry_add_listener(state.wl_registry, &wl_registry_listener, &state);
        wl_display_roundtrip(state.wl_display);
    
        state.wl_surface = wl_compositor_create_surface(state.wl_compositor);
        state.xdg_surface = xdg_wm_base_get_xdg_surface(
                state.xdg_wm_base, state.wl_surface);
        xdg_surface_add_listener(state.xdg_surface, &xdg_surface_listener, &state);
        state.xdg_toplevel = xdg_surface_get_toplevel(state.xdg_surface);
        xdg_toplevel_set_title(state.xdg_toplevel, "Example client");
        wl_surface_commit(state.wl_surface);
    
        while (wl_display_dispatch(state.wl_display)) {
            /* This space deliberately left blank */
        }
    
        return 0;
    }
    

    相关文章

      网友评论

          本文标题:7. XDG shell 基础

          本文链接:https://www.haomeiwen.com/subject/iozegdtx.html