Auf dieser Seite

Benutzerdefinierte Shell

„Benutzerdefinierte Shell“ zeigt, wie man eine benutzerdefinierte Shell-Erweiterung implementiert.

Shell-Erweiterungen für Wayland sind Protokolle, die den Status, die Position und die Größe von Fenstern verwalten. Die meisten Compositoren unterstützen eine oder mehrere der integrierten Erweiterungen, doch unter bestimmten Umständen kann es sinnvoll sein, eine benutzerdefinierte Erweiterung zu schreiben, die genau die Funktionen enthält, die Ihre Anwendungen benötigen.

Compositor mit dem Qt Quick 3D-Beispiel „Baked Lightmap“ und einem 3D-Raum mit roten, grünen und weißen Wänden sowie dem Kontrollkästchen „Lightmaps verwenden“

Dies setzt voraus, dass Sie die Shell-Erweiterung sowohl auf der Server- als auch auf der Client-Seite der Wayland-Verbindung implementieren. Daher ist dies vor allem dann sinnvoll, wenn Sie eine Plattform entwickeln und sowohl den Compositor als auch dessen Client-Anwendungen kontrollieren.

Das Beispiel „Custom Shell“ zeigt die Implementierung einer einfachen Shell-Erweiterung. Es ist in drei Teile gegliedert:

  • Eine Protokollbeschreibung für eine benutzerdefinierte Shell-Schnittstelle.
  • Ein Plugin zur Anbindung an die Schnittstelle in einer Client-Anwendung.
  • Ein Beispiel-Compositor mit einer serverseitigen Implementierung der Schnittstelle.

Die Protokollbeschreibung folgt dem Standard-XML-Format, das von wayland-scanner gelesen wird. Sie wird hier nicht im Detail behandelt, umfasst jedoch die folgenden Funktionen:

  • Eine Schnittstelle zum Erstellen von Shell-Oberflächen für einen „ wl_surface “. Dadurch kann das Protokoll die bestehenden „ wl_surface “-APIs um zusätzliche Funktionen erweitern.
  • Eine Anforderung zum Festlegen eines Fenstertitels auf der Shell-Oberfläche.
  • Eine Anforderung zum Minimieren bzw. Wiederherstellen der Shell-Oberfläche.
  • Ein Ereignis, das den Client über den aktuellen minimierten Zustand der Shell-Oberfläche informiert.

Damit „ qtwaylandscanner “ automatisch als Teil des Builds ausgeführt wird, verwenden wir die CMake-Funktionen `qt_generate_wayland_protocol_server_sources() ` und `qt_generate_wayland_protocol_client_sources() `, um den serverseitigen bzw. den clientseitigen Glue-Code zu generieren. (Bei Verwendung von „ qmake “ erfüllen die Variablen „ WAYLANDSERVERSOURCES “ und „ WAYLANDCLIENTSOURCES “ denselben Zweck.)

Das Client-Plugin

Damit die Shell-Integration von einem Qt-Client erkannt wird, müssen wir das `QWaylandShellIntegrationPlugin` neu implementieren.

class QWaylandExampleShellIntegrationPlugin : public QWaylandShellIntegrationPlugin
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID QWaylandShellIntegrationFactoryInterface_iid FILE "example-shell.json")

public:
    QWaylandShellIntegration *create(const QString &key, const QStringList &paramList) override;
};

QWaylandShellIntegration *QWaylandExampleShellIntegrationPlugin::create(const QString &key, const QStringList &paramList)
{
    Q_UNUSED(key);
    Q_UNUSED(paramList);
    return new ExampleShellIntegration();
}

Dadurch wird der Schlüssel „example-shell“ an die Shell-Integration angehängt und es wird eine Möglichkeit bereitgestellt, die Klasse „ ExampleShellIntegration “ zu instanziieren, wenn ein Client eine Verbindung zur Schnittstelle herstellt.

Die APIs zum Erstellen von Shell-Erweiterungen sind in der Header-Datei ` qwaylandclientshellapi_p.h` verfügbar.

#include <QtWaylandClient/private/qwaylandclientshellapi_p.h>

Diese Header-Datei erfordert die Einbindung privater APIs, da im Gegensatz zu öffentlichen Qt-APIs keine Garantien hinsichtlich der Binärkompatibilität bestehen. Die APIs gelten dennoch als stabil und bleiben quellkompatibel; in dieser Hinsicht ähneln sie anderen Plugin-APIs in Qt.

Die ` ExampleShellIntegration ` ist der clientseitige Einstiegspunkt für die Erstellung von Shell-Oberflächen, wie oben beschrieben. Sie erweitert die Klasse `QWaylandShellIntegrationTemplate` unter Verwendung des „Curiously Recurring Template Pattern“.

class Q_WAYLANDCLIENT_EXPORT ExampleShellIntegration
        : public QWaylandShellIntegrationTemplate<ExampleShellIntegration>
        , public QtWayland::qt_example_shell
{
public:
    ExampleShellIntegration();

    QWaylandShellSurface *createShellSurface(QWaylandWindow *window) override;
};

Außerdem erbt sie von der Klasse „ QtWayland::qt_example_shell “, die von „ qtwaylandscanner “ auf Basis der XML-Beschreibung des Protokolls generiert wird.

Der Konstruktor gibt die von uns unterstützte Version des Protokolls an:

ExampleShellIntegration::ExampleShellIntegration()
    : QWaylandShellIntegrationTemplate(/* Supported protocol version */ 1)
{
}

Das „example_shell“-Protokoll befindet sich derzeit in Version 1, daher übergeben wir ein ` 1 ` an die übergeordnete Klasse. Dies wird bei der Protokollaushandlung verwendet und stellt sicher, dass ältere Clients weiterhin funktionieren, falls der Compositor eine neuere Version des Protokolls verwendet.

Wenn das ` ExampleShellIntegration ` initialisiert ist, ist die Anwendung mit dem Server verbunden und hat die Übertragung der globalen Schnittstellen erhalten, die der Compositor unterstützt. Bei Erfolg kann sie Anfragen für die Schnittstelle stellen. In diesem Fall gibt es nur eine zu unterstützende Anfrage: das Erstellen einer Shell-Oberfläche. Dabei wird die integrierte Funktion „ wlSurfaceForWindow() “ verwendet, um das `QWaylandWindow` in ein ` wl_surface` umzuwandeln; anschließend wird die Anfrage gesendet. Danach wird die zurückgegebene Oberfläche um ein ` ExampleShellSurface `-Objekt erweitert, das die Anfragen und Ereignisse an der Schnittstelle ` qt_example_shell_surface ` verarbeitet.

QWaylandShellSurface *ExampleShellIntegration::createShellSurface(QWaylandWindow *window)
{
    if (!isActive())
        return nullptr;
    auto *surface = surface_create(wlSurfaceForWindow(window));
    return new ExampleShellSurface(surface, window);
}

Die Klasse „ ExampleShellSurface “ erbt von zwei Klassen.

class ExampleShellSurface : public QWaylandShellSurface
        , public QtWayland::qt_example_shell_surface

Die erste ist die Klasse „ QtWayland::qt_example_shell_surface “, die auf der Grundlage der XML-Beschreibung des Protokolls generiert wird. Diese stellt virtuelle Funktionen für Ereignisse und gewöhnliche Mitgliedsfunktionen für die Anfragen im Protokoll bereit.

Die Klasse „ QtWayland::qt_example_shell_surface “ verfügt nur über ein einziges Ereignis.

    void example_shell_surface_minimize(uint32_t minimized) override;

Die Klasse „ ExampleShellSurface “ implementiert dieses Ereignis neu, um ihren internen Fensterzustand zu aktualisieren. Wenn sich der Fensterzustand ändert, speichert sie den ausstehenden Zustand für später und ruft „ applyConfigureWhenPossible() “ in „ QWaylandShellSurface “ auf. Änderungen an Zustand, Größe und Position sollten auf diese Weise organisiert werden. Auf diese Weise stellen wir sicher, dass Änderungen die Darstellung auf der Oberfläche nicht beeinträchtigen und dass mehrere zusammenhängende Änderungen problemlos als eine einzige angewendet werden können.

Sobald es sicher ist, die Oberfläche neu zu konfigurieren, wird die Funktion `virtual applyConfigure() ` aufgerufen.

void ExampleShellSurface::applyConfigure()
{
    if (m_stateChanged)
        QWindowSystemInterface::handleWindowStateChanged(platformWindow()->window(), m_pendingStates);
    m_stateChanged = false;
}

Hier wird der neue (minimierte oder nicht mehr minimierte) Zustand tatsächlich im Fenster übernommen.

Die zweite Oberklasse ist ` QWaylandShellSurface`. Dies ist die Schnittstelle, die vom Wayland-QPA-Plugin und von `QWaylandWindow` zur Kommunikation mit der Shell verwendet wird. Die Klasse ` ExampleShellSurface ` implementiert ebenfalls einige virtuelle Funktionen aus dieser Schnittstelle neu.

    bool wantsDecorations() const override;
    void setTitle(const QString &) override;
    void requestWindowStates(Qt::WindowStates states) override;
    void applyConfigure() override;

Wenn beispielsweise die Qt-Anwendung den Titel eines Fensters festlegt, führt dies zu einem Aufruf der virtuellen Funktion ` setTitle() `.

void ExampleShellSurface::setTitle(const QString &windowTitle)
{
    set_window_title(windowTitle);
}

In der ` ExampleShellSurface ` führt dies wiederum zu einer Anforderung an unsere benutzerdefinierte Shell-Surface-Schnittstelle.

Der Compositor

Der letzte Teil des Beispiels ist der Compositor selbst. Dieser weist dieselbe allgemeine Struktur auf wie die anderen Compositor-Beispiele. Weitere Details zu den Bausteinen eines Qt Wayland Compositor.

Ein bemerkenswerter Unterschied beim „Custom Shell“-Compositor ist die Instanziierung der Shell-Erweiterung. Während im „Minimal QML“-Beispiel die Shell-Erweiterungen „ IviApplication “, „ XdgShell “ und „ WlShell “ instanziiert werden, erstellt das „Custom Shell“-Beispiel lediglich eine Instanz der Erweiterung „ ExampleShell “.

ExampleShell {
    id: shell
    onShellSurfaceCreated: (shellSurface) => {
        shellSurfaces.append({shellSurface: shellSurface});
    }
}

Wir erstellen die Instanz der Shell-Erweiterung als direktes untergeordnetes Element von „ WaylandCompositor “, damit sie als globale Schnittstelle registriert wird. Diese wird an Clients gesendet, sobald diese eine Verbindung herstellen, und sie können sich, wie im vorherigen Abschnitt beschrieben, an die Schnittstelle anbinden.

Die ` ExampleShell ` ist eine Unterklasse der generierten Schnittstelle ` QtWaylandServer::qt_example_shell `, die die im Protokoll-XML definierte API enthält. Sie ist zudem eine Unterklasse von ` QWaylandCompositorExtensionTemplate`, wodurch sichergestellt wird, dass die Objekte von ` QWaylandCompositor ` als Erweiterungen erkannt werden.

class ExampleShell
        : public QWaylandCompositorExtensionTemplate<ExampleShell>
        , QtWaylandServer::qt_example_shell

Diese doppelte Vererbung ist ein typisches Muster in „ Qt Wayland Compositor “ beim Erstellen von Erweiterungen. Die Klasse „ QWaylandCompositorExtensionTemplate “ stellt die Verbindung zwischen „ QWaylandCompositorExtension “ und der von „ qtwaylandscanner “ generierten Klasse „ qt_example_shell “ her.

Entsprechend erweitert die Klasse „ ExampleShellSurface “ sowohl die generierte Klasse „ QtWaylandServer::qt_example_shell_surface “ als auch „ QWaylandShellSurfaceTemplate “, wodurch sie zu einer Unterklasse der Klasse „ ShellSurface “ wird und die Verbindung zwischen Qt Wayland Compositor und dem generierten Protokollcode herstellt.

Um den Typ für „ Qt Quick “ verfügbar zu machen, verwenden wir der Einfachheit halber das Präprozessor-Makro „ Q_COMPOSITOR_DECLARE_QUICK_EXTENSION_CLASS “. Dieses sorgt unter anderem dafür, dass die Erweiterung automatisch initialisiert wird, sobald sie dem „ Qt Quick “-Graphen hinzugefügt wurde.

void ExampleShell::initialize()
{
    QWaylandCompositorExtensionTemplate::initialize();

    QWaylandCompositor*compositor = static_cast<QWaylandCompositor*>(extensionContainer());
    if (!compositor) {
        qWarning() << "Failed to find QWaylandCompositor when initializing ExampleShell";
       return;
    }

    init(compositor->display(), 1);
}

Die Standardimplementierung der Funktion ` initialize() ` registriert die Erweiterung beim Compositor. Darüber hinaus initialisieren wir die Protokollerweiterung selbst. Dazu rufen wir die generierte Funktion ` init() ` in der Klasse ` QtWaylandServer::qt_example_shell_surface ` auf.

Außerdem implementieren wir die für die Anforderung ` surface_create ` generierte virtuelle Funktion neu.

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);
}

Diese virtuelle Funktion wird immer dann aufgerufen, wenn ein Client die Anfrage über die Verbindung stellt.

Unsere Shell-Erweiterung unterstützt zwar nur eine einzige „ QWaylandSurfaceRole “, dennoch ist es wichtig, dass wir diese der „ QWaylandSurface “ zuweisen, wenn wir eine Shell-Oberfläche dafür erstellen. Der Hauptgrund dafür ist, dass die Zuweisung widersprüchlicher Rollen derselben Oberfläche als Protokollfehler gewertet wird und es in der Verantwortung des Compositors liegt, diesen Fehler auszulösen, falls er auftritt. Das Festlegen einer Rolle für die Oberfläche bei deren Übernahme stellt sicher, dass der Protokollfehler ausgelöst wird, falls die Oberfläche später mit einer anderen Rolle wiederverwendet wird.

Wir verwenden integrierte Funktionen, um zwischen Wayland- und Qt-Typen zu konvertieren, und erstellen ein „ ExampleShellSurface “-Objekt. Wenn alles vorbereitet ist, senden wir das Signal „ shellSurfaceCreated() “ aus, das wiederum im QML-Code abgefangen und der Liste der Shell-Oberflächen hinzugefügt wird.

ExampleShell {
    id: shell
    onShellSurfaceCreated: (shellSurface) => {
        shellSurfaces.append({shellSurface: shellSurface});
    }
}

In ` ExampleShellSurface` aktivieren wir entsprechend den Shell-Surface-Teil der Protokoll-Erweiterung.

Ausführen des Beispiels

Damit ein Client erfolgreich eine Verbindung zur neuen Shell-Erweiterung herstellen kann, müssen einige Konfigurationsdetails beachtet werden.

Zunächst muss der Client das Plugin der Shell-Erweiterung finden können. Eine einfache Möglichkeit hierfür besteht darin, die „ QT_PLUGIN_PATH “ so einzustellen, dass sie auf das Installationsverzeichnis des Plugins verweist. Da Qt Plugins nach Kategorien sucht, sollte der Plugin-Pfad auf das übergeordnete Verzeichnis verweisen, das das Verzeichnis für die Kategorie „ wayland-shell-integration “ enthält. Wenn die installierte Datei also „ /path/to/build/plugins/wayland-shell-integration/libexampleshellplugin.so “ lautet, sollten Sie „ QT_PLUGIN_PATH “ wie folgt einstellen:

export QT_PLUGIN_PATH=/path/to/build/plugins

Weitere Möglichkeiten zur Konfiguration des Plugin-Verzeichnisses finden Sie in der Plugin-Dokumentation.

Der letzte Schritt besteht darin, sicherzustellen, dass der Client tatsächlich die richtige Shell-Erweiterung anwählt. Qt-Clients versuchen automatisch, die integrierten Shell-Erweiterungen anzuwählen, dies kann jedoch überschrieben werden, indem die Umgebungsvariable „ QT_WAYLAND_SHELL_INTEGRATION “ auf den Namen der zu ladenden Erweiterung gesetzt wird.

export QT_WAYLAND_SHELL_INTEGRATION=example-shell

Und das war’s auch schon. Das Beispiel „Custom Shell“ ist eine eingeschränkte Shell-Erweiterung mit nur sehr wenigen Funktionen, kann jedoch als Ausgangspunkt für die Entwicklung spezialisierter Erweiterungen dienen.

Beispielprojekt @ 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.