当前位置: 首页 > 工具软件 > Wayland > 使用案例 >

Wayland 第四章 Wayland协议及操作模型 官网翻译

欧阳德运
2023-12-01

基本原则

Wayland 协议是一种异步的面向对象的协议。所有请求都是对某个对象的方法调用。请求包括对象在服务器上的唯一标识—对象 ID。每个对象实现一个接口,并且包括一个操作码(用于标识要调用接口中的哪个方法)的请求。

该协议是基于消息的。客户端向服务器发送的消息称为请求。从服务器到客户端的消息称为事件。一条消息有许多参数,每个参数都有一个特定的类型(有关参数类型列表,请参阅“Wire Format”一节)。

此外,协议指定了一些列名称与特定数字枚举值相关联的枚举(说白了就是有特定含义的枚举量)。这些本质上只是描述性的:在线格式(the wire format)级别枚举只是整数(后期需调整,意义不大准确)。但它们也用于增强类型安全性,或以其他方式添加用于语言绑定,或其他此类型的代码。只有在引入这些属性之前编写的代码在引入之后仍然有效时,才支持后一种用法;换句话说,添加枚举不应破坏 API,否则会使向后兼容性面临风险。

枚举可以定义为一组整数或位域。这是通过枚举定义中的位域布尔属性来定义的。如果此属性为真,则可以通过按位运算的方式来访问该枚举,例如,可以将任意多个属性通过OR运算放到一起;如果它是假的,或者属性被省略,那么枚举参数只是一个数值序列。

enum 属性可用于 uint 或 int 参数,但是如果将 enum 定义为位域,则它只能用于 uint args。

服务器将事件发送回客户端,并且每个事件都是从一个对象发出的。事件可以是错误条件。事件包括对象 ID 和事件操作码,客户端可以从中确定事件的类型。事件是为了响应请求(在这种情况下请求和事件构成往返)或在服务器状态更改时自发生成的。

在保持连接的情况下,状态信息下是广播形式的;只要连接状态发送改变,事件就会被广播出去。客户端必须侦听这些更改并缓存状态,而无需(或机制)查询服务器状态。

服务器将广播多个全局对象的存在,这些对象又将广播它们的当前状态。

代码生成

接口、请求和事件在protocol/wayland.xml 中定义。此 xml 用于生成可由客户端和合成器使用的函数原型。

协议入口点是作为内联函数生成的,这些函数只是包装了 wl_proxy_*函数。内联函数不是库 ABI 的一部分,程序绑定应该为根据xml 的协议入口点生成自己的存根(stubs)。

电线格式

该协议通过 UNIX 域流套接字发送,其中端点通常命名为 wayland-0(尽管它可以通过环境中的 WAYLAND_DISPLAY 进行更改)。从 Wayland 1.15 开始,实现可以选择支持位于文件系统中任意位置的服务器套接字端点,方法是将 WAYLAND_DISPLAY 设置为服务器端点侦听的绝对路径。

每条消息都由 32 位字构成;大小端采用主机的字节顺序。消息头中有 2 个字:

  • 第一个字是发送者的对象 ID(32 位)。

  • 第二个有 2 个 16 位部分。高 16 位是以字节为单位的消息大小,从头开始(即它的最小值为 8 = header size)。低位是请求/事件操作码。

负载(即消息体)描述请求/事件参数。每个参数始终保持 32 位对齐。在需要填充的地方,填充字节的值是未定义的。没有描述类型的前缀,但它是从 xml 规范中隐式推断出来的。

参数类型的表示如下:

int, uint : 该值是有符号/无符号整数的 32 位值。

fixed : 带符号的 24.8 十进制数。它是一种有符号十进制类型,提供一个符号位、23 位整数精度和 8 位十进制精度。这被公开为一个不透明的结构,在 C API 端拥有与 double 和 int 之间的转换助手。

string : 以无符号 32 位长度开始,后跟字符串内容,包括终止空字节,然后填充到 32 位边界(即32bit对齐)。

object : 32 位对象 ID。

new_id : 32 位对象 ID。一般情况下,新对象使用的接口是从xml推断出来的,但在没有定义的情况下,new_id被提前预设(???)为一个指定接口名称的字符串,一个指定版本的uint。

array : 包含两个部分: 32 位的数组大小标识,以及数组内容(保持32 位边界对齐,不足则填充)。

fd :文件描述符不存储在消息缓冲区中,而是存储在 UNIX 域套接字 的辅助数据中(msg_control)。

接口

该协议包括几个用于与服务器交互的接口。如上所述,每个接口都提供请求、事件和错误(实际上只是特殊事件)。特定的合成器实现可能有自己的接口作为扩展提供,但总有几个是预期存在的。

核心接口:

wl_display : 核心全局对象
wl_registry : 全局注册对象
wl_callback : 回调对象
wl_compositor:合成器单例
wl_shm_pool : 共享内存池
wl_shm : 共享内存支持
wl_buffer : wl_surface 的内容
wl_data_offer : 提供传输数据
wl_data_source : 提供传输数据
wl_data_device : 数据传输设备
wl_data_device_manager : 数据传输接口
wl_shell : 创建桌面风格的表面
wl_shell_surface : 桌面风格的元数据界面
wl_surface : 屏幕表面
wl_seat : 输入设备组
wl_pointer : 指针输入设备
wl_keyboard : 键盘输入设备
wl_touch : 触摸屏输入设备
wl_output : 合成器输出区域
wl_region : 区域界面
wl_subcompositor : 次表面 合成器
wl_subsurface: wl_surface 的次表面接口

版本控制 (未翻译)

每个接口都是版本化的,每个协议对象都实现其接口的特定版本。对于全局对象,服务器支持的最大版本与全局对象一起公布,创建的协议对象的实际版本由传递给 wl_registry.bind()的版本参数决定。对于不是全局对象的对象,它们的版本是从创建它们的对象推断出来的。

为了让事情保持清晰,这对接口版本有一些隐藏约定:

  • 对象创建层次结构必须是一棵树。否则,从父对象推断对象版本变得难以正确跟踪。

  • 当一个接口的版本增加时,它的父版本也会增加(递归直到你到达一个全局接口)

  • 全局接口的版本号就像它所有子接口的计数器。每当子接口被修改时,全局父接口的版本号也会增加(见上文)。然后子接口采用与其父全局接口的新版本相同的版本号。

为了说明上述内容,请考虑 wl_compositor接口。它有两个孩子,wl_surfacewl_region。从 wayland 1.2 版开始,wl_surfacewl_compositor 都是版本 3。如果向wl_region 接口添加了一些东西,wl_regionwl_compositor 都会被提升到版本 4。如果之后 wl_surface 被改变,wl_compositorwl_surface 都将变成版本 5。通过这种方式,全局接口版本被用作其所有子接口的一种“计数器”。这使得在给定父级版本的情况下知道子级的版本变得非常简单。子项处于可能的最高接口版本,该版本小于或等于其父项的版本。
上述版本控制方案存在一个特殊的例外情况。 wl_display(以及扩展 wl_registry)接口不能改变,因为它是核心协议对象,它的版本从不公布,也没有请求不同版本的机制。

连接时间

没有固定的连接设置信息,服务器在连接时发出多个事件,以指示全局对象的存在和属性:输出、合成器、输入设备。

安全和认证

大多数情况下关于访问底层缓冲区,需要新的 drm auth 机制(授予 ioctl 的想法),需要检查 cmd 流?

获取服务器套接字取决于合成器类型,可以是系统范围的名称,通过 fd 在会话 dbus 上传递。或者客户端是通过已有的合成器fork出来的,那么 fd 已经打开了。

创建对象

每个对象都有一个唯一的 ID。 ID 由创建对象的实体(客户端或服务器)分配。客户端分配的ID在[1, 0xfeffffff]范围内,而服务器分配的ID在[0xff000000, 0xffffffff]范围内。 0 ID 保留用于表示空或不存在的对象。出于效率目的,ID 被密集打包(ID连续分配),因为在使用 N-1 之前不会使用 ID N。任何不维护此属性的 ID 分配算法都与 libwayland 中的实现不兼容。

合成器

合成器是一个全局对象,在连接时公开。

有关协议说明,请参阅名为“wl_compositor - 合成器单例”的部分。

表面

表面用于管理客户端创建用于在屏幕上显示他们的内容矩形的像素网格。客户端不知道其表面的全局位置,也无法访问其他客户端的表面。

一旦客户端完成写入像素,它就会“提交”缓冲区;这允许合成器访问缓冲区并读取像素。当合成器访问完成时,它将缓冲区释放回客户端。

有关协议说明,请参阅名为“wl_surface - 屏幕表面”的部分。

输入

输入设备组(seat)代表一组输入设备,包括鼠标、键盘和触摸屏。它有一个键盘和指针焦点。输入设备组是全局对象。指针事件以表面局部坐标系进行传递。

当按下按钮时,合成器会保持隐式抓取,以确保相应的按钮释放事件被传递到同一表面。但是客户端没有办法明确地抓取。相反,表面可以映射为“弹出”,它将瞬态窗口语义与指针抓取相结合。

为了避免竞争条件,可能会触发进一步请求(例如按钮按下、按键事件、指针运动)的输入事件带有序列号,而诸如 wl_surface.set_popup之类的请求需要指定触发事件的序列号。服务器为这些序列号维护一个单调递增的计数器。

输入事件还带有毫秒级的时间戳。它们的基数未定义,因此无法与系统时间(通过clock_gettimegettimeofday 获得)进行比较。不过,它们可以相互比较,例如用于将按钮按下序列识别为双击或三次单击。

有关协议说明,请参阅名为“wl_seat - 输入设备组”的部分。

谈论:

  • 键盘映射,事件变化

  • Wayland上的xkb

  • 多指针Wayland

当表面是输入设备的指针焦点时,表面可以改变指针图像。当指针进入表面时,Wayland 不会自动更改指针图像,但希望应用程序设置它想要的光标以响应指针焦点和运动事件。基本原理是,无论如何,客户端必须管理表面内 作为UI 元素的指针图像(光标形状)以响应运动事件,因此我们将使其作为设置或更改指针图像的唯一机制。如果在表面失去指针焦点后服务器收到设置指针图像的请求,则该请求将被忽略。对于客户端来说,这看起来像是成功设置了指针图像。

将指针图像设置为 NULL 会导致光标隐藏。

当没有表面具有该设备的指针焦点时,合成器会将指针图像恢复为默认图像。

如果指针从一个设置了特殊指针图像的窗口移动到一个没有设置图像,作为响应运动事件的指针怎么办?新表面将被特殊的指针图像卡住。我们不能在离开表面时恢复指针图像,因为如果我们立即进入设置不同图像的表面,图像会闪烁。如果客户端在指针进入表面时未设置指针图像,则指针将保留在最后更改它的表面设置的图像上,甚至可能隐藏。这样的客户端很可能只是坏掉了。(说了鼠标图标的处理思路,确实如此)

输出

输出是一个全局对象,在连接变化时(连接、断开)时通告。

有关协议说明,请参阅名为“wl_output - 合成器输出区域”的部分。

  • 在一个大(合成器)坐标系中布局

  • 基本上是在 Wayland 上的 xrandr

  • 需要在合成器坐标系中的位置

  • 使用事件来通知可用模式;使用请求来移动和更改模式

客户端之间的数据共享

Wayland 协议为客户端提供了一种共享数据的机制,允许实现复制粘贴和拖放。提供数据的客户端创建一个 wl_data_source 对象,获取数据的客户端将其视为 wl_data_offer 对象。该接口允许客户端就相互支持的 mime 类型达成一致,并通过通过协议传递的文件描述符传输数据。

下一节将解释数据源和数据提议对象之间的协商。名为“数据设备”的部分解释了如何使用实现复制粘贴和拖放支持的 wl_data_device 接口创建这些对象并将其传递给不同的客户端。

有关协议说明,请参见“wl_data_offer - 提供传输数据”部分、“wl_data_source - 提供传输数据”部分、“wl_data_device - 数据传输设备”部分和“wl_data_device_manager - 数据传输接口”部分。

MIME 在 RFC 的 2045-2049 中定义。 MIME 类型的注册表由 Internet 号码分配机构 (IANA) 维护。

客户端之间的数据共享

Wayland 协议为客户端提供了一种共享数据的机制,允许实现复制粘贴和拖放。提供数据的客户端创建一个 wl_data_source 对象,获取数据的客户端将其视为 wl_data_offer 对象。该接口允许客户端就相互支持 mime 类型,并通过文件描述符(根据协议传递的)来传输数据。

下一节将解释数据源和数据提供对象之间的协商机制。名为“数据设备”的部分解释了如何使用 wl_data_device 来创建对象并将其传递给不同的客户端,以实现复制粘贴和拖放支持。

有关协议说明,请参见“wl_data_offer - 提供传输数据”部分、“wl_data_source - 提供传输数据”部分、“wl_data_device - 数据传输设备”部分和“wl_data_device_manager - 数据传输接口”部分。

MIME 在 RFC 的 2045-2049 中定义。 MIME 类型的注册表由 Internet 号码分配机构 (IANA) 维护。

数据协商

向其他客户端提供数据的客户端将创建一个 wl_data_source对象,并通过 wl_data_source.offer 请求为其支持该数据的格式通告 mime 类型。在接收端,数据提供对象将为每种支持的 mime 类型生成一个 wl_data_offer.offer 事件。

实际数据传输发生在接收客户端发送 wl_data_offer.receive 请求时。此请求采用 MIME 类型和文件描述符作为参数。此请求将使用相同的参数在发送客户端上生成 wl_data_source.send 事件,并且期望后一个客户端使用所选的 mime 类型将其数据写入给定的文件描述符。

数据设备

数据设备将数据源和提供者粘合在一起。数据设备与 wl_seat 相关联,并由客户端使用 wl_data_device_manager 工厂对象获取,该工厂对象还负责创建数据源。

客户端通过 wl_data_device.data_offer 事件获知新的数据提供者信息。生成此事件后,数据提供者将通告可用的 MIME 类型。新的数据提供者被优先引入用于复制粘贴或拖放。

抉择

每个数据设备都有一个选择数据源。客户端使用设备管理器创建数据源对象,并可以将其设置为给定数据设备的当前选择。每当当前选择更改时,具有键盘焦点的客户端都会收到 wl_data_device.selection事件。此事件也在客户端收到键盘焦点之前立即生成。

数据提供者是在选择事件之前通过 wl_data_device.data_offer 事件引入的。

拖放

使用 wl_data_device.start_drag 请求开始拖放操作。此请求会导致指针抓取,从而在数据设备上生成进入、运动和离开事件。将数据源作为参数提供给 start_drag;与其关联的数据提供者将被提供给 wl_data_device.enter 事件中的客户端表面。在 wl_data_device.data_offer 事件之前,数据提供者被优先引入客户端。

客户端应通过使用它接受的 mime 类型调用 wl_data_offer.accept 请求来向数据发送客户端提供反馈。如果接收客户端不支持任何通告的 MIME 类型,则应为接受请求参数应该为NULL。接受请求使发送客户端接收到具有 mime 类型的 wl_data_source.target 事件。

当拖动结束时,接收客户端会收到一个 wl_data_device.drop 事件,需在该事件中使用 wl_data_offer.receive 请求传输数据。

说明

本文档来自于官方文档:Chapter 4. Wayland Protocol and Model of Operation
这篇翻译得真辣鸡,不少概念自己都没消化,后面消化了再来修改了。

 类似资料: