在 Linux 桌面领域,Wayland 常被提及为 “X11 的继任者”。事实上,各大主流 Linux 发行版都在加速推进去 “X11 化”,国产桌面系统也在积极转向 Wayland 作为默认显示协议。
然而,与这一趋势形成鲜明对比的是:Wayland 相关的开发资料却异常稀少。除了官方文档以及《The Wayland Protocol》等少数教程外,系统性、成体系的资料并不多,这与 Windows 平台下丰富的图形系统开发书籍形成了明显反差。
我认为,造成这一现象的一个重要原因在于:
绝大多数应用开发者并不直接面对显示协议层,而是通过 Qt、GTK 等成熟的 GUI 框架完成开发,既不需要理解 X11,也无需关心 Wayland 的内部机制。
但并非所有项目都如此。某些底层或跨平台的开源项目(例如 Wine),为了避免引入体量庞大的 GUI 框架依赖,其图形子系统往往直接基于 X11 API 或 Wayland 协议实现。在这种场景下,就必须理解底层显示系统。
在研究 winewayland.drv 的实现过程中,很自然地会将其与 winex11.drv 的实现进行对比。正是在这种对照之下,Wayland 与 X11 在设计理念上的差异显得尤为突出。因此,本文尝试站在 X11 的视角,借助熟悉的概念、对象和调用流程,逐步拆解 Wayland 中的核心对象与接口,希望对读者有所帮助。
在 X11 程序中,几乎所有代码都会从这样一句开始:
Display *dpy = XOpenDisplay(NULL);
在 X11 体系中,Display 对象具有非常核心的地位,它代表了:
可以说,在 X11 编程中,Display * 无处不在,绝大多数 Xlib API 都需要将其作为第一个参数传入。
在 Wayland 中,也存在一个在“形式上”与之相似的对象 —— wl_display:
struct wl_display *display = wl_display_connect(NULL);
但需要特别注意的是,**wl_display 的职责要比 X11 中的 Display 明显少得多。它本质上只是客户端与 compositor 之间的一个纯连接对象**,主要负责:
与 X11 不同的是,wl_display不包含任何显示设备、窗口层级或 UI 语义相关的信息。在 Wayland 编程中,这一点需要时刻牢记:
不要期待像 X11 那样,通过一个全局对象就能获取窗口、屏幕或视觉信息。
在 X11 中,Window 是一个极其重要的对象。只要是图形应用程序,几乎必然会与 Window 打交道:
Window w = XCreateWindow(...);
XMapWindow(dpy, w);
在 X11 语义下,一个 Window 同时承担了多种职责:
可以说,Window 是一个高度“复合”的对象。
而在 Wayland 中,只有一个对象在形式上可以与之类比,即 wl_surface:
struct wl_surface *surface = wl_compositor_create_surface(...);
初次接触 Wayland 时,最难理解的一点往往在于:
wl_surface 本身既没有位置、也没有大小,更没有窗口类型的概念。它只表示一块可以向 compositor 提交 buffer 的平面。
那么,Wayland 中的“窗口”究竟是如何定义的?
答案是:通过为 wl_surface 赋予角色(role)。 这些角色并不定义在 Wayland 核心协议中,而是由扩展协议提供,其中最重要、也是最通用的一个扩展协议就是 XDG shell。
XDG(cross-desktop group,跨桌面工作组)shell 是 Wayland 的一个标准协议扩展,它为 wl_surface 定义了两种最核心的角色(role):
通过这两种角色,客户端可以构建一棵由 surface 组成的树结构:
需要强调的是:
这种“树结构”并不是 X11 中那种由 Server 维护的全局窗口树,而是由协议语义明确约束的 surface 关系模型。
经过 XDG shell 的角色定义之后,Wayland 中才逐步具备了与 X11 类似的“窗口层级”概念,但这种层级是显式、受限且由 compositor 主导的,这正是 Wayland 与 X11 在设计理念上的根本差异之一。
在 X11 中,Pixmap 由客户端通过如下接口创建:
Pixmap XCreatePixmap(Display*, Drawable, width, height, depth);
但需要注意的是,Pixmap 虽然由客户端发起创建,其实际归属却在 X Server 侧。它本质上是由服务器管理的一块像素存储资源,可以在 X11 体系中承担多种角色,包括:
Drawable)在典型的 X11 绘制模型中,客户端并不直接操作像素数据,而是不断向 X Server 发送绘制指令,由服务器在 Pixmap 上执行这些命令,例如:
DrawLineCopyAreaFillRectangle也就是说,Pixmap 是一个可被反复修改的服务器端状态对象,其内容由一系列绘制命令逐步累积而成。
而在 Wayland 中,对应的概念是 wl_buffer。与 Pixmap 不同,wl_buffer 是客户端提供给合成器的像素缓冲描述,它本身不包含任何绘制接口,也不承载绘制语义。
在 Wayland 的设计中,渲染完全由客户端自行完成:客户端先在本地将一帧内容绘制到 buffer 中,通常“一帧对应一个 buffer”,然后通过如下方式提交给合成器:
wl_surface_attach(surface, buffer, 0, 0);
wl_surface_commit(surface);
对于合成器而言,wl_buffer 是一个纯粹的输入结果。 它不关心这块 buffer 是如何生成的(CPU 绘制、OpenGL、Vulkan、零拷贝等),只关心“当前这一帧应该被合成并显示成什么样子”。
在 X11 中,典型的事件循环是这样的:
XEvent ev;
while (1) {
XNextEvent(dpy, &ev);
handle_event(&ev);
}
事件是结构化数据(XEvent),由应用显式拉取并分发。XNextEvent() 做的事情有:
在 Wayland 中,事件处理模型发生了根本变化。代码看起来和 X11 差不多:
while (wl_display_dispatch(state.wl_display)) {
/* This space deliberately left blank */
}
但是,在 Wayland 中没有“事件结构体”,事件不会以返回值的形式交给应用,而是在 dispatch() 内部被直接分发,具体来说,就是调用对应对象上注册的 listener 回调。
所以在 Wayland 上的处理流程是:
无论是 X11 还是 Wayland,本质上都只是一套显示协议。对于大多数开发者而言,通常并不希望直接面对繁琐、低层的协议交互细节,因此才有了 Xlib / XCB 以及 libwayland-client 这样的客户端开发库。它们的定位都偏向“协议访问层”,而非像 Qt / GTK 那样试图提供完整的 GUI 框架解决方案。
Xlib 诞生于 X11 生态的早期阶段,其设计目标是为应用程序提供一套完整且易用的客户端 API,尽可能隐藏 X11 协议细节。它在内部维护了大量隐式状态,使得应用开发变得相对简单,但也因此在可控性、并发模型以及现代硬件适配方面逐渐显露出局限性。
XCB 则被视为对 Xlib 的一次现代化重构。它采用了更“薄”的封装方式,与 X11 协议结构高度贴合,强调异步通信、显式控制和线程友好性。通过 XCB,开发者可以更精细地掌控请求、回复和事件的生命周期,这也使其更适合作为底层组件或复杂系统中的基础库。
相比之下,libwayland-client 是 Wayland 体系中的原生客户端库,其设计目标更加克制而明确:仅提供最小且通用的协议运行与调度能力。它不试图抽象“窗口”、不提供任何绘制或 GUI 相关接口,而是专注于对象管理、协议消息收发以及回调分发。窗口语义、渲染方式以及交互逻辑,全部由上层协议(如 xdg-shell)或应用自身来定义和实现。
从这个角度看,Xlib / XCB 更像是“围绕 X11 协议逐步堆叠出来的客户端 API”,而 libwayland-client 则是一个刻意保持最小化的协议运行时,这也正体现了 X11 与 Wayland 在设计理念上的根本差异。
在展开对比之前,事先说明一下,现在 X 系统中也有合成器。
早期 X11 中,X Server 直接将 Window 内容绘制到屏幕,不支持透明、阴影或动画效果。随着桌面体验需求的提升,X11 通过一系列扩展(XComposite、XDamage、XRender 等)引入了合成能力。在这种架构下,合成器与窗口管理器一样,都是运行在 X Server 之上的普通客户端进程:
[ X Server ]
↑
[ Window Manager + Compositor ]
↑
[ Applications ]
但本文要对比的,并不是“有没有合成器”,而是 X Server 与 Wayland Compositor 在系统中的架构地位和职责边界。
在传统 X11 模型中,X Server 承担了极其繁重且集中的职责:
Wayland 的设计目标之一,就是彻底削减中心节点的复杂度。在 Wayland 中,合成器的职责被刻意限制为:
在这里,窗口管理器的角色也发生了变化:
通过以上对照可以看到,Wayland 并不是在 X11 之上“修修补补”,也不是简单地“换一个 API 名字”,而是一次从架构层面重新定义显示系统边界的尝试:
从 X11 的角度理解 Wayland,最大的收获并不在于记住新的 API,而在于意识到:
显示系统不再是替应用画图的服务器,而是协调各个应用提交结果的调度者。
正是建立起这样的认知框架,才能避免简单地将 winex11 的实现思路机械地平移到 winewayland 之上。只有在理解 Wayland 设计初衷的前提下,才能读懂 winewayland 中的实现是对 Wayland 架构原则的自然遵循。