Extension personnalisée
La section « Extension personnalisée » explique comment implémenter une extension Wayland personnalisée.
Il est facile d'écrire de nouvelles extensions pour Wayland. Elles sont définies à l'aide d'un format basé sur XML et l'outil wayland-scanner les convertit en code de liaison en C. Qt complète cette fonctionnalité avec l'qtwaylandscanner, qui génère du code de liaison supplémentaire en Qt et en C++.

L'exemple « Extension personnalisée » montre comment utiliser ces outils pour étendre le protocole Wayland et envoyer des requêtes et des événements personnalisés entre un client Wayland et un serveur.
L'exemple se compose de quatre éléments :
- La définition du protocole lui-même.
- Un compositeur prenant en charge l’extension.
- Un client en C++ prenant en charge l'extension.
- Un client basé sur QML prenant en charge l'extension.
La définition du protocole
Le fichier XML custom.xml définit le protocole. Le nom de base du fichier doit correspondre à l’attribut name de son élément racine (protocol). Il contient une interface appelée « qt_example_extension ». C’est le nom qui sera diffusé par le serveur et auquel le client se connectera pour envoyer des requêtes et recevoir des événements. Ce nom doit être unique ; il est donc recommandé d’utiliser un préfixe qui le distingue des interfaces officielles.
Une interface se compose généralement de deux types d’appels de procédure à distance : les requêtes et les événements. Les « requêtes » sont des appels effectués par le client vers le serveur, tandis que les « événements » sont des appels effectués par le serveur vers le client.
L’extension d’exemple contient un ensemble de requêtes qui ordonnent au serveur d’appliquer certaines transformations à la fenêtre du client. Par exemple, si le client envoie une requête « bounce », le serveur doit y répondre en faisant rebondir la fenêtre à l’écran.
De même, elle comporte un ensemble d’événements que le serveur peut utiliser pour fournir des instructions au client. Par exemple, l’événement « set_font_size » est une instruction demandant au client de définir la taille de police par défaut sur une valeur spécifique.
Le protocole définit l’existence des requêtes et des événements, ainsi que les arguments qu’ils prennent en paramètre. Lorsqu’on exécute « qtwaylandscanner » sur ce protocole, cela génère le code nécessaire pour marshaler l’appel de procédure et ses arguments, puis pour les transmettre via la connexion. À l’autre extrémité, cela se traduit par un appel à une fonction virtuelle qui peut être implémentée pour fournir la réponse effective.
Afin que ` qtwaylandscanner ` s’exécute automatiquement dans le cadre de la compilation, nous utilisons les fonctions CMake ` qt_generate_wayland_protocol_server_sources() ` et `qt_generate_wayland_protocol_client_sources() ` pour générer respectivement le code de liaison côté serveur et côté client. (Lorsque l’on utilise qmake, les variables WAYLANDSERVERSOURCES et WAYLANDCLIENTSOURCES permettent d’obtenir le même résultat.)
L'implémentation du compositeur
L'application Compositor elle-même est implémentée à l'aide de QML et d'Qt Quick, mais l'extension est implémentée en C++.
La première étape consiste à créer une sous-classe du code de liaison généré par qtwaylandscanner afin de pouvoir accéder à ses fonctionnalités. Nous ajoutons la macro QML_ELEMENT à la classe afin de la rendre accessible depuis QML.
class CustomExtension : public QWaylandCompositorExtensionTemplate<CustomExtension>
, public QtWaylandServer::qt_example_extension
{
Q_OBJECT
QML_ELEMENTEn plus d’hériter de la classe générée, nous héritons également de la classe QWaylandCompositorExtensionTemplate, qui offre une certaine facilité supplémentaire lors de la gestion des extensions, grâce au modèle de template récurrent (Curiously Recurring Template Pattern).
Notez que la classe ` QWaylandCompositorExtensionTemplate ` doit figurer en premier dans la liste d’héritage, car il s’agit d’une classe basée sur ` QObject`.
La sous-classe réimplémente les fonctions virtuelles de la classe de base générée, ce qui nous permet de traiter les requêtes émises par un client.
protected:
void example_extension_bounce(Resource *resource, wl_resource *surface, uint32_t duration) override;Dans ces réimplémentations, nous nous contentons de traduire la requête en une émission de signal, afin de pouvoir la traiter dans le code QML proprement dit du compositeur.
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);
}De plus, la sous-classe définit des slots pour chacun des événements, afin que ceux-ci puissent être soit appelés depuis QML, soit connectés à des signaux. Les slots appellent simplement les fonctions générées qui envoient les événements au client.
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);
}
}
}Comme nous avons ajouté la macro QML_ELEMENT à la définition de la classe (et ajouté les étapes de compilation correspondantes aux fichiers du système de compilation), celle-ci peut être instanciée en QML.
Nous en faisons un enfant direct de l’objet ` WaylandCompositor ` afin que le compositeur l’enregistre en tant qu’extension.
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)
}
}L'objet dispose de gestionnaires de signaux pour les requêtes qu'il peut recevoir du client et y réagit en conséquence. De plus, nous pouvons appeler ses slots pour envoyer des événements.
onFontSizeChanged: {
custom.setFontSize(surface, fontSize)
}L'implémentation du client en C++
Les deux clients partagent la mise en œuvre en C++ de l'interface. Comme dans le compositeur, nous créons une sous-classe du code généré qui hérite également d'une classe modèle. Dans ce cas, nous héritons de ` QWaylandClientExtensionTemplate`.
class CustomExtension : public QWaylandClientExtensionTemplate<CustomExtension>
, public QtWayland::qt_example_extensionL'approche est très similaire à celle du compositeur, à la différence près qu'elle est inversée : les requêtes sont implémentées sous forme de slots qui appellent les fonctions générées, et les événements sous forme de fonctions virtuelles que nous réimplémentons pour émettre des signaux.
void CustomExtension::sendBounce(QWindow *window, uint ms)
{
QtWayland::qt_example_extension::bounce(getWlSurface(window), ms);
}Le code client lui-même est très simple et ne vise qu’à montrer comment déclencher le comportement. Dans un événement de dessin personnalisé, il dessine un ensemble de rectangles et d’étiquettes. Lorsqu’on clique sur l’un d’entre eux, il envoie des requêtes au serveur.
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();
}Pour mettre à jour la taille de la police à la réception de l’événement « set_font_size », le signal de notre classe d’extension est connecté à un slot.
connect(m_extension, &CustomExtension::fontSize, this, &TestWindow::handleSetFontSize);Le slot mettra à jour la taille de la police et rafraîchira l'affichage de la fenêtre.
L'implémentation du client QML
Le client QML est similaire au client C++. Il s'appuie sur la même implémentation de l'extension personnalisée que le client C++ et l'instancie en QML pour l'activer.
CustomExtension {
id: customExtension
onActiveChanged: {
registerWindow(topLevelWindow)
}
onFontSize: (window, pixelSize) => {
topLevelWindow.fontSize = pixelSize
}
}L'interface utilisateur se compose de plusieurs rectangles cliquables et utilise la méthode ` TapHandler ` pour envoyer les requêtes correspondantes lorsqu'un rectangle est cliqué.
TapHandler {
onTapped: {
if (customExtension.active)
customExtension.sendBounce(topLevelWindow, 1000)
}
}Par souci de simplicité, cet exemple se limite à illustrer les requêtes bounce et spin, ainsi que l’événement set_font_size. L’ajout de la prise en charge des fonctionnalités supplémentaires est laissé à la discrétion du lecteur.
© 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.