Wayland Coding 速记-Staging
ssk-wh Lv4

因工作需要加之个人比较感兴趣的原因,在实现 Wayland 合成器相关协议之途中,随笔记录记录一些相关的基础知识。

Wayland

关于 wayland 的总结,个人觉得下面一段话挺好:

WaylandWayland is a replacement for the X11 window system protocol and architecture with the aim to be easier to develop, extend, and maintain.

WaylandWayland 是 X11 窗口系统协议和架构的替代品,旨在更易于开发、扩展和维护。

Wayland is the language (protocol) that applications can use to talk to a display server in order to make themselves visible and get input from the user (a person). A Wayland server is called a “compositor”. Applications are Wayland clients.

Wayland 是应用程序可用来与显示服务器对话的语言(协议),以便使自己可见并获取用户(人)的输入。 Wayland 服务器被称为“合成器”。应用程序是 Wayland 客户端。

Wayland also refers to a system architecture. It is not just a server-client relationship between a compositor and applications. There is no single common Wayland server like Xorg is for X11, but every graphical environment brings with it one of many compositor implementations. Window management and the end user experience are often tied to the compositor rather than swappable components.

Wayland 也指一种系统架构。它不仅仅是合成器和应用程序之间的服务器-客户端关系。没有像 Xorg 那样适用于 X11 的单一通用 Wayland 服务器,但每个图形环境都带来了许多合成器实现之一。窗口管理和最终用户体验通常与合成器而不是可交换组件相关。

A core part of Wayland architecture is libwayland: an inter-process communication library that translates a protocol definition in XML to a C language API. This library does not implement Wayland, it merely encodes and decodes Wayland messages. The actual implementations are in the various compositor and application toolkit projects.

Wayland 架构的核心部分是 libwayland:一个进程间通信库,它将 XML 中的协议定义转换为 C 语言 API。该库没有实现 Wayland,它只是对 Wayland 消息进行编码和解码。实际的实现是在各种合成器和应用程序工具包项目中。

Wayland does not restrict where and how it is used. A Wayland compositor could be a standalone display server running on Linux kernel modesetting and evdev input devices or on many other operating systems, or a nested compositor that itself is an X11 or Wayland application (client). Wayland can even be used in application-internal communication as is done in some web browsers.

Wayland 不限制其使用地点和方式。 Wayland 合成器可以是在 Linux 内核模式设置和 evdev 输入设备或许多其他操作系统上运行的独立显示服务器,也可以是本身就是 X11 或 Wayland 应用程序(客户端)的嵌套合成器。 Wayland 甚至可以用于应用程序内部通信,就像某些 Web 浏览器中所做的那样。

Part of the Wayland project is also the Weston reference implementation of a Wayland compositor. Weston can run as an X client or under Linux KMS and ships with a few demo clients. The Weston compositor is a minimal and fast compositor and is suitable for many embedded and mobile use cases.

Wayland 项目的一部分也是 Wayland 合成器的 Weston 参考实现。 Weston 可以作为 X 客户端或在 Linux KMS 下运行,并附带一些演示客户端。 Weston 合成器是一个最小且快速的合成器,适用于许多嵌入式和移动用例。

开发库

后续开发内容均基于 libwayland-dev 进行。

主要结构体

Struct Description Example
wl_display 代表一个 Wayland 显示服务器的连接。它用于管理客户端和服务器之间的通信和事件处理 struct wl_display *wl_display_create(void);
void wl_display_run(struct wl_display *display);
struct wl_list *wl_display_get_client_list(struct wl_display *display);
wl_global 用于描述全局对象。这些全局对象在 Wayland 显示服务器中注册,可以被客户端发现和使用。wl_global 对象通常表示服务器中提供的某些功能或接口,例如 compositor、shell、seat 等 struct wl_global *wl_global_create(struct wl_display *display, const struct wl_interface *interface, int version, void *data, wl_global_bind_func_t bind);
wl_event_loop 用于管理事件循环。在 Wayland 服务器中,事件循环用于处理来自客户端的请求、内部超时事件以及文件描述符上的事件。 struct wl_event_loop *wl_event_loop_create(void);
void wl_event_loop_destroy(struct wl_event_loop *loop);
wl_event_source 用于描述一个事件源。在 Wayland 服务器的事件循环中,事件源可以是文件描述符事件、定时器事件或信号事件。 struct wl_event_source *wl_event_loop_add_fd(struct wl_event_loop *loop, int fd, uint32_t mask, wl_event_loop_fd_func_t func, void *data);
wl_interface 用于描述 Wayland 协议中的接口。接口是 Wayland 协议的基本构建块,定义了客户端和服务器之间可以进行的交互。每个接口包括一组方法(requests)和事件(events)。 struct wl_interface {
const char *name;
int version;
int method_count;
const struct wl_message *methods;
int event_count;
const struct wl_message *events;
};
wl_message 用于描述接口中的每个方法和事件。 static const struct wl_message my_interface_events[] = { { “something_done”, “s”, NULL } // “s” 表示事件发送一个字符串参数 };
wl_resource 用于表示 Wayland 客户端与服务器之间的一个协议对象。它在客户端和服务器之间传递方法调用和事件通知。每个 wl_resource 都与一个特定的 wl_interface(接口)相关联,表示该接口的一个实例。 struct wl_resource *wl_resource_create(struct wl_client *client, const struct wl_interface *interface, int version, uint32_t id);
void wl_resource_set_implementation(struct wl_resource *resource, const void *implementation, void *data, wl_resource_destroy_func_t destroy);
wl_surface 代表了一个可供客户端绘制的表面(Surface)。它是构建用户界面的基本单元,可以是窗口、按钮、文本框等可见的元素。wl_surface 通过 Wayland 协议与客户端和服务器进行通信,客户端可以向 wl_surface 发送绘图指令,服务器则负责将这些指令转换为屏幕上的图像。
wl_output Wayland 中用于表示显示器(output)的接口。每个 wl_output 对象代表了系统中的一个物理显示设备,比如显示器或投影仪。通过 wl_output 接口,客户端程序可以获取有关显示器的信息,如分辨率、缩放因子、物理尺寸、制造商信息等,并接收显示器的事件,如模式更改、连接状态变化等。 wl_output_add_geometry_listener
wl_client 用于表示一个连接到 Wayland 服务器的客户端。它负责管理客户端连接、处理客户端的请求,并向客户端发送事件 struct wl_client *wl_resource_get_client(struct wl_resource *resource);
void wl_client_post_no_memory(struct wl_client *client);
void wl_client_post_implementation_error(struct wl_client *client, const char *msg);
void wl_client_post_event(struct wl_client *client, uint32_t opcode, …);
wl_signal 用于实现发布-订阅模式的信号机制。它允许对象在状态变化时通知感兴趣的侦听器(监听器)。wl_signal 是一个简单但功能强大的机制,可以在 Wayland 服务端内部或者在客户端与服务端之间传递事件通知。 struct wl_signal my_signal;
wl_signal_init(&my_signal);
void my_signal_handler(struct wl_listener *listener, void *data) {
printf(“Signal received with data: %s\n”, (char *)data);
}
struct wl_listener my_listener;
my_listener.notify = my_signal_handler;
wl_signal_add(&my_signal, &my_listener);
// send signal to notify all listener
const char *signal_data = “Hello, World!”;
wl_signal_emit(&my_signal, (void *)signal_data);
wl_listener 用于监听 wl_signal 发出的信号。每个 wl_listener 都包含一个回调函数,当监听的信号发出时,该回调函数会被调用。这种机制使得对象之间的通信变得更加灵活和解耦。 struct wl_listener {
struct wl_list link;
wl_notify_func_t notify;
};
struct wl_listener my_listener;
my_listener.notify = my_signal_handler; // 设置回调函数
wl_signal_add(&my_signal, &my_listener); // 将监听器添加到信号中
wl_list Wayland 核心库中的一个双向链表实现,用于在 Wayland 内部和相关组件中进行列表管理 struct wl_list {
struct wl_list *prev;
struct wl_list *next;
};
void wl_list_init(struct wl_list *list);
void wl_list_insert(struct wl_list *list, struct wl_list *elm);
void wl_list_remove(struct wl_list *elm);
wl_list_for_each & wl_list_for_each_safe
wl_shm_buffer Wayland 的共享内存(Shared Memory)缓冲区,用于在客户端和服务器之间共享图像数据。它允许客户端将图像数据写入共享内存,然后将该内存区域作为缓冲区发送到服务器。服务器可以直接访问这个共享内存,从而避免了数据的拷贝,提高了效率
wl_shm_pool Wayland 提供的一个共享内存池,用于在客户端和服务器之间共享图像数据。wl_shm_pool 是通过 Wayland 的 wl_shm 接口创建的,它允许客户端从共享内存中分配多个缓冲区。这些缓冲区可以被客户端用来绘制图像,并将其传递给服务器显示。
wl_protocol_logger 用于记录 Wayland 协议的消息。它允许开发者记录客户端和服务器之间交换的协议消息,方便调试和分析 Wayland 协议的使用情况。 struct wl_protocol_logger {
void (*log)(void *user_data, struct wl_resource *resource,
uint32_t opcode, const struct wl_message *message,
union wl_argument *args);
void *user_data;
};

struct wl_protocol_logger logger = {
.log = protocol_log,
.user_data = “Wayland” // 这里可以传递任何用户数据
};
wl_display_add_protocol_logger(display, &logger);
wl_display_add_destroy_listener 绑定到一个 wl_listener 结构,通过指定 wl_listener 的.notify成员实现对 display 销毁时的监听
用于在 wl_display 对象销毁时注册一个回调函数。这个回调函数会在 wl_display 对象销毁时被调用,以便进行清理或其他必要的操作。
manager->display_destroy.notify = handle_display_destroy;
wl_display_add_destroy_listener(display, &manager->display_destroy);

主要函数

Function Description Example
wl_display_add_destroy_listener 绑定到一个 wl_listener 结构,通过指定 wl_listener 的.notify成员实现对 display 销毁时的监听
用于在 wl_display 对象销毁时注册一个回调函数。这个回调函数会在 wl_display 对象销毁时被调用,以便进行清理或其他必要的操作。
manager->display_destroy.notify = handle_display_destroy;
wl_display_add_destroy_listener(display, &manager->display_destroy);
wl_event_loop_add_destroy_listener 用于向事件循环添加一个监听器,以便在事件循环被销毁时触发回调函数。 struct wl_listener *wl_event_loop_add_destroy_listener(struct wl_event_loop *loop, wl_listener *listener);
wl_global_create 用于创建一个全局对象,并将其注册到 Wayland 显示服务器上。全局对象可以被客户端程序获取并使用,从而实现客户端和服务器之间的通信。 struct wl_global *wl_global_create(struct wl_display *display,
const struct wl_interface *interface,
int version,
void *data,
wl_global_bind_func_t bind);
wl_global_remove 类似wl_global_destroy,但并不销毁。通常使用 wl_global_destroy 即可
wl_global_destroy 销毁一个全局对象。
wl_resource_set_implementation 用于将一组回调函数(即接口实现)和用户数据关联到一个 wl_resource 对象。每当与该资源相关的客户端请求到达时,Wayland 会调用相应的回调函数,从而实现具体的行为。 void wl_resource_set_implementation(struct wl_resource *resource,
const void *implementation,
void *data,
wl_resource_destroy_func_t destroy);
wl_resource_set_user_data 用于将用户自定义的数据与特定的 wl_resource 资源关联起来。这使得在处理资源相关的回调时,可以访问和使用这些用户数据。 void wl_resource_set_user_data(struct wl_resource *resource, void *user_data);
wl_display_roundtrip Wayland 客户端程序中常用的函数之一,用于同步地发送请求并等待服务器对请求的响应。它会阻塞当前线程,直到服务器返回响应或者发生错误。 int wl_display_roundtrip(struct wl_display *display);
wl_resource_get_user_data 用于获取与特定 wl_resource 资源关联的用户数据。这个函数通常与 wl_resource_set_user_data 一起使用,后者用于将用户数据与资源关联。 void *wl_resource_get_user_data(struct wl_resource *resource);

核心文件

wayland-server-core.h

协议分类

详见: https://wayland.app/protocols/
:::success
实际上,wayland 合成器就是一组协议的实现者。
:::
wayland 协议大致分为 core(核心)、stable(稳定)、staging(考虑中,可能会变为稳定)、unstable(不稳定)等,表明其当前状态,出于兼容考虑,合成器的开发应且务必实现 core 和 stable 协议,视情况需要实现部分 unstable 或其它甚至是自定义协议。

Core

Name Description
Wayland Wayland 核心协议,这个文件定义了客户端和服务器之间通信的标准接口,包括各种对象、请求和事件。是使用其他协议的前提。

Stable

Name Description
Presentation time Presentation-Time 协议是为了解决音频与视频同步播放时出现的问题而设计的。它允许客户端在显示器上的特定时间点提交图像,以确保图像在预期时间显示,从而实现音频和视频的同步播放。
Viewporter 它旨在支持客户端动态调整输出显示区域(viewport)的大小和位置。这个协议特别适用于需要对输出进行缩放、平移或裁剪的应用场景,比如 VR(虚拟现实)和多显示器环境下的窗口管理器等。
XDG shell 定义了一种标准的方式来管理窗口和窗口管理器之间的通信,使得窗口的创建、调整和销毁等操作能够在 Wayland 环境下进行。
Linux DMA-BUF Linux 内核中用于在设备之间共享内存的机制。DMA-BUF 全称是 Direct Memory Access Buffer,它允许不同的设备(如图形处理器、显示器、摄像头等)直接访问内核中的一块共享内存区域,而无需复制数据到每个设备的私有内存中。
Tablet 旨在提供对触摸板和手写板等输入设备的更丰富支持。它定义了一组标准接口,使得客户端能够更好地与这些特殊输入设备进行交互,并实现更丰富的用户体验。

Staging

Name Description
XDG activation 旨在提供一种标准化的方式来启动和激活桌面应用程序。它定义了一组接口,允许应用程序和桌面环境之间进行通信,以便启动应用程序、切换窗口焦点、和处理用户交互。
DRM lease 允许应用程序临时租用图形硬件资源,例如显示器和显卡,从而绕过窗口系统,直接控制这些资源。这在虚拟现实(VR)和增强现实(AR)等需要低延迟和高性能图形渲染的应用中尤为重要。
DRM synchronization object 用于在图形渲染过程中协调和同步不同操作和资源的使用。它们在确保图形管线中的各个阶段按正确顺序执行、避免资源竞争和数据不一致方面发挥着关键作用。
Session lock 用于保护用户会话安全,防止未授权的访问。当用户暂时离开工作站时,可以锁定会话以确保其正在运行的应用程序和数据不会被其他人查看或篡改。
Single-pixel buffer 专门用于优化小图形元素的传输和渲染。它的主要目标是提供一种高效的方式来处理单个像素的图形操作,这对于某些类型的图形应用程序(如光标、点状图形、单色图形元素等)非常重要。
Content type hint 允许客户端向合成器(compositor)提供关于表面(surface)内容类型的提示。这些提示有助于合成器优化渲染和处理不同类型的内容,提高显示性能和视觉效果。
Idle notify 使客户端能够接收用户空闲状态的通知,从而在用户不活动时执行特定任务。这种机制有助于优化系统资源使用,提高用户体验,特别是在屏幕保护和节能模式等应用场景中。通过合理使用该协议,开发者可以显著提升应用程序的智能化和响应能力。
Tearing control 旨在解决屏幕撕裂问题。屏幕撕裂是在显示器刷新过程中,显示的图像部分来自于不同的帧,导致图像出现不连续的现象。这种现象在快速运动的场景中特别明显。Tearing Control 协议通过提供机制让客户端和合成器协作,来减少或消除屏幕撕裂,提高显示效果和用户体验。
Xwayland shell Xwayland 是 Wayland 的一个兼容层,使得 X11 应用程序可以在 Wayland 合成器上运行。Xwayland Shell 协议是一个特定的 Wayland 扩展协议,旨在为运行在 Xwayland 上的 X11 客户端提供更好的窗口管理和集成支持。这个协议使得 Wayland 合成器能够更好地控制和管理这些 X11 窗口,从而提高整体用户体验。
Fractional scale 用于支持显示器的分数缩放(Fractional Scaling)。传统的显示器缩放通常只支持整数比例,例如 1x、2x、3x 等,这可能无法完全满足高分辨率显示器上的 UI 缩放需求。Fractional Scale 协议允许用户以分数形式(如 1.25x、1.5x、1.75x 等)调整 UI 的缩放级别,以更好地适应高分辨率显示器和不同的视觉需求。
Cursor shape 旨在允许客户端动态地改变鼠标指针的形状。这个协议使得应用程序可以根据需要更改鼠标指针的外观,以提供更好的用户体验和交互反馈。
Foreign toplevel list 旨在提供一种机制,让 Wayland 合成器(compositor)可以跟踪和管理来自外部系统的顶层窗口(例如 X11 窗口)。通过这个协议,Wayland 合成器可以更好地集成和管理来自不同窗口系统的窗口,提供更统一的用户体验。
Security context Security Context 协议为 Wayland 提供了一种机制,允许客户端和服务端在通信中传递安全上下文信息,增强通信的安全性。尽管该协议需要客户端和服务端的正确实现,并可能带来一定的性能开销,但它能够有效地保护通信内容的机密性和完整性,并支持基于权限的访问控制,从而提高了 Wayland 通信的安全性。
Transient seat 它允许在同一系统上的不同输入设备之间建立父子关系。这种关系对于多屏幕系统或者具有多个输入设备的系统非常有用,它能够确保特定的输入设备(例如触摸板或者鼠标)仅控制特定的屏幕或应用程序。
XDG toplevel drag 用于实现在 Wayland 上对窗口进行拖动操作。这个协议允许用户在屏幕上拖动应用程序的顶层窗口,以便更改其位置或将其拖入其他工作区等。XDG Toplevel Drag 协议的实现使得用户能够通过简单的拖动操作来管理和组织窗口,提高了桌面环境的交互性和可用性。
XDG dialog windows 旨在为桌面环境提供一种标准化的方式来管理对话框窗口。该协议定义了一组接口和事件,用于创建、显示和管理对话框窗口,并规定了对话框窗口的行为和外观,以提供更一致的用户体验。
Alpha modifier protocol 它允许客户端与服务器协商窗口表面(surface)的 Alpha 值,以实现窗口的透明度调整。

其他协议请查阅:https://wayland.app/protocols/

自定义协议

使用 wayland-scanner 命令将协议文件(XML)生成胶水代码,如果是服务端,需设置新协议的实现,客户端直接调用胶水代码的接口即可。

服务端实现

参考 wlroots 项目,不再赘述。
附一些之前手写的合成器对 ext_session_lock_v1 协议支持的代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#pragma once

#include "ext-session-lock-server-protocol.h"

struct ext_session_lock_manager_v1
{
struct wl_event_loop *event_loop;
struct wl_global *global;
struct wl_list contexts;
struct wl_resource *client;
struct wl_listener display_destroy;

struct
{
struct wl_signal lock;
struct wl_signal destroy;
} events;
};

struct ext_session_lock_v1
{
struct wl_resource *resource;
uint32_t id;
struct wl_list contexts;

struct
{
struct wl_signal get_lock_surface;
struct wl_signal unlock_and_destroy;
struct wl_signal destroy;
} events;
};

struct ext_session_lock_surface_v1
{
struct wl_resource *resource;
struct wl_resource *surface;
struct wl_resource *output;
uint32_t id;

struct
{
struct wl_signal ack_configure;
struct wl_signal destroy;
} events;
};

void ext_session_lock_v1_destroy(struct ext_session_lock_v1 *context);
void ext_session_lock_surface_v1_destroy(struct ext_session_lock_surface_v1 *context);
struct ext_session_lock_manager_v1 *ext_session_lock_manager_v1_create(struct wl_display *display);

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
#include "ext_session_lock_manager_impl.h"

void lock_surface_handle_destroy(struct wl_client *client, struct wl_resource *resource)
{
wl_resource_destroy(resource);
}

static void lock_surface_handle_ack_configure([[maybe_unused]] struct wl_client *client,
struct wl_resource *resource,
uint32_t serial)
{
struct ext_session_lock_surface_v1 *context =
static_cast<ext_session_lock_surface_v1 *>(wl_resource_get_user_data(resource));
if (!context) {
return;
}

wl_signal_emit_mutable(&context->events.ack_configure, context);
}

static const struct ext_session_lock_surface_v1_interface lock_surface_implementation = {
.destroy = lock_surface_handle_destroy,
.ack_configure = lock_surface_handle_ack_configure,
};

void ext_session_lock_surface_v1_destroy(struct ext_session_lock_surface_v1 *context)
{
wl_signal_emit_mutable(&context->events.destroy, context);
free(context);
}

void ext_session_lock_surface_v1_destroy_func(wl_resource *resource)
{
struct ext_session_lock_surface_v1 *context =
static_cast<ext_session_lock_surface_v1 *>(wl_resource_get_user_data(resource));
if (!context) {
return;
}

ext_session_lock_surface_v1_destroy(context);
}

static void lock_handle_get_lock_surface(struct wl_client *client,
struct wl_resource *lock_resource,
uint32_t id,
struct wl_resource *surface,
struct wl_resource *output)
{
struct ext_session_lock_v1 *context =
static_cast<ext_session_lock_v1 *>(wl_resource_get_user_data(lock_resource));
struct wl_resource *resource = wl_resource_create(client,
&ext_session_lock_surface_v1_interface,
EXT_SESSION_LOCK_V1_DESTROY_SINCE_VERSION,
id);
if (resource == NULL) {
wl_resource_post_no_memory(lock_resource);
return;
}

struct ext_session_lock_surface_v1 *lock_surface =
static_cast<ext_session_lock_surface_v1 *>(calloc(1, sizeof(*lock_surface)));
if (lock_surface == NULL) {
wl_resource_post_no_memory(lock_resource);
return;
}

wl_resource_set_implementation(resource,
&lock_surface_implementation,
lock_surface,
ext_session_lock_surface_v1_destroy_func);

wl_resource_set_user_data(resource, lock_surface);

wl_signal_init(&lock_surface->events.ack_configure);
wl_signal_init(&lock_surface->events.destroy);

lock_surface->resource = resource;
lock_surface->surface = surface;
lock_surface->output = output;
lock_surface->id = id;

wl_signal_emit_mutable(&context->events.get_lock_surface, lock_surface);
}

static void lock_handle_destroy([[maybe_unused]] struct wl_client *client,
struct wl_resource *resource)
{
wl_resource_destroy(resource);
}

static void lock_handle_unlock_and_destroy(struct wl_client *client, struct wl_resource *resource)
{
struct ext_session_lock_v1 *context =
static_cast<ext_session_lock_v1 *>(wl_resource_get_user_data(resource));
wl_signal_emit_mutable(&context->events.unlock_and_destroy, context);
lock_handle_destroy(client, resource);
}

static const struct ext_session_lock_v1_interface lock_implementation = {
.destroy = lock_handle_destroy,
.get_lock_surface = lock_handle_get_lock_surface,
.unlock_and_destroy = lock_handle_unlock_and_destroy,
};

static void manager_handle_destroy(struct wl_client *client, struct wl_resource *resource)
{
struct ext_session_lock_manager_v1 *context =
static_cast<ext_session_lock_manager_v1 *>(wl_resource_get_user_data(resource));
if (!context) {
return;
}

wl_signal_emit_mutable(&context->events.destroy, context);
wl_list_remove(wl_resource_get_link(resource));
}

void ext_session_lock_v1_destroy(struct ext_session_lock_v1 *context)
{
wl_signal_emit_mutable(&context->events.destroy, context);
free(context);
}

void ext_session_lock_v1_destroy_func(wl_resource *resource)
{
struct ext_session_lock_v1 *context =
static_cast<ext_session_lock_v1 *>(wl_resource_get_user_data(resource));
if (!context) {
return;
}

ext_session_lock_v1_destroy(context);
}

static void manager_handle_lock(struct wl_client *client,
struct wl_resource *manager_resource,
uint32_t id)
{
struct ext_session_lock_manager_v1 *manager =
static_cast<ext_session_lock_manager_v1 *>(wl_resource_get_user_data(manager_resource));

struct wl_resource *resource = wl_resource_create(client,
&ext_session_lock_v1_interface,
EXT_SESSION_LOCK_V1_DESTROY_SINCE_VERSION,
id);
if (resource == NULL) {
wl_resource_post_no_memory(manager_resource);
return;
}

struct ext_session_lock_v1 *context =
static_cast<ext_session_lock_v1 *>(calloc(1, sizeof(*context)));
if (context == NULL) {
wl_resource_post_no_memory(manager_resource);
return;
}

wl_resource_set_implementation(resource,
&lock_implementation,
context,
ext_session_lock_v1_destroy_func);
wl_resource_set_user_data(resource, context);

wl_signal_init(&context->events.get_lock_surface);
wl_signal_init(&context->events.unlock_and_destroy);
wl_signal_init(&context->events.destroy);

context->resource = resource;
context->id = id;
wl_list_init(&context->contexts);
wl_list_insert(&manager->contexts, wl_resource_get_link(resource));

wl_signal_emit_mutable(&manager->events.lock, context);
}

static const struct ext_session_lock_manager_v1_interface lock_manager_implementation = {
.destroy = manager_handle_destroy,
.lock = manager_handle_lock,
};

static void bind_ext_session_lock_manager_v1(struct wl_client *client,
void *data,
uint32_t version,
uint32_t id)
{
struct ext_session_lock_manager_v1 *manager =
static_cast<struct ext_session_lock_manager_v1 *>(data);
struct wl_resource *resource =
wl_resource_create(client, &ext_session_lock_manager_v1_interface, version, id);
if (!resource) {
wl_client_post_no_memory(client);
return;
}
wl_resource_set_implementation(resource, &lock_manager_implementation, manager, NULL);

wl_list_insert(&manager->contexts, wl_resource_get_link(resource));

manager->client = resource;
}

static void handle_display_destroy(struct wl_listener *listener, [[maybe_unused]] void *data)
{
struct ext_session_lock_manager_v1 *manager =
wl_container_of(listener, manager, display_destroy);
wl_signal_emit_mutable(&manager->events.destroy, manager);
wl_list_remove(&manager->display_destroy.link);
wl_global_destroy(manager->global);
free(manager);
}

#define SESSION_LOCK_MANAGEMENT_V1_VERSION 1

ext_session_lock_manager_v1 *ext_session_lock_manager_v1_create(wl_display *display)
{
struct ext_session_lock_manager_v1 *manager =
static_cast<struct ext_session_lock_manager_v1 *>(calloc(1, sizeof(*manager)));
if (!manager) {
return NULL;
}

manager->event_loop = wl_display_get_event_loop(display);
manager->global = wl_global_create(display,
&ext_session_lock_manager_v1_interface,
SESSION_LOCK_MANAGEMENT_V1_VERSION,
manager,
bind_ext_session_lock_manager_v1);
if (!manager->global) {
free(manager);
return NULL;
}

wl_signal_init(&manager->events.lock);
wl_signal_init(&manager->events.destroy);
wl_list_init(&manager->contexts);

manager->display_destroy.notify = handle_display_destroy;
wl_display_add_destroy_listener(display, &manager->display_destroy);

return manager;
}

协议的书写上面大同小异,其他协议均按照类似的代码方式进行了支持,如感兴趣,强烈建议参阅 wlroots 、weston、sway、mutter、kwin等项目的源码。

客户端调用

ext_session_lock_v1 协议为例,客户端(例如锁屏程序)的实现如下:

1
2
3
4
# 生成协议头文件
wayland-scanner client-header ext-session-lock-v1.xml client.h
# 生成协议源文件(通过命令生成的代码,全是胶水代码)
wayland-scanner code ext-session-lock-v1.xml client.c

main文件内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#include <stdio.h>
#include <stdlib.h>
#include <wayland-client.h>
#include "client.h"

struct example_data {
struct wl_display *display;
struct wl_registry *registry;
struct wl_compositor *compositor;
struct wl_surface *surface;
struct wl_output *output;
struct ext_session_lock_manager_v1 *lock_manager;
struct ext_session_lock_v1 *lock;
struct ext_session_lock_surface_v1 *lock_surface;
};

static void on_locked(void *data, struct ext_session_lock_v1 *lock) {
printf("Session successfully locked.\n");
}

static void on_configure(void *data,
struct ext_session_lock_surface_v1 *ext_session_lock_surface_v1,
uint32_t serial,
uint32_t width,
uint32_t height) {
printf("Received configure event, send ack_configure back\n");
ext_session_lock_surface_v1_ack_configure(ext_session_lock_surface_v1, serial);
}


static const struct ext_session_lock_v1_listener lock_listener = {
on_locked
};

static const struct ext_session_lock_surface_v1_listener lock_surface_listener = {
on_configure
};

static void registry_handle_global(void *data, struct wl_registry *registry, uint32_t id,
const char *interface, uint32_t version) {
struct example_data *example_data = data;
if (strcmp(interface, "ext_session_lock_manager_v1") == 0) {
example_data->lock_manager = wl_registry_bind(registry, id, &ext_session_lock_manager_v1_interface, 1);
}

if (strcmp(interface, "wl_compositor") == 0) {
example_data->compositor = wl_registry_bind(registry, id, &wl_compositor_interface, 1);
printf("Got wl_compositor\n");
}

if (strcmp(interface, "wl_output") == 0) {
example_data->output = wl_registry_bind(registry, id, &wl_output_interface, version);
printf("Got wl_output\n");
}
}

static const struct wl_registry_listener registry_listener = {
registry_handle_global,
NULL
};

int main(int argc, char **argv) {
struct example_data data;

data.lock = NULL;

data.display = wl_display_connect(NULL);
if (!data.display) {
fprintf(stderr, "Failed to connect to Wayland display.\n");
return -1;
}

// Get registry
data.registry = wl_display_get_registry(data.display);
wl_registry_add_listener(data.registry, &registry_listener, &data);
wl_display_roundtrip(data.display);

if (!data.lock_manager || !data.compositor || !data.output) {
fprintf(stderr, "Required interfaces not available\n");
wl_registry_destroy(data.registry);
wl_display_disconnect(data.display);
return -1;
}

// Request lock
data.lock = ext_session_lock_manager_v1_lock(data.lock_manager);
ext_session_lock_v1_add_listener(data.lock, &lock_listener, &data);
wl_display_roundtrip(data.display);
if (!data.lock) {
fprintf(stderr, "Required interfaces: lock not available\n");
wl_registry_destroy(data.registry);
wl_display_disconnect(data.display);
return -1;
}

// Request destroy
// ext_session_lock_manager_v1_destroy(data.lock);
// wl_display_roundtrip(data.display);

// // Wait for events
// while (wl_display_dispatch(data.display) != -1) {
// // Handle events
// }
// return 0;


// Create surface
data.surface = wl_compositor_create_surface(data.compositor);
if (!data.surface) {
fprintf(stderr, "Failed to create wl_surface\n");
wl_registry_destroy(data.registry);
wl_display_disconnect(data.display);
return 1;
}

// Request get_lock_surface
data.lock_surface = ext_session_lock_v1_get_lock_surface(data.lock, data.surface, data.output);
ext_session_lock_surface_v1_add_listener(data.lock_surface, &lock_surface_listener, &data);
wl_display_roundtrip(data.display);


// Request unlock_and_destroy
fprintf(stderr, "Start Unlock\n");
ext_session_lock_v1_unlock_and_destroy(data.lock);
wl_display_roundtrip(data.display);
fprintf(stderr, "Unlock success\n");

// Wait for events
while (wl_display_dispatch(data.display) != -1) {
// Handle events
}

// Cleanup
ext_session_lock_manager_v1_destroy(data.lock_manager);
wl_registry_destroy(data.registry);
wl_display_disconnect(data.display);
return 0;
}

通过 gcc -o client client.c main.c -lwayland-client编译生成 client 二进制。
运行:注意在运行此二进制之前,需要指定其 WAYLAND_DISPLAY环境变量,这是由 Wayland 合成器决定的。

1
2
export WAYLAND_DISPLAY=wayland-0
./client

至此,我们已经完成了一个简单的客户端调用 Wayland 协议的 demo。在实际开发中,这些调用通常由各种开发库进行了封装。例如,libqtwayland 就是由 Qt 对部分 Wayland 协议的调用进行了封装,从而使得开发者在开发桌面应用时无需直接处理协议调用,只需使用 Qt 中已有的类和接口即可。

// By A Way
A week 的合成器之旅达到 Ending,以后有时间再继续丰富.

参考

https://wayland.freedesktop.org/
The Wayland Protocol 中文版

 Comments
Comment plugin failed to load
Loading comment plugin