Auf dieser Seite

Benutzerdefinierte Erweiterung

„Benutzerdefinierte Erweiterung“ zeigt, wie eine benutzerdefinierte Wayland-Erweiterung implementiert wird.

Es ist einfach, neue Erweiterungen für Wayland zu schreiben. Sie werden in einem XML-basierten Format definiert, und das Tool „ wayland-scanner “ konvertiert dieses in Glue-Code in C. Qt erweitert dies mit dem „ qtwaylandscanner “, das zusätzlichen Glue-Code in Qt und C++ generiert.

Benutzerdefinierter Compositor mit zwei Client-Fenstern mit den Bezeichnungen „QML Client“ und „C++ Client“, mit farbigen Feldern für Spin- und Bounce-Anfragen

Das Beispiel „Custom Extension“ zeigt, wie man diese Tools nutzt, um das Wayland-Protokoll zu erweitern und benutzerdefinierte Anfragen und Ereignisse zwischen einem Wayland-Client und einem Server auszutauschen.

Das Beispiel besteht aus vier Elementen:

  • Die Definition des Protokolls selbst.
  • Ein Compositor, der die Erweiterung unterstützt.
  • Ein C++-basierter Client, der die Erweiterung unterstützt.
  • Ein QML-basierter Client, der die Erweiterung unterstützt.

Die Protokolldefinition

Die XML-Datei „ custom.xml “ definiert das Protokoll. Der Basisname der Datei muss mit dem Attribut „ name “ ihres Stammelements (protocol) übereinstimmen. Sie enthält eine Schnittstelle namens „qt_example_extension“. Dies ist der Name, der vom Server gesendet wird und an den sich der Client anmeldet, um Anfragen zu senden und Ereignisse zu empfangen. Dieser Name sollte eindeutig sein; daher empfiehlt es sich, ein Präfix zu verwenden, das ihn von offiziellen Schnittstellen unterscheidet.

Eine Schnittstelle besteht in der Regel aus zwei Arten von Remote-Prozeduraufrufen: Anfragen und Ereignisse. „Anfragen“ sind Aufrufe, die der Client auf der Serverseite ausführt, und „Ereignisse“ sind Aufrufe, die der Server auf der Clientseite ausführt.

Die Beispielerweiterung enthält eine Reihe von Anfragen, die den Server anweisen, bestimmte Transformationen auf das Client-Fenster anzuwenden. Wenn der Client beispielsweise eine „bounce“-Anfrage sendet, sollte der Server darauf reagieren, indem er das Fenster auf dem Bildschirm hüpfen lässt.

Ebenso enthält sie eine Reihe von Ereignissen, mit denen der Server dem Client Anweisungen geben kann. Das Ereignis „set_font_size“ ist beispielsweise eine Anweisung an den Client, seine Standardschriftgröße auf eine bestimmte Größe festzulegen.

Das Protokoll definiert die Existenz von Anfragen und Ereignissen sowie die von ihnen übergebenen Argumente. Wenn „ qtwaylandscanner “ darauf angewendet wird, generiert es den Code, der erforderlich ist, um den Prozeduraufruf und seine Argumente zu marshallen und über die Verbindung zu übertragen. Am anderen Ende wird dies zu einem Aufruf einer virtuellen Funktion, die implementiert werden kann, um die eigentliche Antwort bereitzustellen.

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. clientseitigen Glue-Code zu generieren. (Bei Verwendung von „ qmake “ erreichen die Variablen „ WAYLANDSERVERSOURCES “ und „ WAYLANDCLIENTSOURCES “ dasselbe Ergebnis.)

Die Compositor-Implementierung

Die Compositor-Anwendung selbst wird mit QML und Qt Quick implementiert, die Erweiterung hingegen in C++.

Der erste Schritt besteht darin, eine Unterklasse des von qtwaylandscanner generierten Glue-Codes zu erstellen, damit wir auf dessen Funktionalität zugreifen können. Wir fügen der Klasse das Makro „ QML_ELEMENT “ hinzu, um sie von QML aus zugänglich zu machen.

class CustomExtension  : public QWaylandCompositorExtensionTemplate<CustomExtension>
        , public QtWaylandServer::qt_example_extension
{
    Q_OBJECT
    QML_ELEMENT

Zusätzlich zur Vererbung von der generierten Klasse erben wir auch die Klasse „ QWaylandCompositorExtensionTemplate “, die mithilfe des Curiously Recurring Template Pattern einige zusätzliche Annehmlichkeiten beim Umgang mit Erweiterungen bietet.

Beachten Sie, dass „ QWaylandCompositorExtensionTemplate “ an erster Stelle in der Vererbungsliste stehen muss, da es sich um eine auf „ QObject “ basierende Klasse handelt.

Die Unterklasse enthält Neuimplementierungen der virtuellen Funktionen der generierten Basisklasse, in denen wir die von einem Client ausgegebenen Anfragen bearbeiten können.

protected:
    void example_extension_bounce(Resource *resource, wl_resource *surface, uint32_t duration) override;

In diesen Neuimplementierungen wandeln wir die Anfrage einfach in die Ausgabe eines Signals um, sodass wir sie im eigentlichen QML-Code des Compositors verarbeiten können.

void CustomExtension::example_extension_bounce(QtWaylandServer::qt_example_extension::Resource *resource, wl_resource *wl_surface, uint32_t duration)
{
    Q_UNUSED(resource);
    auto surface = QWaylandSurface::fromResource(wl_surface);
    qDebug() << "server received bounce" << surface << duration;
   emit bounce(surface, duration);
}

Darüber hinaus definiert die Unterklasse Slots für jedes der Ereignisse, sodass diese entweder aus QML aufgerufen oder mit Signalen verbunden werden können. Die Slots rufen einfach die generierten Funktionen auf, die die Ereignisse an den Client senden.

void CustomExtension::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);
        }
    }
}

Da wir das Makro „ QML_ELEMENT “ zur Klassendefinition hinzugefügt haben (und die entsprechenden Build-Schritte in die Build-System-Dateien aufgenommen haben), kann die Klasse in QML instanziiert werden.

Wir machen es zu einem direkten Kind des WaylandCompositor -Objekts, damit der Compositor es als Erweiterung registriert.

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

Das Objekt verfügt über Signal-Handler für die Anfragen, die es vom Client erhalten kann, und reagiert entsprechend darauf. Darüber hinaus können wir seine Slots aufrufen, um Ereignisse zu senden.

            onFontSizeChanged: {
                custom.setFontSize(surface, fontSize)
            }

Die C++-Client-Implementierung

Beide Clients nutzen die C++-Implementierung der Schnittstelle. Wie im Compositor erstellen wir eine Unterklasse des generierten Codes, die ebenfalls von einer Template-Klasse erbt. In diesem Fall erben wir v QWaylandClientExtensionTemplate.

class CustomExtension : public QWaylandClientExtensionTemplate<CustomExtension>
        , public QtWayland::qt_example_extension

Der Ansatz ist dem des Compositors sehr ähnlich, nur umgekehrt: Anfragen werden als Slots implementiert, die die generierten Funktionen aufrufen, und Ereignisse als virtuelle Funktionen, die wir neu implementieren, um Signale auszulösen.

void CustomExtension::sendBounce(QWindow *window, uint ms)
{
    QtWayland::qt_example_extension::bounce(getWlSurface(window), ms);
}

Der Client-Code selbst ist sehr einfach und soll lediglich veranschaulichen, wie das Verhalten ausgelöst wird. In einem benutzerdefinierten „paint“-Ereignis zeichnet er eine Reihe von Rechtecken und Beschriftungen. Wenn auf eines davon geklickt wird, sendet er Anfragen an den Server.

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

Um die Schriftgröße zu aktualisieren, sobald das Ereignis „ set_font_size “ empfangen wird, wird das Signal in unserer Erweiterungsklasse mit einem Slot verbunden.

        connect(m_extension, &CustomExtension::fontSize, this, &TestWindow::handleSetFontSize);

Der Slot aktualisiert die Schriftgröße und lässt das Fenster neu zeichnen.

Die QML-Client-Implementierung

Der QML-Client ähnelt dem C++-Client. Er stützt sich auf dieselbe Implementierung der benutzerdefinierten Erweiterung wie der C++-Client und instanziiert diese in QML, um sie zu aktivieren.

    CustomExtension {
        id: customExtension
        onActiveChanged: {
            registerWindow(topLevelWindow)
        }
        onFontSize: (window, pixelSize) => {
            topLevelWindow.fontSize = pixelSize
        }
    }

Die Benutzeroberfläche besteht aus einigen anklickbaren Rechtecken und nutzt „ TapHandler “, um die entsprechenden Anfragen zu senden, wenn auf ein Rechteck geklickt wird.

            TapHandler {
                onTapped: {
                    if (customExtension.active)
                        customExtension.sendBounce(topLevelWindow, 1000)
                }
            }

Der Einfachheit halber beschränkt sich das Beispiel darauf, die Anfragen „ bounce “ und „ spin “ sowie das Ereignis „ set_font_size “ zu veranschaulichen. Die Implementierung der zusätzlichen Funktionen bleibt als Übung für den Leser.

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.