Benutzerdefinierte Erweiterung
Custom Extension zeigt, wie man eine benutzerdefinierte Wayland-Erweiterung implementiert.
Es ist einfach, neue Erweiterungen für Wayland zu schreiben. Sie werden in einem XML-basierten Format definiert und das Werkzeug wayland-scanner
wandelt dies in Glue-Code in C um. Qt erweitert dies mit qtwaylandscanner
, das zusätzlichen Glue-Code in Qt und C++ erzeugt.
Das Beispiel für benutzerdefinierte Erweiterungen zeigt, wie man diese Werkzeuge verwendet, um das Wayland-Protokoll zu erweitern und benutzerdefinierte Anfragen und Ereignisse zwischen einem Wayland-Client und einem Server zu senden.
Das Beispiel besteht aus vier Elementen:
- Die Definition des Protokolls selbst.
- Einem 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. Sie enthält eine Schnittstelle namens "qt_example_extension". Dies ist der Name, der vom Server gesendet wird und an den sich der Client anhängt, um Anfragen zu senden und Ereignisse zu empfangen. Dieser Name sollte eindeutig sein, daher ist es gut, ein Präfix zu verwenden, das ihn von den offiziellen Schnittstellen unterscheidet.
Eine Schnittstelle besteht in der Regel aus zwei Arten von Remote Procedure Calls: Anforderungen und Ereignisse. "Requests" sind Aufrufe, die der Client auf der Server-Seite macht, und "Events" sind Aufrufe, die der Server auf der Client-Seite macht.
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"-Anforderung sendet, sollte der Server darauf reagieren, indem er das Fenster auf dem Bildschirm hüpfen lässt.
Außerdem gibt es eine Reihe von Ereignissen, die der Server nutzen kann, um dem Client Anweisungen zu geben. So ist beispielsweise das Ereignis "set_font_size" eine Anweisung an den Client, seine Standardschriftgröße auf eine bestimmte Größe einzustellen.
Das Protokoll definiert die Existenz von Anfragen und Ereignissen sowie die Argumente, die sie annehmen. Wenn qtwaylandscanner
darauf ausgeführt wird, generiert es den Code, der benötigt wird, um den Prozeduraufruf und seine Argumente zu marshallieren und über die Verbindung zu übertragen. Auf der anderen Seite wird daraus ein Aufruf an eine virtuelle Funktion, die implementiert werden kann, um die eigentliche Antwort zu liefern.
Um qtwaylandscanner
automatisch als Teil des Builds laufen zu lassen, verwenden wir die CMake-Funktionen qt_generate_wayland_protocol_server_sources() und qt_generate_wayland_protocol_client_sources() zur Erzeugung des serverseitigen bzw. clientseitigen Glue-Codes. (Bei der Verwendung von qmake
erreichen die Variablen WAYLANDSERVERSOURCES
und WAYLANDCLIENTSOURCES
das Gleiche).
Die Compositor-Implementierung
Die Compositor-Anwendung selbst ist mit QML und Qt Quick implementiert, aber die Erweiterung ist in C++ implementiert.
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 das Makro QML_ELEMENT zu der Klasse 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 zusätzliche Annehmlichkeiten beim Umgang mit Erweiterungen bietet, indem sie das Curiously Recurring Template Pattern verwendet.
Beachten Sie, dass QWaylandCompositorExtensionTemplate an erster Stelle in der Vererbungsliste stehen muss, da es sich um eine QObject-basierte Klasse handelt.
Die Unterklasse verfügt über Re-Implementierungen virtueller Funktionen in der generierten Basisklasse, mit denen wir von einem Client ausgegebene Anfragen bearbeiten können.
protected: void example_extension_bounce(Resource *resource, wl_resource *surface, uint32_t duration) override;
In diesen Re-Implementierungen übersetzen wir die Anforderung einfach in eine Signalemission, so dass 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, so dass diese entweder von QML aus 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*Oberfläche, 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 zu den Build-System-Dateien hinzugefügt haben), kann es in QML instanziiert werden.
Wir machen es zu einem direkten Kind des WaylandCompositor Objekts, damit der Compositor es als Erweiterung registrieren kann.
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) } }
Das Objekt verfügt über Signalhandler für die Anfragen, die es vom Client erhält, und reagiert entsprechend auf diese. Darüber hinaus können wir seine Slots aufrufen, um Ereignisse zu senden.
onFontSizeChanged: { custom.setFontSize(surface, fontSize) }
Die C++-Client-Implementierung
Beide Clients teilen sich 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 von QWaylandClientExtensionTemplate.
class CustomExtension : public QWaylandClientExtensionTemplate<CustomExtension> , public QtWayland::qt_example_extension
Der Ansatz ist dem des Compositors sehr ähnlich, nur in umgekehrter Form: Anfragen werden als Slots implementiert, die die generierten Funktionen aufrufen, und Ereignisse als virtuelle Funktionen, die wir neu implementieren, um Signale auszusenden.
void CustomExtension::sendBounce(QWindow *window, uint ms) { QtWayland::qt_example_extension::bounce(getWlSurface(window), ms); }
Der Client-Code selbst ist sehr einfach und soll nur zeigen, wie man das Verhalten auslöst. In einem benutzerdefinierten Malereignis wird eine Reihe von Rechtecken und Beschriftungen gezeichnet. Wenn eines dieser Elemente angeklickt wird, werden Anfragen an den Server gestellt.
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, wenn 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 wird die Schriftgröße aktualisieren und das Fenster neu malen.
Die QML-Client-Implementierung
Der QML-Client ist dem C++-Client ähnlich. Er stützt sich auf die gleiche 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 verwendet TapHandler, um die entsprechenden Anfragen zu senden, wenn ein Rechteck angeklickt wird.
TapHandler { onTapped: { if (customExtension.active) customExtension.sendBounce(topLevelWindow, 1000) } }
Der Einfachheit halber wurde das Beispiel darauf beschränkt, nur die Anforderungen bounce
und spin
sowie das Ereignis set_font_size
zu demonstrieren. Das Hinzufügen von Unterstützung für die zusätzlichen Funktionen wird als Übung für den Leser belassen.
© 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.