[TOC]
我们在1.3章简单介绍过Wayland库——这是最流行的Wayland实现。本书的大部分内容适用于任何实现,但我们将用接下来的两章来让您熟悉这一实现。
Wayland软件包包括用于wayland-client和wayland-server的pkg-config规范——请查阅您的构建系统文档以了解如何链接它们。当然,大多数应用程序只会链接到其中一个。该库包括一些简单的原语(例如链表)和预先编译的wayland.xml版本——这是核心的Wayland协议。
我们将从介绍原语开始。
3.1 Wayland库的实用程序原语
在客户端和服务器库中常见的是wayland-util.h,它定义了许多结构、实用程序函数和宏,为Wayland应用程序建立了一些原语。其中包括:
- 用于在生成的代码中序列化和反序列化Wayland协议消息的结构
- 一个链表(wl_list)实现
- 一个数组(wl_array)实现(与相应的Wayland原语相关联)
- 用于转换Wayland标量(例如定点数)和C类型的实用程序
- 从libwayland内部冒泡信息的调试日志设施
该头文件本身包含许多注释,其中包含相当好的文档-您应该自己阅读它们。我们将在接下来的几页中详细介绍如何应用这些原语。
3.2 wayland-scanner
Wayland软件包附带一个二进制文件:wayland-scanner。此工具用于从第2.3章讨论的Wayland协议XML文件生成C头文件和粘合代码。该工具在“wayland”包的构建过程中用于预生成核心协议wayland.xml的头文件和粘合代码。头文件成为wayland-client-protocol.h和wayland-server-protocol.h,不过你通常包括wayland-client.h和wayland-server.h而不是直接使用这些。
此工具的使用相当简单(可通过wayland-scanner -h进行总结),但可以总结如下。要生成客户端头文件:
wayland-scanner client-header < protocol.xml > protocol.h
要生成服务器端头文件:
wayland-scanner server-header < protocol.xml > protocol.h
要生成粘合代码:
wayland-scanner private-code < protocol.xml > protocol.c
不同的构建系统将采用不同的方法来配置自定义命令-请查阅您构建系统的文档。一般来说,您想在构建时运行wayland-scanner,然后将您的应用程序编译并链接到粘合代码。
如果您现在手头有任何Wayland协议,请继续进行(例如,wayland.xml可能在/usr/share/wayland中可用)。打开粘合代码和头文件,并在阅读以下章节时参考它,以了解libwayland中提供的原语如何在生成的代码中实际应用。
3.3 代理和资源
对象是客户端和服务器双方都知晓的实体,它具有一定的状态,通过协商在电线上进行更改。在客户端方面,libwayland通过wl_proxy接口引用这些对象。这些都是对抽象对象的实际友好的C“代理”,并提供间接供客户端使用的函数,以便将请求转换为电线的格式。如果您查看wayland-client-core.h文件,您会发现几个用于此目的的低级函数。一般来说,您不直接使用这些。
在服务器上,通过wl_resource对对象进行抽象,这是非常相似的,但具有额外的复杂性-服务器必须跟踪哪个对象属于哪个客户端。每个wl_resource由单个客户端拥有。除此之外,该接口非常相似,并为向相关客户端发送Marshall事件提供低级抽象。您在服务器上直接使用wl_resource的频率将比在客户端上直接使用wl_proxy的频率更高。这样使用的一个例子是,当您正在上下文之外操作资源时,获取对拥有该资源的wl_client的引用,或者在客户端尝试执行无效操作时发送协议错误。
再往上一层是另一组更高级别的接口,大多数Wayland客户端和服务器都与之交互以完成其大部分任务。我们将在下一部分中介绍它们。
3.4 接口和监听器
最后,我们达到了libwayland抽象的顶峰:接口和监听器。前几章讨论的想法-wl_proxy和wl_resource以及原始类型-是单例实现,它们存在于libwayland中,并且它们的存在为这一层提供支持。当您通过wayland-scanner运行XML文件时,它生成接口和监听器,以及它们之间和低级电报协议接口之间的粘合代码,所有这些都是针对每个高级协议中的接口的。
请记住,Wayland连接上的每个参与者都可以接收和发送消息。客户端正在监听事件并发送请求,而服务器正在监听请求并发送事件。每一方都在使用一个恰当地称为wl_listener的接口来监听另一方的消息。以下是这个接口的一个例子:
struct wl_surface_listener {
/** surface enters an output */
void (*enter)(void *data,
struct wl_surface *wl_surface,
struct wl_output *output);
/** surface leaves an output */
void (*leave)(void *data,
struct wl_surface *wl_surface,
struct wl_output *output);
};
这是wl_surface的客户端监听器。wayland-scanner用于生成此监听器的XML是:
<interface name="wl_surface" version="4">
<event name="enter">
<arg name="output"
type="object"
interface="wl_output"/>
</event>
<event name="leave">
<arg name="output"
type="object"
interface="wl_output"/>
</event>
<!-- additional details omitted for brevity -->
</interface>
这些事件如何成为监听器接口应该相当清楚。每个函数指针都采用一些任意的用户数据,该事件所涉及的资源的引用以及该事件的参数。我们可以将监听器绑定到wl_surface上,如下所示:
static void wl_surface_enter(void *data,
struct wl_surface *wl_surface, struct wl_output *output) {
// ...
}
static void wl_surface_leave(void *data,
struct wl_surface *wl_surface, struct wl_output *output) {
// ...
}
static const struct wl_surface_listener surface_listener = {
.enter = wl_surface_enter,
.leave = wl_surface_leave,
};
// ...
struct wl_surface *surf;
wl_surface_add_listener(surf, &surface_listener, NULL);
wl_surface接口还定义了一些客户端可以为该表面提出的请求:
<interface name="wl_surface" version="4">
<request name="attach">
<arg name="buffer"
type="object"
interface="wl_buffer"
allow-null="true"/>
<arg name="x" type="int"/>
<arg name="y" type="int"/>
</request>
<!-- additional details omitted for brevity -->
</interface>
wayland-scanner生成以下原型,以及Marshals此消息的粘合代码。
void wl_surface_attach(struct wl_surface *wl_surface,
struct wl_buffer *buffer, int32_t x, int32_t y);
接口和监听器在服务器端代码上是相同的,但方向相反-它为请求生成监听器,并为事件生成粘合代码。当libwayland收到消息时,它会查找对象ID及其接口,然后使用该ID解码消息的其余部分。然后,它会在该对象上查找监听器并使用消息的参数调用您的函数。
这就是全部!我们经过了几层抽象才到达这里,但您现在应该理解了事件如何在您的服务器代码中启动,如何在电线上成为消息,被客户端理解,并分派到您的客户端代码中。然而,仍有一个未解决的问题。所有这些都假定您已经有了对Wayland对象的引用。您是如何获得这些引用的?
网友评论