4. Wayland协议基础

Wayland 协议本质上是一套基于 Unix 域套接字的对象化 RPC 协议。客户端与合成器之间并不传递“窗口”或“按钮”这类语义对象,而是围绕一组由接口(interface)定义的对象进行方法调用与事件通知。理解 wl_display、wl_registry、wl_compositor、wl_surface、wl_shm、wl_seat 这几个核心接口,就掌握了 Wayland 图形栈的骨架。

一、对象模型:接口、对象与消息

Wayland 协议中的每个对象都有唯一的数字 ID,并绑定到一个接口。接口定义了若干 request(客户端发给合成器)和 event(合成器发给客户端)。消息通过 wl_display 建立的连接传输,携带对象 ID、操作码和参数。对象之间可以建立父子或关联关系,例如 wl_surface 由 wl_compositor 创建,wl_buffer 由 wl_shm 创建。

核心思想:客户端不直接操作硬件或窗口系统,而是通过协议对象请求合成器完成合成、输入分发和缓冲区管理。

二、核心接口职责

接口 角色 关键请求/事件 典型用途
wl_display 连接与全局调度入口 get_registry、sync、error 建立连接、获取注册表、同步往返
wl_registry 全局对象发现 bind、global、global_remove 枚举并绑定 compositor、shm、seat 等
wl_compositor 表面工厂 create_surface、create_region 创建 wl_surface 与区域
wl_surface 可绘制表面 attach、commit、damage 提交缓冲区、声明损伤区域
wl_shm 共享内存缓冲 create_pool、format CPU 可写像素缓冲,软件渲染
wl_seat 输入设备集合 get_pointer、get_keyboard、capabilities 获取指针、键盘、触摸输入

三、连接建立与对象绑定流程

客户端首先连接合成器提供的 socket,创建 wl_display。随后通过 wl_display.get_registry 获得 wl_registry,并监听 global 事件。每个全局对象会携带接口名、版本号和名称。客户端根据自身需要调用 wl_registry.bind,将全局名称绑定为本地代理对象。

/* 伪代码:绑定 wl_compositor 与 wl_shm */
registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, &registry_listener, NULL);
wl_display_roundtrip(display);

/* 在 global 回调中 */
if (strcmp(interface, wl_compositor_interface.name) == 0) {
    compositor = wl_registry_bind(registry, name,
                                  &wl_compositor_interface, 4);
}
if (strcmp(interface, wl_shm_interface.name) == 0) {
    shm = wl_registry_bind(registry, name, &wl_shm_interface, 1);
}

绑定完成后,客户端即可调用 wl_compositor.create_surface 创建表面,再通过 wl_shm.create_pool 创建共享内存池并生成 wl_buffer,最后用 wl_surface.attach 与 commit 提交内容。

四、信号链视角:从输入到提交

下面这张图展示了核心接口之间的对象创建关系与数据流方向。左侧是客户端侧对象,右侧是合成器侧处理,中间通过协议消息连接。

Wayland 核心接口对象关系与数据流 客户端进程 wl_display 连接入口 / 同步调度 wl_registry 全局对象发现与绑定 wl_compositor 创建表面 wl_shm 共享内存缓冲 wl_surface attach / commit wl_buffer 像素数据载体 wl_seat pointer / keyboard / touch 合成器进程 全局对象管理 registry / global 事件 表面合成与场景图 surface 状态机 缓冲区管理 shm pool / buffer 释放 输入事件分发 焦点 / 指针 / 键盘 渲染与显示输出 DRM / KMS / EGL get_registry bind create_pool attach / commit get_pointer 请求/绑定 缓冲/提交 对象创建关系

五、常见误区与复盘

  • 误区一:绑定后立即使用。 wl_registry.bind 是异步的,必须通过 wl_display_roundtrip 或事件循环确认全局对象已就绪,否则可能拿到空代理。
  • 误区二:忽略版本号。 每个接口有版本范围,绑定版本过高会导致协议错误。应使用 wl_registry.global 事件中的 version 与本地支持版本取较小值。
  • 误区三:忘记释放缓冲区。 wl_buffer 在合成器使用完毕后会发送 release 事件,客户端必须监听并回收,否则共享内存池会耗尽。
  • 误区四:混淆 wl_surface 与窗口。 wl_surface 只是可绘制区域,窗口语义由 xdg_shell 等扩展协议提供,核心协议本身不定义窗口管理。
调试建议:使用 WAYLAND_DEBUG=1 运行客户端,可以打印所有协议消息,快速定位对象 ID、请求与事件顺序问题。

掌握这些核心接口后,后续的 xdg_shell、wl_output、wl_pointer 等扩展都只是在同一对象模型上的自然延伸。理解 wl_display 的连接调度、wl_registry 的发现机制、wl_compositor 与 wl_surface 的创建提交链路、wl_shm 的缓冲管理以及 wl_seat 的输入分发,就具备了阅读和编写 Wayland 客户端的基础能力。