自定义外壳
“自定义 Shell”介绍了如何实现自定义 Shell 扩展。
Wayland 的Shell 扩展是用于管理窗口状态、位置和大小的协议。大多数合成器都会支持一种或多种内置扩展,但在某些情况下,编写一个包含应用程序所需确切功能的自定义扩展可能会非常有用。

这要求您在 Wayland 连接的服务器端和客户端两端都实现该 Shell 扩展,因此当您正在构建一个平台,并且同时控制着合成器及其客户端应用程序时,此方法才特别有用。
“自定义 Shell”示例展示了简单 Shell 扩展的实现。该示例分为三个部分:
- 自定义 Shell 接口的协议描述。
- 用于在客户端应用程序中连接该接口的插件。
- 一个包含该接口服务器端实现的合成器示例。
该协议描述遵循wayland-scanner 可读取的标准XML格式。本文将不对此进行详细说明,但该协议包含以下功能:
- 用于为
wl_surface创建外壳表面的接口。这使得该协议能够在现有的wl_surfaceAPI基础上扩展功能。 - 用于在 Shell 表面上设置窗口标题的请求。
- 用于将 shell 表面最小化/恢复的请求。
- 一个用于通知客户端 shell 表面当前最小化状态的事件。
为了让qtwaylandscanner 作为构建的一部分自动运行,我们使用 CMake 函数qt_generate_wayland_protocol_server_sources()和qt_generate_wayland_protocol_client_sources()分别生成服务器端和客户端的胶合代码。 (当使用qmake 时,WAYLANDSERVERSOURCES 和WAYLANDCLIENTSOURCES 变量可实现相同效果。)
客户端插件
为了让 Qt 客户端能够发现 shell 集成,我们必须重实现 QWaylandShellIntegrationPlugin。
class QWaylandExampleShellIntegrationPlugin : public QWaylandShellIntegrationPlugin
{
Q_OBJECT
Q_PLUGIN_METADATA(IID QWaylandShellIntegrationFactoryInterface_iid FILE "example-shell.json")
public:
QWaylandShellIntegration *create(const QString &key, const QStringList ¶mList) override;
};
QWaylandShellIntegration *QWaylandExampleShellIntegrationPlugin::create(const QString &key, const QStringList ¶mList)
{
Q_UNUSED(key);
Q_UNUSED(paramList);
return new ExampleShellIntegration();
}这会将“example-shell”键附加到 shell 集成上,并提供了一种机制,当客户端连接到该接口时,可据此实例化ExampleShellIntegration 类。
用于创建 shell 扩展的 API 可在头文件qwaylandclientshellapi_p.h 中找到。
#include <QtWaylandClient/private/qwaylandclientshellapi_p.h>该头文件要求包含私有 API,因为与 Qt 的公共 API 不同,它不保证二进制兼容性。这些 API 仍被视为稳定的,并将保持源代码兼容性,在这方面与 Qt 中的其他插件 API 类似。
ExampleShellIntegration 是如上所述用于创建 shell 表面的客户端入口点。它继承了 QWaylandShellIntegrationTemplate 类,并采用了“奇异递归模板模式”(Curiously Recurring Template Pattern)。
class Q_WAYLANDCLIENT_EXPORT ExampleShellIntegration
: public QWaylandShellIntegrationTemplate<ExampleShellIntegration>
, public QtWayland::qt_example_shell
{
public:
ExampleShellIntegration();
QWaylandShellSurface *createShellSurface(QWaylandWindow *window) override;
};它还继承自QtWayland::qt_example_shell 类,该类由qtwaylandscanner 根据协议的XML描述生成的。
构造函数指定了我们支持的协议版本:
ExampleShellIntegration::ExampleShellIntegration()
: QWaylandShellIntegrationTemplate(/* Supported protocol version */ 1)
{
}example_shell 协议当前为 1 版,因此我们将一个1 传递给父类。这将在协议协商中使用,并确保当合成器使用新版协议时,旧版客户端仍能正常工作。
当 `ExampleShellIntegration ` 初始化完成后,应用程序便已连接到服务器,并接收到了合成器支持的全局接口广播。若初始化成功,应用程序即可向该接口发起请求。 在此情况下,仅需支持一种请求:创建一个 shell 表面。它使用内置函数wlSurfaceForWindow() 将 QWaylandWindow 转换为wl_surface ,随后发出该请求。接着,它使用一个ExampleShellSurface 对象扩展返回的表面,该对象将处理qt_example_shell_surface 接口上的请求和事件。
QWaylandShellSurface *ExampleShellIntegration::createShellSurface(QWaylandWindow *window)
{
if (!isActive())
return nullptr;
auto *surface = surface_create(wlSurfaceForWindow(window));
return new ExampleShellSurface(surface, window);
}ExampleShellSurface 继承了两个类。
class ExampleShellSurface : public QWaylandShellSurface
, public QtWayland::qt_example_shell_surface第一个是QtWayland::qt_example_shell_surface 类,该类是根据协议的XML描述生成的。它为协议中的事件提供了虚函数,并为请求提供了普通成员函数。
QtWayland::qt_example_shell_surface 类仅包含一个事件。
void example_shell_surface_minimize(uint32_t minimized) override;ExampleShellSurface 重写了该函数以更新其内部窗口状态。当窗口状态发生变化时,它会将待处理状态暂存起来,随后在QWaylandShellSurface 中调用 `applyConfigureWhenPossible() `。状态、大小和位置的更改应按此方式组织。这样,我们就能确保这些更改不会干扰到绘制表面,并且可以轻松地将多个相关更改作为一个整体应用。
当可以安全地重新配置渲染面时,将调用虚拟函数applyConfigure() 。
void ExampleShellSurface::applyConfigure()
{
if (m_stateChanged)
QWindowSystemInterface::handleWindowStateChanged(platformWindow()->window(), m_pendingStates);
m_stateChanged = false;
}此时,我们会将新的(最小化或恢复原状)状态实际应用到窗口上。
第二个超类是QWaylandShellSurface 。这是 Wayland 的 QPA 插件和 QWaylandWindow 用于与 shell 通信的接口。ExampleShellSurface 也重新实现了该接口中的几个虚函数。
bool wantsDecorations() const override;
void setTitle(const QString &) override;
void requestWindowStates(Qt::WindowStates states) override;
void applyConfigure() override;例如,当 Qt 应用程序设置窗口标题时,这会转化为对setTitle() 虚拟函数的调用。
void ExampleShellSurface::setTitle(const QString &windowTitle)
{
set_window_title(windowTitle);
}在ExampleShellSurface 中,这进而会转化为对我们自定义外壳表面接口的一个请求。
合成器
本示例的最后一部分是合成器本身。其总体结构与其他合成器示例相同。有关合成器构成模块的更多详细信息,请参阅“Minimal QML”示例Qt Wayland Compositor。
“自定义 Shell”组合器的一个显著区别在于 Shell 扩展的实例化。在“最小 QML”示例中,会实例化 Shell 扩展IviApplication 、XdgShell 和WlShell ,而“自定义 Shell”示例仅创建了ExampleShell 扩展的一个实例。
ExampleShell {
id: shell
onShellSurfaceCreated: (shellSurface) => {
shellSurfaces.append({shellSurface: shellSurface});
}
}我们将 shell 扩展的实例作为WaylandCompositor 的直接子项创建,以便将其注册为全局接口。当客户端连接时,该接口将被广播出去,客户端将能够按照上一节所述的方式附加到该接口上。
ExampleShell 是生成的QtWaylandServer::qt_example_shell 接口的子类,该接口包含协议XML中定义的API。它也是QWaylandCompositorExtensionTemplate 的子类,这确保了这些对象会被QWaylandCompositor 识别为扩展。
class ExampleShell
: public QWaylandCompositorExtensionTemplate<ExampleShell>
, QtWaylandServer::qt_example_shell这种双重继承是Qt Wayland Compositor 在构建扩展时的一种典型模式。QWaylandCompositorExtensionTemplate 类在QWaylandCompositorExtension 与由qtwaylandscanner 生成的qt_example_shell 类之间建立了关联。
等效地,ExampleShellSurface 类同时继承了生成的QtWaylandServer::qt_example_shell_surface 类和QWaylandShellSurfaceTemplate 类,这使其成为ShellSurface 类的子类,并建立了Qt Wayland Compositor 与生成的协议代码之间的关联。
为了使该类型可供Qt Quick 使用,我们出于方便起见使用了Q_COMPOSITOR_DECLARE_QUICK_EXTENSION_CLASS 预处理器宏。其中一项功能是,当该扩展被添加到Qt Quick 图中时,该宏会自动处理其初始化。
voidExampleShell::initialize()
{
QWaylandCompositorExtensionTemplate::initialize();
QWaylandCompositor*compositor = static_cast<QWaylandCompositor*>(extensionContainer());
if(!compositor) {
qWarning() << "Failed to find QWaylandCompositor when initializing ExampleShell";
return;
}
init(compositor->display(), 1);
}initialize() 函数的默认实现会将该扩展注册到合成器中。除此之外,我们还会初始化协议扩展本身。我们通过调用QtWaylandServer::qt_example_shell_surface 类中生成的init() 函数来实现这一点。
我们还重新实现了为surface_create 请求生成的虚拟函数。
void ExampleShell::example_shell_surface_create(Resource *resource, wl_resource *surfaceResource, uint32_t id)
{
QWaylandSurface *surface = QWaylandSurface::fromResource(surfaceResource);
if (!surface->setRole(ExampleShellSurface::role(), resource->handle, QT_EXAMPLE_SHELL_ERROR_ROLE))
return;
QWaylandResource shellSurfaceResource(wl_resource_create(resource->client(), &::qt_example_shell_surface_interface,
wl_resource_get_version(resource->handle), id));
auto *shellSurface = new ExampleShellSurface(this, surface, shellSurfaceResource);
emit shellSurfaceCreated(shellSurface);
}每当客户端通过连接发出该请求时,都会调用此虚函数。
我们的 Shell 扩展仅支持一个QWaylandSurfaceRole ,但在为其创建 Shell 表面时,将其分配给QWaylandSurface 仍然非常重要。 这样做的主要原因是,为同一表面分配冲突的角色会被视为协议错误,而如果发生这种情况,由合成器负责触发该错误。在采用该表面时为其设置角色,可确保如果该表面稍后被赋予不同角色而重复使用,系统将触发协议错误。
我们使用内置函数在 Wayland 和 Qt 类型之间进行转换,并创建一个 `ExampleShellSurface ` 对象。当一切准备就绪后,我们会发出 `shellSurfaceCreated() ` 信号,该信号随后会在 QML 代码中被拦截,并添加到 shell 表面列表中。
ExampleShell {
id: shell
onShellSurfaceCreated: (shellSurface) => {
shellSurfaces.append({shellSurface: shellSurface});
}
}在ExampleShellSurface 中,我们相应地启用了协议扩展中的shell surface部分。
运行示例
为了使客户端能够成功连接到新的 Shell 扩展,需要处理一些配置细节。
首先,客户端必须能够找到该 Shell 扩展的插件。 一种简单的方法是将QT_PLUGIN_PATH 设置为指向插件的安装目录。由于Qt会按类别查找插件,因此插件路径应指向包含wayland-shell-integration 类别目录的父目录。因此,如果已安装的文件为/path/to/build/plugins/wayland-shell-integration/libexampleshellplugin.so ,则应按如下方式设置QT_PLUGIN_PATH :
export QT_PLUGIN_PATH=/path/to/build/plugins有关配置插件目录的其他方法,请参阅插件文档。
最后一步是确保客户端确实挂载到了正确的 Shell 扩展。Qt XML 客户端会自动尝试挂载内置的 Shell 扩展,但可以通过将环境变量QT_WAYLAND_SHELL_INTEGRATION 设置为要加载的扩展名称来覆盖此行为。
export QT_WAYLAND_SHELL_INTEGRATION=example-shell就这样,全部完成了。“自定义 Shell”示例是一个功能有限的 Shell 扩展,仅包含极少数功能,但可作为构建专用扩展的起点。
© 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.