3. Wayland协议概览

Wayland 并不是一个“显示服务器程序”,而是一套客户端与合成器之间的通信协议,以及围绕该协议形成的参考实现与扩展生态。理解 Wayland 多屏显示开发,第一步必须把“谁负责画、谁负责合成、谁负责输出到哪块屏”这三件事分清楚。Wayland 的核心思想是:客户端只负责把内容渲染到缓冲区,合成器负责收集所有客户端的缓冲区,决定它们在哪个输出(output)上、以什么位置和缩放显示,并最终提交给 DRM/KMS 或后端显示系统。

3.1 Wayland 架构:协议层、合成器层与后端层

从工程视角看,Wayland 架构可以拆成三层:协议层定义对象、接口、请求与事件;合成器层实现协议并管理窗口、输入、输出和渲染;后端层负责真正把合成结果送到显示硬件或嵌套窗口。协议层通过 Unix domain socket 传输二进制消息,客户端连接后先绑定 wl_registry,再通过 registry 的 global 事件发现合成器暴露的能力,例如 wl_compositor、wl_shm、wl_seat、wl_output、xdg_wm_base 等。

多屏显示的关键在于:wl_output 代表一个逻辑输出,合成器可以暴露多个 output 全局对象;每个 output 有自己的模式、缩放、变换、位置和名称。客户端通过 wl_output 事件获知这些信息,但不能直接控制输出布局,布局由合成器决定。这是 Wayland 与 X11 的重要差异:X11 客户端可以查询和影响屏幕布局,而 Wayland 中输出管理属于合成器特权。

Wayland 架构:客户端 → 合成器 → 多输出 Wayland 客户端 应用 / 工具包 / 渲染线程 wl_surface + wl_buffer xdg_surface / xdg_toplevel Wayland 合成器 协议实现 + 窗口管理 + 输入 wl_registry / wl_compositor 输出布局 / 缩放 / 变换 后端与显示硬件 DRM/KMS / EGL / Vulkan CRTC / Connector / Plane HDMI / DP / eDP 输出 socket 协议 提交/扫描 多屏显示中的核心对象与关系 wl_output #1 eDP-1 / 1920×1080 wl_output #2 HDMI-A-1 / 2560×1440 输出布局 位置 / 缩放 / 变换 合成器策略 决定窗口落在哪块屏 客户端只提交缓冲区;合成器决定输出目标与布局;后端完成实际扫描输出

3.2 合成器与客户端角色:谁决定窗口出现在哪块屏

在 Wayland 中,客户端角色是“内容生产者”。它创建 wl_surface,附加 wl_buffer,提交 commit,并通过 xdg_surface 等扩展表达窗口语义。客户端可以知道有哪些 output、每个 output 的当前模式与缩放,但通常不能强制指定窗口必须出现在某块屏。合成器角色是“策略与合成中心”:它接收客户端提交的 surface,决定窗口位置、层级、焦点、输入路由,并把多个 surface 合成为最终帧,再分配给各个 output。

多屏场景下,合成器需要维护“输出拓扑”:每块屏的逻辑位置、分辨率、缩放因子、旋转角度以及主屏标记。窗口管理器策略可能包括:新窗口出现在指针所在输出、按应用规则固定到某输出、全屏窗口独占当前输出、跨屏窗口按最大缩放因子渲染等。客户端若想实现多屏适配,应监听 wl_output 的 geometry、mode、scale、done 事件,并在 done 后统一应用变更,避免在事件中途使用不完整状态。

关键理解:Wayland 客户端不是“直接往屏幕上画”,而是“把缓冲区交给合成器”。多屏显示的核心控制权在合成器,客户端通过协议事件感知输出变化并调整自身渲染策略。

3.3 核心协议接口:从连接、注册到表面提交

一个典型的 Wayland 客户端启动流程如下:连接显示 socket,获取 wl_display,绑定 wl_registry,监听 global 事件,绑定所需接口,创建 surface 与 buffer,提交 commit,进入事件循环。下面是一段伪代码式流程,帮助建立接口调用顺序:

wl_display_connect(NULL)
  → wl_display_get_registry(display)
  → wl_registry_add_listener(registry, &registry_listener, NULL)
  → wl_display_roundtrip(display)   // 获取 global 列表
  → bind: wl_compositor, wl_shm, xdg_wm_base, wl_seat, wl_output
  → wl_compositor_create_surface()
  → xdg_wm_base_get_xdg_surface()
  → xdg_surface_get_toplevel()
  → wl_surface_attach(buffer) + wl_surface_commit()
  → wl_display_dispatch(display)    // 事件循环

核心接口可以按职责分组:wl_compositor 负责创建 surface 和 region;wl_shm 提供共享内存缓冲区;wl_seat 管理输入设备与焦点;wl_output 描述输出;xdg_wm_base 提供桌面窗口语义;wl_surface 的 damage、frame、commit 控制重绘与同步。多屏开发中,wl_output 与 xdg_surface 的配合最关键:前者告诉客户端“有哪些屏、参数如何”,后者决定“窗口如何被合成器摆放”。

接口/对象 职责 多屏开发关注点
wl_registry 发现合成器全局对象 获取多个 wl_output 全局
wl_compositor 创建 surface/region 为不同输出准备不同 surface 内容
wl_output 描述输出模式、缩放、位置 监听 mode/scale/geometry/done
xdg_wm_base 桌面窗口语义 全屏、最大化与输出绑定策略
wl_surface 缓冲区提交与同步 按输出缩放调整 buffer scale
wl_seat 输入设备与焦点 多屏下指针进入/离开输出

3.4 多屏显示相关扩展:layer-shell、xdg-output 与 fractional-scale

基础 Wayland 协议只提供 wl_output 的通用描述,真正的多屏桌面集成往往依赖扩展协议。常见扩展包括:xdg-output 提供输出逻辑位置、名称和描述;wlr-layer-shell 允许面板、壁纸、通知等按输出锚定;fractional-scale-v1 支持非整数缩放,解决 1.25、1.5 倍缩放下的模糊问题;wp-viewporter 用于裁剪与缩放 surface;wp-presentation-time 用于帧时序反馈。多屏开发中,若只使用基础协议,客户端只能知道“有输出”,却难以精确知道“输出之间的相对位置”和“窗口应锚定到哪块屏”。

实践建议:开发多屏 Wayland 客户端时,优先绑定 zxdg_output_manager_v1 获取逻辑位置与名称;对高 DPI 混合环境,检查合成器是否支持 wp_fractional_scale_manager_v1;对面板类应用,使用 zwlr_layer_shell_v1 并指定目标 output。

3.5 案例与误区:多屏窗口错位与缩放模糊

假设一台笔记本 eDP-1 为 1920×1080@1.0,外接 HDMI-A-1 为 2560×1440@1.5,合成器将外接屏放在笔记本右侧。客户端若只按 1.0 缩放渲染,窗口移动到外接屏后会模糊;若客户端在收到 wl_output.scale 后立即重建缓冲区,但未等待 done,可能使用半更新状态导致尺寸跳变。正确做法是:在 done 后统一读取输出状态,按目标输出缩放因子设置 wl_surface.set_buffer_scale 或使用 fractional-scale 协议,再提交新缓冲区。

常见误区:把 wl_output 当成“可控制的屏幕对象”,试图在客户端里直接设置输出位置或分辨率。Wayland 客户端没有这个权限;输出配置属于合成器或显示管理服务。客户端应做的是“感知并适配”,而不是“配置硬件”。