3. Wayland协议基础

Wayland 并不是一个图形库,也不是一套渲染 API,而是一份显示服务器与客户端之间的通信协议。它规定了客户端如何把自己的窗口内容、输入事件、缓冲区提交等意图,通过一条本地套接字传递给合成器(Compositor),由合成器统一完成合成、显示与输入分发。理解 Wayland,本质上就是理解“谁拥有什么对象、谁向谁发什么请求、谁在什么时候收到什么事件”。

Wayland 的核心思想可以概括为一句话:客户端只负责画自己的表面,合成器负责决定这些表面如何出现在屏幕上。客户端不再拥有屏幕、不再直接操作显卡输出,也不再自行处理全局输入焦点。

一、整体架构:客户端、合成器与协议对象

在 Weston 嵌入式桌面环境中,Wayland 架构通常由三层构成。最上层是客户端应用,例如 weston-terminal、weston-simple-egl 或基于 Qt/GTK 的应用;中间层是Wayland 协议,它定义了接口、请求、事件和对象生命周期;最下层是合成器,在 Weston 中即 weston 进程本身,它同时承担显示服务器、输入管理器、窗口管理器和渲染合成器的角色。

客户端与合成器之间通过 Unix 域套接字建立连接。连接建立后,双方并不是直接传递“窗口”或“按钮”这样的高级概念,而是围绕一组协议对象进行交互。每个对象都有唯一的数字 ID,客户端通过请求(request)操作对象,合成器通过事件(event)通知客户端状态变化。这种“对象 + 请求 + 事件”的模型,是 Wayland 协议最基础的抽象。

Wayland 客户端—合成器通信架构 Wayland 客户端 应用进程 / 图形工具包 wl_display 连接 wl_registry 发现 wl_surface 绘制 wl_buffer 提交 wl_seat 输入 Wayland 协议 对象 / 请求 / 事件 接口定义 XML 对象 ID 与生命周期 请求 request 事件 event Unix 域套接字 Weston 合成器 显示服务器 / 窗口管理 场景图 scene graph 输入焦点管理 渲染与合成 DRM / KMS 输出 输入设备 evdev 请求 请求 事件 客户端通过 wl_display 连接,经协议对象向 Weston 发送请求;Weston 通过事件回传状态与输入

二、客户端—合成器通信模型

Wayland 的通信模型是双向异步的。客户端向合成器发送请求,合成器向客户端发送事件。两者都通过同一条连接传输,但方向相反。客户端不能直接调用合成器的函数,也不能直接读取合成器的内部状态;它只能通过协议规定的请求和事件来间接交互。

一个典型的交互过程如下:客户端首先连接 wl_display,然后通过 wl_registry 获取合成器暴露的全局对象,例如 wl_compositor、wl_shm、wl_seat、xdg_wm_base 等。客户端绑定这些全局对象后,创建自己的 wl_surface,并将 wl_buffer 附加到 surface 上。当客户端完成绘制并调用 wl_surface_commit 时,合成器收到提交请求,随后在合适的时机进行合成,并通过 frame callback 或 buffer release 事件通知客户端“可以继续画下一帧”。

输入方向则相反。当用户移动鼠标或按下键盘时,Weston 从 evdev 读取输入事件,经过 seat 和 pointer/keyboard 对象,将事件发送给当前拥有输入焦点的客户端。客户端收到 wl_pointer.motion 或 wl_keyboard.key 事件后,更新自己的界面状态,再提交新的表面内容。整个过程中,客户端不需要知道屏幕分辨率、不需要知道其他窗口的位置,也不需要直接访问输入设备。

三、核心接口与对象

Wayland 协议由一组接口组成,每个接口对应一类对象。下表列出了嵌入式 Weston 开发中最常接触的核心对象及其职责。

接口/对象 方向 核心职责 典型使用场景
wl_display 客户端 ↔ 合成器 连接入口,管理协议对象 ID 与事件队列 连接 Weston、分发事件、同步请求
wl_registry 合成器 → 客户端 枚举全局对象,通知客户端可用能力 发现 wl_compositor、wl_seat、xdg_wm_base
wl_compositor 客户端 → 合成器 创建 surface 和 region 创建窗口表面、裁剪区域
wl_surface 客户端 → 合成器 表示一块可被合成的像素区域 窗口内容、光标、子表面
wl_buffer 客户端 → 合成器 承载像素数据的内存缓冲区 共享内存 wl_shm 或 EGL 缓冲区
wl_seat 合成器 → 客户端 输入设备集合,管理 pointer/keyboard/touch 鼠标、键盘、触摸输入
xdg_wm_base 客户端 → 合成器 桌面窗口语义,管理窗口状态 最大化、最小化、标题、移动

四、案例:一个最小 Wayland 客户端的连接流程

下面这段伪代码展示了客户端从连接 Weston 到提交第一帧的典型顺序。它省略了错误处理和缓冲区分配细节,但保留了 Wayland 协议交互的关键步骤。

display = wl_display_connect(NULL);
registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, &registry_listener, NULL);
wl_display_roundtrip(display);          // 获取全局对象

surface = wl_compositor_create_surface(compositor);
xdg_surface = xdg_wm_base_get_xdg_surface(xdg_wm_base, surface);
xdg_toplevel = xdg_surface_get_toplevel(xdg_surface);
xdg_toplevel_set_title(xdg_toplevel, "weston-demo");
wl_surface_commit(surface);             // 提交初始表面

buffer = create_shm_buffer(width, height);
wl_surface_attach(surface, buffer, 0, 0);
wl_surface_damage(surface, 0, 0, width, height);
wl_surface_commit(surface);             // 提交第一帧

while (wl_display_dispatch(display) != -1) {
    // 处理事件,等待 frame callback 或输入事件
}

在这个流程中,wl_display_roundtrip 非常关键。它让客户端发送请求后等待合成器处理完并返回事件,从而确保 registry 中的全局对象已经全部到达。如果没有 roundtrip,客户端可能在 compositor 或 seat 尚未绑定时就尝试创建 surface,导致协议错误。

在 Weston 中调试 Wayland 客户端时,可以设置 WAYLAND_DEBUG=1 环境变量。它会把客户端与合成器之间的所有请求和事件打印到终端,是理解协议交互顺序最直接的工具。

五、常见误区与复盘

  • 误区一:把 Wayland 当成 X11 的替代品。Wayland 不是 X Server 的重新实现,它没有全局坐标、没有窗口树、没有 X 属性。客户端不能查询其他窗口的位置,也不能直接抓取屏幕。
  • 误区二:认为客户端可以直接画到屏幕。客户端只能画到自己的 wl_surface 和 wl_buffer 中,最终显示由合成器决定。合成器可以缩放、旋转、裁剪甚至丢弃某一帧。
  • 误区三:忽略对象生命周期。Wayland 对象有明确的创建和销毁顺序。例如 xdg_toplevel 必须在 xdg_surface 之后创建,surface 销毁前必须先销毁其角色对象,否则会触发协议错误。
  • 误区四:在错误线程中调用 wl_display_dispatch。Wayland 连接不是线程安全的。多线程客户端需要把事件分发集中在一个线程,或者使用 wl_display_prepare_read 等机制进行同步。
在嵌入式 Weston 环境中,如果客户端启动后没有窗口显示,优先检查三件事:XDG_RUNTIME_DIR 是否设置正确、WAYLAND_DISPLAY 是否指向 Weston 的 socket、以及客户端是否成功绑定了 xdg_wm_base。很多“黑屏”问题并不是渲染错误,而是协议对象没有正确创建。

总结来看,Wayland 协议基础可以浓缩为一条主线:客户端通过 wl_display 连接合成器,通过 wl_registry 发现全局对象,通过 wl_surface 和 wl_buffer 提交内容,通过 wl_seat 接收输入,通过 xdg_wm_base 获得桌面窗口语义。掌握这些核心对象及其请求/事件关系,就掌握了 Weston 嵌入式桌面开发中所有上层框架(Qt、GTK、EFL、SDL)与合成器交互的底层逻辑。