本页内容

自定义扩展

“自定义扩展”介绍了如何实现一个自定义的 Wayland 扩展。

为 Wayland 编写新扩展非常简单。这些扩展采用基于 XML 的格式进行定义,wayland-scanner 工具会将其转换为 C 语言的胶合代码。Qt 通过qtwaylandscanner 进一步扩展了这一功能,该工具会生成额外的 Qt 和 C++ 胶合代码。

自定义合成器,包含两个分别标记为“QML Client”和“C++ Client”的客户端窗口,并为旋转和弹跳请求提供了彩色面板

“自定义扩展”示例演示了如何使用这些工具来扩展 Wayland 协议,并在 Wayland 客户端与服务器之间发送自定义请求和事件。

该示例包含四个部分:

  • 协议本身的定义。
  • 一个支持该扩展的合成器。
  • 一个支持该扩展的基于 C++ 的客户端。
  • 一个支持该扩展的基于 QML 的客户端。

协议定义

XML 文件custom.xml 定义了该协议。该文件的基名必须与其根元素(protocol )的name 属性相匹配。它包含一个名为“qt_example_extension”的接口。这是服务器将广播的名称,客户端将通过此名称进行连接,以便发送请求和接收事件。 该名称应具有唯一性,因此建议使用前缀将其与官方接口区分开来。

一个接口通常由两种类型的远程过程调用组成:请求和事件。“请求”是指客户端在服务器端发起的调用,“事件”是指服务器在客户端发起的调用。

示例扩展包含一组请求,这些请求指示服务器对客户端窗口应用特定的变换。例如,如果客户端发送一个“bounce”请求,则服务器应通过让窗口在屏幕上弹跳来响应此请求。

同样,该示例扩展还包含一组事件,服务器可通过这些事件向客户端发出指令。例如,“set_font_size”事件就是一条指令,要求客户端将其默认字体大小设置为特定尺寸。

该协议定义了请求和事件的存在,以及它们所接受的参数。当在该协议上运行qtwaylandscanner 时,它将生成所需的代码,用于对过程调用及其参数进行序列化,并通过连接传输这些内容。在另一端,这将转化为对一个虚拟函数的调用,该函数可以被实现以提供实际的响应。

为了让qtwaylandscanner 作为构建流程的一部分自动运行,我们使用CMake函数qt_generate_wayland_protocol_server_sources() 和qt_generate_wayland_protocol_client_sources(),分别生成服务器端和客户端的胶合代码。 (当使用qmake 时,通过WAYLANDSERVERSOURCES 和WAYLANDCLIENTSOURCES 变量也能实现相同效果。)

合成器实现

合成器应用程序本身使用 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=QWaylandSurface::fromResource(wl_surface);
    qDebug() << "server received bounce" << surface << duration;
   emitbounce(surface,duration);
}

此外,子类为每个事件都定义了槽,以便这些槽既可以从 QML 调用,也可以连接到信号。这些槽只是调用生成的函数,从而将事件发送给客户端。

voidCustomExtension::setFontSize(QWaylandSurface*surface,uint pixelSize)
{
    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) => {
            const item = comp.itemForSurface(surface)
            item.isCustom = true
        }

        onBounce: (surface, ms) => {
            const item = comp.itemForSurface(surface)
            item.doBounce(ms)
        }

        onSpin: (surface, ms) => {
            const item = comp.itemForSurface(surface)
            item.doSpin(ms)
        }

        onCustomObjectCreated: (obj) => {
            const item = customObjectComponent.createObject(output.surfaceArea, { "obj": obj } )
        }
    }

    function setDecorations(shown) {
        for (const elem of itemList) {
            if (elem.isCustom)
                custom.showDecorations(elem.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 事件。其他功能的支持留作读者练习。

示例项目 @ code.qt.io

© 2026 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.