现在,我们忽略了Wayland协议如何管理客户端和服务器之间对象的共同所有权的一个重要细节:这些对象是如何创建的。Wayland显示或wl_display在每个Wayland连接上隐式存在。它具有以下接口:
<interface name="wl_display" version="1">
<request name="sync">
<arg name="callback" type="new_id" interface="wl_callback"
summary="callback object for the sync request"/>
</request>
<request name="get_registry">
<arg name="registry" type="new_id" interface="wl_registry"
summary="global registry object"/>
</request>
<event name="error">
<arg name="object_id" type="object" summary="object where the error occurred"/>
<arg name="code" type="uint" summary="error code"/>
<arg name="message" type="string" summary="error description"/>
</event>
<enum name="error">
<entry name="invalid_object" value="0" />
<entry name="invalid_method" value="1" />
<entry name="no_memory" value="2" />
<entry name="implementation" value="3" />
</enum>
<event name="delete_id">
<arg name="id" type="uint" summary="deleted object ID"/>
</event>
</interface>
对于普通Wayland用户来说,这些中最有趣的是get_registry,我们将在下一章中详细讨论它。简而言之,注册表用于分配其他对象。接口的其余部分用于维护连接,除非您正在编写自己的libwayland替代品,否则它们通常不重要。
相反,本章将重点关注libwayland与wl_display对象相关联的一些函数,以建立和维护您的Wayland连接。这些函数用于操作libwayland的内部状态,而不是直接与电报协议请求和事件相关联。
我们将从最重要的函数开始:建立显示器。对于客户端,这将涵盖连接到服务器的实际过程,对于服务器,将是配置显示器以供客户端连接的过程。
4.1 创建display
打开你的文本编辑器——是时候编写我们的第一行代码了。
对于Wayland客户端
连接到Wayland服务器并创建一个wl_display来管理连接状态很容易:
#include <stdio.h>
#include <wayland-client.h>
int
main(int argc, char *argv[])
{
struct wl_display *display = wl_display_connect(NULL);
if (!display) {
fprintf(stderr, "Failed to connect to Wayland display.\n");
return 1;
}
fprintf(stderr, "Connection established!\n");
wl_display_disconnect(display);
return 0;
}
让我们编译并运行这个程序。假设你在阅读这篇文章时使用的是Wayland合成器,结果应该如下所示:
$ cc -o client client.c -lwayland-client
$ ./client
Connection established!
wl_display_connect是客户端建立Wayland连接的最常见方式。其签名是:
struct wl_display *wl_display_connect(const char *name);
“name”参数是Wayland显示器的名称,通常为“wayland-0”。您可以在我们的测试客户端中将NULL替换为该名称并自己尝试一下——很可能会起作用。这与$XDG_RUNTIME_DIR中的Unix套接字名称相对应。然而,在NULL的情况下,libwayland将:
- 如果设置了WAYLAND_DISPLAY,则尝试连接到XDG_RUNTIME_DIR/WAYLAND_DISPLAY
- 否则,尝试连接到XDG_RUNTIME_DIR/wayland-0
- 否则,失败:(
这允许用户通过将$WAYLAND_DISPLAY设置为所需显示来指定要在其上运行客户端的Wayland显示。如果您有更复杂的要求,您还可以自己建立连接并从文件描述符创建Wayland显示:
struct wl_display *wl_display_connect_to_fd(int fd);
无论您是如何创建的显示器,您都可以通过wl_display_get_fd获得wl_display使用的文件描述符。
int wl_display_get_fd(struct wl_display *display);
Wayland服务端
对于服务器来说,这个过程也很简单。显示器的创建和绑定到套接字是分开的,以便在任何客户端能够连接到它之前,您有时间对显示器进行配置。这里再给出一个最小化的示例程序:
#include <stdio.h>
#include <wayland-server.h>
int
main(int argc, char *argv[])
{
struct wl_display *display = wl_display_create();
if (!display) {
fprintf(stderr, "Unable to create Wayland display.\n");
return 1;
}
const char *socket = wl_display_add_socket_auto(display);
if (!socket) {
fprintf(stderr, "Unable to add socket to Wayland display.\n");
return 1;
}
fprintf(stderr, "Running Wayland display on %s\n", socket);
wl_display_run(display);
wl_display_destroy(display);
return 0;
}
编译运行:
$ cc -o server server.c -lwayland-server
$ ./server &
Running Wayland display on wayland-1
$ WAYLAND_DISPLAY=wayland-1 ./client
Connection established!
使用wl_display_add_socket_auto将允许libwayland自动为显示器的名称做出决定,默认为wayland-0,或者wayland-n,具体取决于是否有其他Wayland合成器在XDG_RUNTIME_DIR中有套接字。然而,与客户端一样,您还有一些其他选项可以配置显示器:
int wl_display_add_socket(struct wl_display *display, const char *name);
int wl_display_add_socket_fd(struct wl_display *display, int sock_fd);
添加套接字后,调用wl_display_run将运行libwayland的内部事件循环并阻塞,直到调用wl_display_terminate。这个事件循环是什么?翻页了解!
4.2 加入事件循环
libwayland为Wayland服务器提供了自己的事件循环实现以利用,但维护人员已经认识到这是一个设计上的越位。对于客户端来说,没有这样的等价物。无论如何,Wayland服务器事件循环非常有用,即使它超出了范围。
Wayland服务端事件循环
libwayland-server创建的每个wl_display都有与之对应的wl_event_loop,您可以使用wl_display_get_event_loop来获取其引用。如果您正在编写新的Wayland合成器,您可能希望将其用作您唯一的事件循环。您可以使用wl_event_loop_add_fd向其添加文件描述符,使用wl_event_loop_add_timer添加计时器。它还通过wl_event_loop_add_signal处理信号,这非常方便。
将事件循环配置为您要监视的事件的配置,以响应该合成器必须响应的所有事件,您可以通过调用wl_display_run来一次性处理事件和分发Wayland客户端,这将处理事件循环并阻塞,直到显示器终止(通过wl_display_terminate)。大多数从Wayland开始构建的Wayland合成器(与从X11移植的不同)使用这种方法。
然而,也有可能将Wayland显示纳入您自己的事件循环。wl_display在内部使用事件循环来处理客户端,您可以选择自己监视Wayland事件循环,根据需要分发它,或者完全忽略它并手动处理客户端更新。如果您希望让Wayland事件循环自己照顾自己并将其视为自己事件循环的下属,您可以使用wl_event_loop_get_fd获取可轮询的文件描述符,然后在该文件描述符上有活动时调用wl_event_loop_dispatch来处理事件。您还需要在有需要写入客户端的数据时调用wl_display_flush_clients。
Wayland客户端事件循环
另一方面,libwayland-client没有自己的事件循环。然而,由于通常只有一个文件描述符,因此没有它更容易管理。如果Wayland事件是您的程序期望的唯一类型,那么这个简单的循环就足够了:
while (wl_display_dispatch(display) != -1) {
/* This space deliberately left blank */
}
但是,如果您有更复杂的应用程序,您可以以任何您喜欢的方式构建自己的事件循环,并使用wl_display_get_fd获取Wayland显示的文件描述符。在POLLIN事件发生时,调用wl_display_dispatch处理传入事件。要刷新传出的请求,请调用wl_display_flush。
结尾
此时,您已经具备了设置Wayland显示和处理事件和请求所需的所有上下文。剩下的唯一一步是为与连接的另一方进行交谈分配对象。为此,我们使用注册表。在下章末尾,我们将拥有第一个有用的Wayland客户端!