The Wayland Protocol(自译中文版)这本书能快速深入理解Wayland的概念、设计和实现,并为您提供构建自行构建Wayland客户端和服务端所需的工具。通过简单的例子,快速去理解wayland的设计和原理。
Wayland基于domain socket实现了一套display server与client间通信的库,并且以XML形式定义了一套可扩展通信协议。这个协议分为Wayland核心协议和扩展协议。
Wayland协议中最基础的是提供了一种面向对象的跨进程过程调用的功能,在作用上类似于Android中的Binder。与Binder不同的是,在Wayland中Client和Server底层通过domain socket
进行连接。和Binder一样,domain socket
支持在进程间传递fd,这为传递graphic buffer和shared memory等提供了基础。
下面是从第四节开始的wayland和server的例子。
例子链接
client代码,连接到 Wayland 服务端:
#include
#include
/*
build: cc -o client client.c -lwayland-client
*/
int
main(int argc, char *argv[])
{
struct wl_display *display = wl_display_connect(NULL);
if (!display) {
fprintf(stderr, "Failed to connect to Wayland display.\n");
return 1;
}
fprintf(stderr, "Connection established!\n");
wl_display_disconnect(display);
return 0;
}
wl_display_connect 是客户端建立 Wayland 连接最常见的方式,其声明如下:
struct wl_display *wl_display_connect(const char *name);
参数 name 是 Wayland 显示服务的名称,通常是 “wayland-0”,这与 $XDG_RUNTIME_DIR 中的 Unix 套接字的名称相对应。
$ echo $XDG_RUNTIME_DIR /run/user/1000
可以看到
/run/user/1000
目录下有这几个文件:$ ls /run/user/1000/wayland-* /run/user/1000/wayland-0 /run/user/1000/wayland-0.lock /run/user/1000/wayland-1 /run/user/1000/wayland-1.lock
server代码,创建一个 wl_display 来管理连接状态:
#include
#include
int
main(int argc, char *argv[])
{
struct wl_display *display = wl_display_create();
if (!display) {
fprintf(stderr, "Unable to create Wayland display.\n");
return 1;
}
const char *socket = wl_display_add_socket_auto(display);
if (!socket) {
fprintf(stderr, "Unable to add socket to Wayland display.\n");
return 1;
}
fprintf(stderr, "Running Wayland display on %s\n", socket);
wl_display_run(display);
wl_display_destroy(display);
return 0;
}
ubuntu编译:
cc -o server server.c -lwayland-server
cc -o client client.c -lwayland-client
启动server,然后执行client,就可以看到连接server成功!
$ ./server &
$ ./client
Connection established!
在ubuntu上,前面wayland server这个简单server还不能绘制,不能输出interface等。最快的是安装Weston(Weston是Wayland compositor的参考实现),启动weston后,往下学习就变得很直观了。
在安装weston后,直接启动weston或者在虚拟terminal(tty(ctrl+alt+[f2-f7])
环境下执行globals就能输出interface的打印和绘制窗口了。globals也是来自这个官方文档,代码如下:
#include
#include
#include
/*
cc -o globals globals.c -lwayland-client
*/
static void
registry_handle_global(void *data, struct wl_registry *registry,
uint32_t name, const char *interface, uint32_t version)
{
printf("interface: '%s', version: %d, name: %d\n",
interface, version, name);
}
static void
registry_handle_global_remove(void *data, struct wl_registry *registry,
uint32_t name)
{
// This space deliberately left blank
}
static const struct wl_registry_listener
registry_listener = {
.global = registry_handle_global,
.global_remove = registry_handle_global_remove,
};
int
main(int argc, char *argv[])
{
struct wl_display *display = wl_display_connect(NULL);
if (!display) {
fprintf(stderr, "Failed to connect to Wayland display.\n");
return 1;
}
fprintf(stderr, "Connection established!\n");
struct wl_registry *registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, ®istry_listener, NULL);
wl_display_roundtrip(display);
return 0;
}
在ubuntu上用前面的server作为wayland server,globals程序是没有输出的,因为我们的server代码还很简单,没有注册这些interface,而使用weston server就能得到了完整的输出了。
Wayland是一套display server(Wayland compositor)与client间的通信协议,而Weston是Wayland compositor的参考实现。
sudo apt install weston
weston&
或者ctl+alt+f4
切换到tty4,也可以用sudo chvt 4
来切换,数字4代表tty4,然后输入:
weston-launch
执行weston-launch
后,切换到tty4,可以看到weston完整的桌面,点击左上角的按钮后会出现新终端界面。这时候执行前面编译的globals程序,就会输出interface的打印。
interface: 'wl_compositor', version: 4, name: 1
interface: 'wl_subcompositor', version: 1, name: 2
interface: 'wp_viewporter', version: 1, name: 3
interface: 'zxdg_output_manager_v1', version: 2, name: 4
interface: 'wp_presentation', version: 1, name: 5
interface: 'zwp_relative_pointer_manager_v1', version: 1, name: 6
interface: 'zwp_pointer_constraints_v1', version: 1, name: 7
interface: 'zwp_input_timestamps_manager_v1', version: 1, name: 8
interface: 'wl_data_device_manager', version: 3, name: 9
interface: 'wl_shm', version: 1, name: 10
interface: 'wl_drm', version: 2, name: 11
interface: 'wl_seat', version: 7, name: 12
interface: 'zwp_linux_dmabuf_v1', version: 3, name: 13
interface: 'weston_direct_display_v1', version: 1, name: 14
interface: 'zwp_linux_explicit_synchronization_v1', version: 2, name: 15
interface: 'weston_content_protection', version: 1, name: 16
interface: 'wl_output', version: 3, name: 17
interface: 'wl_output', version: 3, name: 18
interface: 'zwp_input_panel_v1', version: 1, name: 19
interface: 'zwp_input_method_v1', version: 1, name: 20
interface: 'zwp_text_input_manager_v1', version: 1, name: 21
interface: 'xdg_wm_base', version: 1, name: 22
interface: 'zxdg_shell_v6', version: 1, name: 23
interface: 'wl_shell', version: 1, name: 24
interface: 'weston_desktop_shell', version: 1, name: 25
interface: 'weston_screenshooter', version: 1, name: 26
显示640x480 像素的棋盘格的例子
启动weston服务,然后执行draw-grid就可以看到下面的效果图:
$ cc draw-grid draw-grid.c xdg-shell-protocol.c -lwayland-client -lrt
$ weston &
$ ./draw-grid
这个是启动了weston作为display server,然后编译棋盘格代码,编译命令在下面代码的注释里面。
draw-grid的代码:
/*
cc -o draw-grid draw-grid.c xdg-shell-protocol.c -lwayland-client -lrt
*/
#define _POSIX_C_SOURCE 200112L
#include
#include
#include
#include
#include
#include
#include
#include
#include
#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 {
/*
* 关于 ftruncate 的内容可以参考
* https://www.man7.org/linux/man-pages/man3/ftruncate.3p.html
*/
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;
// 返回一个 ack_configure 以示确认
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 作为代理连接到混成器
wl_display_roundtrip(state.wl_display);
// 从混成器创建一个表面
state.wl_surface = wl_compositor_create_surface(state.wl_compositor);
// 从 wl_surface 创建一个 xdg_surface
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;
}
这个例子中需要先生成xdg-shell-protocol.c和xdg-shell-client-protocol.h,这就用到了wayland-scanner,wayland-scanner,它可以用于从诸如 wayland.xml 之类的文件生成 C 头文件和上下文代码。
xdb-shell协议以xml格式提供,我们在使用时要把它生成为代码:
# 获取wayland-protoclas路径
$ pkg-config wayland-protocols --variable=pkgdatadir
/usr/share/wayland-protocols
# 获取wayland-scanner路径
$ pkg-config --variable=wayland_scanner wayland-scanner
/usr/bin/wayland-scanner
# 获取wayland-client的lib flags
$ pkg-config wayland-client --cflags --libs
-lwayland-client
# xdg-shell的路径
$ ls /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml
# 生成代码
$ 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
#或者
$ wayland-scanner client-header /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml xdg-shell-client-protocol.h
$ wayland-scanner private-code /usr/share/wayland-protocols/stable/xdg-shell/xdg-shell.xml xdg-shell-protocol.c
TTY来源于Teletypewriter,翻译过来就是电传打字机。Teletypewriter和普通打字机的区别在于,Teletypewriter连接到通信设备以发送所打印的文字信息,电传打字机使人类能够通过电线更快地通信,这就是“TTY”这个词最初产生的时间。
随着技术的进一步发展,实体的电传打字机进行了“虚拟化”。因此不再需要物理、机械的TTY,而是虚拟的电子TTY。早期的计算机甚至没有视频屏幕,东西印在纸上,而不是显示在屏幕上。因此,你会看到“打印”一词的使用,而不是“显示”。随着技术的进步,显示器才被添加到终端中,可以称它们为“物理”终端。然后,这些演变成软件模拟终端,并具有增强的能力和功能。这就是所谓的“终端模拟器”。
Linux中的TTY是一个抽象设备。有时它指的是物理输入设备,如串行端口,有时它指的是允许用户与系统交互的虚拟TTY。
可以在大多数发行版上使用以下键盘快捷键来获取TTY屏幕:
CTRL + ALT + F1 – 锁定屏幕
CTRL + ALT + F2 – 桌面环境
CTRL + ALT + F3 – TTY3
CTRL + ALT + F4 – TTY4
CTRL + ALT + F5 – TT5
CTRL + ALT + F6 – TTY6
The Wayland Protocol(自译中文版)
第一个黑框框
Wayland入门1:运行测试程序