文章目录
如果你记得第2.1章,每个请求和事件都与一个对象ID相关联,但到目前为止,我们还没有讨论对象是如何创建的。当我们收到Wayland消息时,我们必须知道对象ID代表什么接口才能解码它。我们还必须以某种方式协商可用的对象、创建新对象以及将ID分配给它们。在Wayland中,我们同时解决了这两个问题——当我们绑定一个对象ID时,我们在所有未来的消息中同意它所使用的接口,并将对象ID到接口的映射存储在我们的本地状态中。
为了引导这些,服务器提供了一个全局对象的列表。这些全局对象通常根据其自身的优点提供信息和功能,但最常用于代理其他对象以满足各种目的,例如创建应用程序窗口。这些全局对象本身也有自己的对象ID和接口,我们必须以某种方式分配和同意。
现在你可能想到了鸡和蛋的问题,我将揭示秘密技巧:当你建立连接时,对象ID 1已经隐式地分配给wl_display接口。当你回想这个接口时,请注意wl_display::get_registry请求:
<interface name="wl_display" version="1">
<request name="sync">
<arg name="callback" type="new_id" interface="wl_callback" />
</request>
<request name="get_registry">
<arg name="registry" type="new_id" interface="wl_registry" />
</request>
<!-- ... -->
</interface>
wl_display::get_registry请求可用于将对象ID绑定到wl_registry接口,该接口是在wayland.xml中发现的下一个接口。考虑到wl_display始终具有对象ID 1,以下电报消息应该有意义(在大端序中):
C->S 00000001 000C0001 00000002 .... .... ....
当我们分解这个消息时,第一个数字是对象ID。第二个数字最重要的16位是消息的总长度(以字节为单位),最不重要的位是请求操作码。其余的字(只有一个)是参数。简而言之,这是对对象ID 1(wl_display)上的请求1(0索引)的调用,它接受一个参数:新对象的生成ID。请注意,在XML文档中,这个新的ID是提前定义的,由wl_registry接口管理:
<interface name="wl_registry" version="1">
<request name="bind">
<arg name="name" type="uint" />
<arg name="id" type="new_id" />
</request>
<event name="global">
<arg name="name" type="uint" />
<arg name="interface" type="string" />
<arg name="version" type="uint" />
</event>
<event name="global_remove">
<arg name="name" type="uint" />
</event>
</interface>
我们将在后面的章节中讨论这个接口。
5.1 绑定全局对象
在创建registry对象时,服务器将为每个可用的全局对象发出全局事件。然后,你可以绑定到你需要的全局对象。
绑定是将已知对象分配一个ID的过程。一旦客户端像这样绑定到registry,服务器就会多次发出全局事件,以宣传它支持哪些接口。这些全局对象中的每一个都被分配了一个唯一的名称,作为无符号整数。接口字符串映射到在协议中找到的接口名称:来自上面的XML文件的wl_display就是一个这样的名称。版本号也在这里定义-有关接口版本控制的更多信息,请参阅附录C。
要绑定到这些接口中的任何一个,我们使用bind请求,这个请求的工作方式与我们绑定到wl_registry的神奇过程类似。例如,考虑以下电报协议交换:
C->S 00000001 000C0001 00000002 .... .... ....
S->C 00000002 001C0000 00000001 00000007 .... .... .... ....
776C5f73 686d0000 00000001 wl_s hm.. ....
[...]
C->S 00000002 00100000 00000001 00000003 .... .... .... ....
第一条消息与我们之前分析的消息完全相同。第二条消息来自服务器:对象2(客户端在第一条消息中将其分配给wl_registry)操作码0(“global”),参数为1,“wl_shm”和1-分别是名称、接口和此全局的版本。客户端通过在对象ID 2(wl_registry::bind)上调用操作码0并分配对象ID 3给全局名称1来响应-绑定到wl_shm全局。未来这个对象的事件和请求由wl_shm协议定义,可以在wayland.xml中找到。
一旦你创建了这个对象,你可以利用它的接口来完成各种任务-在wl_shm的情况下,管理客户端和服务器之间的共享内存。本书的大部分剩余部分都致力于解释这些全局的用法。
有了这些信息,我们可以编写我们的第一个有用的Wayland客户端:一个简单地打印服务器上所有可用全局的客户端。
#include <stdint.h>
#include <stdio.h>
#include <wayland-client.h>
static void
registry_handle_global(void *data, struct wl_registry *registry,
uint32_t name, const char *interface, uint32_t version)
{
printf("interface: '%s', version: %d, name: %d\n",
interface, version, name);
}
static void
registry_handle_global_remove(void *data, struct wl_registry *registry,
uint32_t name)
{
// This space deliberately left blank
}
static const struct wl_registry_listener
registry_listener = {
.global = registry_handle_global,
.global_remove = registry_handle_global_remove,
};
int
main(int argc, char *argv[])
{
struct wl_display *display = wl_display_connect(NULL);
struct wl_registry *registry = wl_display_get_registry(display);
wl_registry_add_listener(registry, ®istry_listener, NULL);
wl_display_roundtrip(display);
return 0;
}
请随意参考前面的章节来解释这个程序。我们连接到显示器(第4.1章),获取registry(本章节),向其添加一个监听器(第3.4章),然后来回传递,通过处理全局事件来打印此合成器上可用的全局变量。亲自尝试一下:
$ cc -o globals -lwayland-client globals.c
注意:在本章节最后一次我们将以十六进制的形式显示电报协议转储,可能也是最后一次一般性地看到它们。在你的程序运行之前,将环境中的WAYLAND_DEBUG变量设置为1,这是更好的追踪Wayland客户端或服务器的方法。现在就用“globals”程序试试吧!
5.2 注册全局变量
使用libwayland-server注册全局变量有所不同。当您使用wayland-scanner生成“服务器代码”时,它会创建接口(类似于监听器)和用于发送事件的粘合代码。第一个任务是注册全局变量,使用一个函数来设置一个资源1,当全局变量被绑定时。就代码而言,结果看起来像这样:
static void
wl_output_handle_bind(struct wl_client *client, void *data,
uint32_t version, uint32_t id)
{
struct my_state *state = data;
// TODO
}
int
main(int argc, char *argv[])
{
struct wl_display *display = wl_display_create();
struct my_state state = { ... };
// ...
wl_global_create(wl_display, &wl_output_interface,
1, &state, wl_output_handle_bind);
// ...
}
如果您将此代码,例如修补到第4.1章的服务器示例中,您将使“globals”程序能够看到我们上次编写的wl_output全局。但是,任何尝试绑定到此全局的尝试都将遇到我们的TODO。为了填补这一空白,我们需要提供wl_output接口的实现。
static void
wl_output_handle_resource_destroy(struct wl_resource *resource)
{
struct my_output *client_output = wl_resource_get_user_data(resource);
// TODO: Clean up resource
remove_to_list(client_output->state->client_outputs, client_output);
}
static void
wl_output_handle_release(struct wl_client *client, struct wl_resource *resource)
{
wl_resource_destroy(resource);
}
static const struct wl_output_interface
wl_output_implementation = {
.release = wl_output_handle_release,
};
static void
wl_output_handle_bind(struct wl_client *client, void *data,
uint32_t version, uint32_t id)
{
struct my_state *state = data;
struct my_output *client_output = calloc(1, sizeof(struct client_output));
struct wl_resource *resource = wl_resource_create(
client, &wl_output_interface, wl_output_interface.version, id);
wl_resource_set_implementation(resource, &wl_output_implementation,
client_output, wl_output_handle_resource_destroy);
client_output->resource = resource;
client_output->state = state;
// TODO: Send geometry event, et al
add_to_list(state->client_outputs, client_output);
}
这一大段内容需要我们逐一解释。在底部,我们将“bind”处理程序扩展为创建用于跟踪此对象的服务器端状态的wl_resource(使用客户端分配的ID)。在此过程中,我们通过将指向接口实现(wl_output_implementation)的指针传递给wl_resource_create。这是一个常量静态结构,在文件中定义。类型(struct wl_output_interface)由wayland-scanner生成,包含每个此接口支持的请求的函数指针。我们还借此机会分配一个小容器来存储任何我们需要的额外状态,这些状态是libwayland无法为我们处理的,具体性质因协议而异。
注意
:这里有两个截然不同的东西,它们共享相同的名称:struct wl_output_interface是一个接口的实例,其中wl_output_interface是由wayland-scanner生成的包含与实现相关的元数据的全局常量变量(如版本,在上面的示例中)。
我们的wl_output_handle_release函数在客户端发送释放请求时被调用,表明客户端不再需要此资源-因此我们销毁它。这反过来触发了wl_output_handle_resource_destroy函数,稍后我们将扩展该函数以释放之前为其分配的任何状态。此函数还作为析构函数传递给wl_resource_create,如果客户端终止而没有显式发送释放请求,则将调用该函数。
我们代码中剩下的另一个“TODO”是发送“name”事件以及其他几个事件。如果我们查看wayland.xml,我们会在接口上看到这个事件:
<event name="geometry">
<description summary="properties of the output">
The geometry event describes geometric properties of the output.
The event is sent when binding to the output object and whenever
any of the properties change.
The physical size can be set to zero if it doesn't make sense for this
output (e.g. for projectors or virtual outputs).
</description>
<arg name="x" type="int" />
<arg name="y" type="int" />
<arg name="physical_width" type="int" />
<arg name="physical_height" type="int" />
<arg name="subpixel" type="int" enum="subpixel" />
<arg name="make" type="string" />
<arg name="model" type="string" />
<arg name="transform" type="int" enum="transform" />
</event>注意:在此处显示wl_output::geometry仅用于说明目的,但在实践中使用时需要考虑一些特殊情况。在您的客户端或服务器中实现此事件之前,请先查看协议XML。
1资源表示每个客户端对象的实例在服务器端的状态。
2如果您对更复杂的版本感兴趣,我们的“globals”程序称为weston-info,可从Weston 项目中获取。
当输出被绑定时,发送此事件似乎是我们的责任。这很容易添加:
static void
wl_output_handle_bind(struct wl_client *client, void *data,
uint32_t version, uint32_t id)
{
struct my_state *state = data;
struct my_output *client_output = calloc(1, sizeof(struct client_output));
struct wl_resource *resource = wl_resource_create(
client, &wl_output_implementation, wl_output_interface.version, id);
wl_resource_set_implementation(resource, wl_output_implementation,
client_output, wl_output_handle_resource_destroy);
client_output->resource = resource;
client_output->state = state;
wl_output_send_geometry(resource, 0, 0, 1920, 1080,
WL_OUTPUT_SUBPIXEL_UNKNOWN, "Foobar, Inc",
"Fancy Monitor 9001 4K HD 120 FPS Noscope",
WL_OUTPUT_TRANSFORM_NORMAL);
add_to_list(state->client_outputs, client_output);
}
注意:在此处显示wl_output::geometry仅用于说明目的,但在实践中使用时需要考虑一些特殊情况。在您的客户端或服务器中实现此事件之前,请先查看协议XML。
1资源表示每个客户端对象的实例在服务器端的状态。
2如果您对更复杂的版本感兴趣,我们的“globals”程序称为weston-info,可从Weston 项目中获取。