自定义扩展
自定义扩展说明了如何实现自定义 Wayland 扩展。
为 Wayland 编写新扩展非常容易。它们使用基于 XML 的格式进行定义,wayland-scanner
工具会将其转换为 C 语言的胶合代码。Qt 在此基础上扩展了qtwaylandscanner
,它能生成 Qt 和 C++ 语言的额外胶合代码。
自定义扩展示例展示了如何使用这些工具扩展 Wayland 协议,并在 Wayland 客户端和服务器之间发送自定义请求和事件。
示例包括四个项目:
- 协议本身的定义。
- 支持扩展的合成器。
- 支持扩展的基于 C++ 的客户端。
- 支持扩展的基于 QML 的客户端。
协议定义
XML 文件custom.xml
定义了协议。它包含一个名为 "qt_example_extension "的接口。这是服务器将广播的名称,客户端将附加该名称以发送请求和接收事件。这个名称应该是唯一的,因此最好使用一个前缀,将其与官方接口区分开来。
接口通常包括两类远程过程调用:请求和事件。"请求 "是客户端在服务器端进行的调用,而 "事件 "是服务器在客户端进行的调用。
示例扩展包含一组请求,指示服务器对客户端窗口应用某些变换。例如,如果客户端发送了一个 "弹跳 "请求,那么服务器就应该做出响应,使窗口在屏幕上弹跳。
同样,服务器也可以使用一组事件为客户端提供指令。例如,"set_font_size "事件是一条指令,要求客户端将默认字体大小设置为特定大小。
协议定义了请求和事件的存在,以及它们的参数。在qtwaylandscanner
上运行时,它将生成所需的代码,对存储过程调用及其参数进行编译,并通过连接进行传输。在另一端,这将成为对一个虚拟函数的调用,该函数可用于提供实际响应。
为了让qtwaylandscanner
作为编译的一部分自动运行,我们使用 CMake 函数qt_generate_wayland_protocol_server_sources()和qt_generate_wayland_protocol_client_sources()分别生成服务器端和客户端的胶水代码。(当使用qmake
时,WAYLANDSERVERSOURCES
和WAYLANDCLIENTSOURCES
变量的作用相同)。
合成器的实现
Compositor 应用程序本身使用 QML 和Qt Quick 实现,但扩展程序使用 C++ 实现。
第一步是为qtwaylandscanner
生成的胶水代码创建一个子类,以便我们可以访问其功能。我们将QML_ELEMENT 宏添加到该类中,以便通过 QML 对其进行访问。
class CustomExtension : public QWaylandCompositorExtensionTemplate<CustomExtension> , public QtWaylandServer::qt_example_extension { Q_OBJECT QML_ELEMENT
除了继承生成的类外,我们还继承了QWaylandCompositorExtensionTemplate 类,该类使用奇妙重复模板模式(Curiously Recurring Template Pattern)处理扩展时提供了一些额外的便利。
请注意,QWaylandCompositorExtensionTemplate 必须在继承列表中排在第一位,因为它是一个基于QObject 的类。
子类重新实现了生成基类中的虚拟函数,我们可以在其中处理客户端发出的请求。
protected: void example_extension_bounce(Resource *resource, wl_resource *surface, uint32_t duration) override;
在这些重新实现中,我们只需将请求转换为信号发射,这样就能在合成器的实际 QML 代码中进行处理。
voidCustomExtension::example_extension_bounce(QtWaylandServer::qt_example_extension::Resource*resource,wl_resource*wl_surface,uint32_t duration) { Q_UNUSED(resource);autosurface=::fromResource(wl_surface). QWaylandSurface::fromResource(wl_surface); qDebug() << "server received bounce" << surface << duration; emitbounce(surface,duration); }
此外,子类还为每个事件定义了插槽,以便从 QML 调用或连接到信号。这些槽可以简单地调用生成的函数,将事件发送到客户端。
voidCustomExtension::setFontSize(QWaylandSurface*surface、 uintpixelSize) {if(surface) { Resource*target =resourceMap().value(surface->waylandClient());if(target) { qDebug() << "Server-side extension sending setFontSize:" << pixelSize; send_set_font_size(target->handle, surface->resource(),pixelSize); } } }
由于我们在类定义中添加了QML_ELEMENT 宏(并在构建系统文件中添加了相应的构建步骤),因此它可以在 QML 中实例化。
我们将其作为WaylandCompositor 对象的直接子对象,以便合成器将其注册为扩展。
CustomExtension { id: custom onSurfaceAdded: (surface) => { var item = itemForSurface(surface) item.isCustom = true } onBounce: (surface, ms) => { var item = itemForSurface(surface) item.doBounce(ms) } onSpin: (surface, ms) => { var item = itemForSurface(surface) item.doSpin(ms) } onCustomObjectCreated: (obj) => { var item = customObjectComponent.createObject(defaultOutput.surfaceArea, { "color": obj.color, "text": obj.text, "obj": obj } ) } } function setDecorations(shown) { var n = itemList.length for (var i = 0; i < n; i++) { if (itemList[i].isCustom) custom.showDecorations(itemList[i].surface.client, shown) } }
该对象有信号处理器来处理它可能从客户端收到的请求,并对这些请求做出相应的反应。此外,我们还可以调用它的插槽来发送事件。
onFontSizeChanged: { custom.setFontSize(surface, fontSize) }
C++ 客户端实现
两个客户端共享接口的 C++ 实现。与合成器一样,我们为生成的代码创建了一个子类,该子类也继承自一个模板类。在本例中,我们继承了 QWaylandClientExtensionTemplate。
class CustomExtension : public QWaylandClientExtensionTemplate<CustomExtension> , public QtWayland::qt_example_extension
这种方法与合成器的方法非常相似,只是颠倒了一下:请求是以槽的形式实现的,槽会调用生成的函数,而事件则是我们重新实现的虚拟函数,以发出信号。
void CustomExtension::sendBounce(QWindow *window, uint ms) { QtWayland::qt_example_extension::bounce(getWlSurface(window), ms); }
客户端代码本身非常简单,只是为了展示如何触发行为。在自定义绘制事件中,它会绘制一组矩形和标签。当其中任何一个被点击时,它就会向服务器发出请求。
void mousePressEvent(QMouseEvent *ev) override { if (rect1.contains(ev->position())) doSpin(); else if (rect2.contains(ev->position())) doBounce(); else if (rect3.contains(ev->position())) newWindow(); else if (rect4.contains(ev->position())) newObject(); }
为了在收到set_font_size
事件时更新字体大小,我们将扩展类中的信号连接到一个插槽。
connect(m_extension, &CustomExtension::fontSize, this, &TestWindow::handleSetFontSize);
该槽将更新字体大小并重新绘制窗口。
QML 客户端实现
QML 客户端与 C++ 客户端类似。它依赖与 C++ 客户端相同的自定义扩展实现,并在 QML 中实例化以启用它。
CustomExtension { id: customExtension onActiveChanged: { registerWindow(topLevelWindow) } onFontSize: (window, pixelSize) => { topLevelWindow.fontSize = pixelSize } }
用户界面由一些可点击的矩形组成,并使用TapHandler 在矩形被点击时发送相应请求。
TapHandler { onTapped: { if (customExtension.active) customExtension.sendBounce(topLevelWindow, 1000) } }
为简单起见,示例仅演示了bounce
和spin
请求以及set_font_size
事件。添加对其他功能的支持留待读者练习。
© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.