[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;
}
网友评论