Sur cette page

Extension personnalisée

Custom Extension montre 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 XML et l'outil wayland-scanner les convertit en code de collage en C. Qt développe cela avec qtwaylandscanner, qui génère du code de collage supplémentaire en Qt et en C++.

L'exemple Custom Extension 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 qui prend en charge l'extension.
  • Un client basé sur C++ qui prend en charge l'extension.
  • Un client basé sur QML qui supporte l'extension.

La définition du protocole

Le fichier XML custom.xml définit le protocole. Il contient une interface appelée "qt_example_extension". Il s'agit du nom qui sera diffusé par le serveur et auquel le client s'attachera pour envoyer des requêtes et recevoir des événements. Ce nom doit être unique, il est donc préférable d'utiliser un préfixe qui le différencie 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 que le client effectue du côté du serveur, et les "événements" sont des appels que le serveur effectue du côté du client.

L'exemple d'extension contient un ensemble de requêtes qui demandent au serveur d'appliquer certaines transformations à la fenêtre du client. Par exemple, si le client envoie une demande de "rebond", le serveur doit y répondre en faisant rebondir la fenêtre sur l'écran.

De même, il existe 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 pour le client de définir sa taille de police par défaut à une taille spécifique.

Le protocole définit l'existence des requêtes et des événements, ainsi que les arguments qu'ils prennent. Lorsque qtwaylandscanner est exécuté sur ce protocole, il génère le code nécessaire pour rassembler l'appel de procédure et ses arguments et les transmettre sur la connexion. De l'autre côté, cela devient un appel à une fonction virtuelle qui peut être mise en œuvre pour fournir la réponse réelle.

Afin que qtwaylandscanner soit exécuté 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 le code de collage côté serveur et côté client, respectivement. (Si vous utilisez qmake, les variables WAYLANDSERVERSOURCES et WAYLANDCLIENTSOURCES sont identiques).

L'implémentation du compositeur

L'application Compositor elle-même est mise en œuvre à l'aide de QML et de Qt Quick, mais l'extension est mise en œuvre en C++.

La première étape consiste à créer une sous-classe du code de collage 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 à partir de QML.

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

En plus d'hériter de la classe générée, nous héritons également de la classe QWaylandCompositorExtensionTemplate qui offre une certaine commodité supplémentaire lorsque nous traitons des extensions, en utilisant le modèle de modèle curieusement récurrent.

Notez que la classe QWaylandCompositorExtensionTemplate doit être la première dans la liste d'héritage, puisqu'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, où nous pouvons traiter les demandes émises par un client.

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

Dans ces réimplémentations, nous traduisons simplement la demande en une émission de signal, de sorte que nous puissions la traiter dans le code QML 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) ; }

En outre, la sous-classe définit des slots pour chacun des événements, de sorte que ceux-ci puissent être appelés à partir de QML ou être 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) ; } }

Puisque nous avons ajouté la macro QML_ELEMENT à la définition de la classe (et ajouté les étapes de construction correspondantes aux fichiers du système de construction), elle peut être instanciée en QML.

Nous en faisons un enfant direct de l'objet WaylandCompositor afin que le compositeur l'enregistre comme une extension.

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

L'objet dispose de gestionnaires de signaux pour les demandes qu'il peut recevoir du client et réagit en conséquence. En outre, nous pouvons appeler ses slots pour envoyer des événements.

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

L'implémentation du client C++

Les deux clients partagent l'implémentation C++ de l'interface. Comme pour 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_extension

L'approche est très similaire à celle du compositeur, sauf qu'elle est inversée : Les demandes sont implémentées comme des slots qui appellent les fonctions générées, et les événements comme des 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 vise uniquement à montrer comment déclencher le comportement. Dans un événement de peinture personnalisé, il dessine un ensemble de rectangles et d'étiquettes. Lorsque l'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 lorsque l'événement set_font_size est reçu, 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 repeindra 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 dans QML pour l'activer.

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

L'interface utilisateur se compose de rectangles cliquables et utilise TapHandler pour envoyer les requêtes correspondantes lorsqu'un rectangle est cliqué.

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

Pour des raisons de simplicité, l'exemple a été limité aux requêtes bounce et spin ainsi qu'à l'événement set_font_size. La prise en charge des fonctionnalités supplémentaires est laissée à l'appréciation du lecteur.

Exemple de projet @ 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.