Sur cette page

Coquille personnalisée

Custom Shell montre comment mettre en œuvre une extension de shell personnalisée.

Lesextensions shell de Wayland sont des protocoles qui gèrent l'état, la position et la taille des fenêtres. La plupart des compositeurs supportent une ou plusieurs extensions intégrées, mais dans certaines circonstances, il peut être utile de pouvoir en écrire une personnalisée qui contient les fonctionnalités exactes dont vos applications ont besoin.

Cela nécessite que vous implémentiez l'extension shell à la fois du côté serveur et du côté client de la connexion Wayland, donc c'est principalement utile lorsque vous construisez une plateforme et que vous contrôlez à la fois le compositeur et ses applications clientes.

L'exemple Custom Shell montre la mise en œuvre d'une extension shell simple. Il est divisé en trois parties :

  • Une description du protocole pour une interface shell personnalisée.
  • Un plugin pour se connecter à l'interface dans une application client.
  • Un exemple de compositeur avec une implémentation de l'interface côté serveur.

La description du protocole suit le format XML standard lu par wayland-scanner. Elle ne sera pas traitée en détail ici, mais elle couvre les caractéristiques suivantes :

  • Une interface pour la création d'une surface shell pour un wl_surface, ce qui permet au protocole d'ajouter des fonctionnalités en plus des API wl_surface existantes.
  • Une demande de définition d'un titre de fenêtre sur la surface de la coquille.
  • Une demande de minimisation/déminimisation de la surface de la coquille.
  • Un événement informant le client de l'état actuel de minimisation de la surface de la coquille.

Pour que qtwaylandscanner soit exécuté automatiquement dans le cadre de la construction, 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).

Le plugin client

Pour que l'intégration du shell soit découverte par un client Qt, nous devons réimplémenter le QWaylandShellIntegrationPlugin.

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

Celui-ci attache la clé "example-shell" à l'intégration shell et permet à la classe ExampleShellIntegration d'être instanciée lorsqu'un client se connecte à l'interface.

Les API pour la création d'extensions shell sont disponibles dans l'en-tête qwaylandclientshellapi_p.h.

#include <QtWaylandClient/private/qwaylandclientshellapi_p.h>

Cet en-tête nécessite l'inclusion d'une API privée car, contrairement aux API publiques de Qt, elle n'est pas assortie de garanties de compatibilité binaire. Les API sont toujours considérées comme stables et resteront compatibles avec les sources, et sont similaires à cet égard à d'autres API de plugin dans Qt.

Le site ExampleShellIntegration est le point d'entrée côté client pour la création de surfaces de coques comme décrit ci-dessus. Il étend la classe QWaylandShellIntegrationTemplate, en utilisant le modèle Curiously Recurring Template Pattern.

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

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

Elle hérite également de la classe QtWayland::qt_example_shell, qui est générée par qtwaylandscanner sur la base de la description XML du protocole.

Le constructeur spécifie la version du protocole que nous prenons en charge :

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

Le protocole example_shell est actuellement à la version 1, nous passons donc un 1 à la classe mère. Ceci est utilisé dans la négociation du protocole, et permet de s'assurer que les anciens clients continueront à fonctionner si le compositeur utilise une version plus récente du protocole.

Lorsque ExampleShellIntegration est initialisé, l'application est connectée au serveur et a reçu la diffusion des interfaces globales prises en charge par le compositeur. En cas de succès, elle peut émettre des requêtes pour l'interface. Dans ce cas, il n'y a qu'une seule demande à prendre en charge : La création d'une surface de coque. Il utilise la fonction intégrée wlSurfaceForWindow() pour convertir la QWaylandWindow en wl_surface, puis il émet la demande. Il étend ensuite la surface renvoyée avec un objet ExampleShellSurface qui traitera les demandes et les événements sur l'interface qt_example_shell_surface.

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

L'objet ExampleShellSurface étend deux classes.

class ExampleShellSurface : public QWaylandShellSurface
        , public QtWayland::qt_example_shell_surface

La première est la classe QtWayland::qt_example_shell_surface qui est générée sur la base de la description XML du protocole. Elle fournit des fonctions virtuelles pour les événements et des fonctions membres ordinaires pour les demandes du protocole.

La classe QtWayland::qt_example_shell_surface n'a qu'un seul événement.

    void example_shell_surface_minimize(uint32_t minimized) override;

Le site ExampleShellSurface réimplémente cet événement pour mettre à jour l'état de sa fenêtre interne. Lorsque l'état de la fenêtre est modifié, il stocke l'état en attente jusqu'à une date ultérieure et appelle applyConfigureWhenPossible() dans QWaylandShellSurface. Les changements d'état, de taille et de position devraient être organisés de cette manière. De cette façon, nous nous assurons que les changements n'interfèrent pas avec le rendu de la surface, et plusieurs changements liés peuvent facilement être appliqués comme un seul.

Lorsque la surface peut être reconfigurée en toute sécurité, la fonction virtuelle applyConfigure() est appelée.

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

C'est là que nous validons le nouvel état (minimisé ou déminimisé) de la fenêtre.

La deuxième super classe est QWaylandShellSurface. C'est l'interface utilisée par le plugin QPA de Wayland et QWaylandWindow pour communiquer avec le shell. Le site ExampleShellSurface réimplante également quelques fonctions virtuelles de cette interface.

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

Par exemple, lorsque les applications Qt XML définissent le titre d'une fenêtre, cela se traduit par un appel à la fonction virtuelle setTitle().

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

Dans le site ExampleShellSurface, cela se traduit à son tour par une requête sur notre interface de surface shell personnalisée.

Le compositeur

La dernière partie de l'exemple est le compositeur lui-même. Sa structure générale est la même que celle des autres exemples de compositeurs. Voir l'exemple QML minimal pour plus de détails sur les éléments constitutifs d'un compositeur. Qt Wayland Compositor.

Une différence notable dans le compositeur Custom Shell est l'instanciation de l'extension shell. Alors que l'exemple Minimal QML instancie les extensions shell IviApplication, XdgShell et WlShell, l'exemple Custom Shell ne crée qu'une instance de l'extension ExampleShell.

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

Nous créons l'instance de l'extension shell en tant qu'enfant direct de WaylandCompositor afin qu'elle soit enregistrée en tant qu'interface globale. Celle-ci sera diffusée aux clients lorsqu'ils se connecteront, et ils pourront s'attacher à l'interface comme indiqué dans la section précédente.

L'interface ExampleShell est une sous-classe de l'interface QtWaylandServer::qt_example_shell générée, qui contient l'API définie dans le protocole XML. Il s'agit également d'une sous-classe de QWaylandCompositorExtensionTemplate, qui garantit que les objets sont reconnus par QWaylandCompositor comme des extensions.

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

Ce double héritage est un modèle typique de Qt Wayland Compositor lors de la construction d'extensions. La classe QWaylandCompositorExtensionTemplate crée la connexion entre QWaylandCompositorExtension et la classe qt_example_shell générée par qtwaylandscanner.

De manière équivalente, la classe ExampleShellSurface étend la classe QtWaylandServer::qt_example_shell_surface générée ainsi que QWaylandShellSurfaceTemplate, ce qui en fait une sous-classe de la classe ShellSurface et établit la connexion entre Qt Wayland Compositor et le code de protocole généré.

Pour rendre le type disponible à Qt Quick, nous utilisons la macro de préprocesseur Q_COMPOSITOR_DECLARE_QUICK_EXTENSION_CLASS par commodité. Entre autres choses, cela permet d'initialiser automatiquement l'extension lorsqu'elle a été ajoutée au graphe Qt Quick.

void ExampleShell::initialize() { QWaylandCompositorExtensionTemplate::initialize() ; QWaylandCompositor *compositeur =  static_cast<QWaylandCompositor *>(extensionContainer()) ; if (!compositor) {        qWarning() << "Failed to find QWaylandCompositor when initializing ExampleShell";
       return; } init(compositor->display(), 1) ; }

L'implémentation par défaut de la fonction initialize() enregistre l'extension avec le compositeur. En outre, nous initialisons l'extension de protocole elle-même. Pour ce faire, nous appelons la fonction init() générée dans la classe QtWaylandServer::qt_example_shell_surface.

Nous réimplémentons également la fonction virtuelle générée pour la requête surface_create.

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

Cette fonction virtuelle est appelée chaque fois qu'un client émet une requête sur la connexion.

Notre extension shell ne prend en charge qu'un seul QWaylandSurfaceRole, mais il est toujours important que nous l'assignions au QWaylandSurface lorsque nous créons une surface shell pour lui. La raison principale est que l'attribution de rôles contradictoires à la même surface est considérée comme une erreur de protocole, et c'est la responsabilité du compositeur d'émettre cette erreur si cela se produit. En attribuant un rôle à la surface lorsque nous l'adoptons, nous nous assurons que l'erreur de protocole sera émise si la surface est réutilisée ultérieurement avec un rôle différent.

Nous utilisons des fonctions intégrées pour convertir les types Wayland et Qt XML et créer un objet ExampleShellSurface. Lorsque tout est prêt, nous émettons le signal shellSurfaceCreated(), qui est à son tour intercepté dans le code QML et ajouté à la liste des surfaces shell.

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

Dans ExampleShellSurface, nous activons de manière équivalente la partie surface shell de l'extension du protocole.

Exécution de l'exemple

Pour qu'un client se connecte avec succès à la nouvelle extension shell, il y a quelques détails de configuration à gérer.

Tout d'abord, le client doit être capable de trouver le plugin de l'extension shell. Une façon simple de le faire est de configurer QT_PLUGIN_PATH pour qu'il pointe vers le répertoire d'installation du plugin. Puisque Qt XML recherche les plugins par catégorie, le chemin du plugin doit pointer vers le répertoire parent qui contient le répertoire de la catégorie wayland-shell-integration. Ainsi, si le fichier installé est /path/to/build/plugins/wayland-shell-integration/libexampleshellplugin.so, vous devez définir QT_PLUGIN_PATH comme suit :

export QT_PLUGIN_PATH=/path/to/build/plugins

Pour d'autres façons de configurer le répertoire du plugin, voir la documentation du plugin.

La dernière étape consiste à s'assurer que le client s'attache effectivement à la bonne extension shell. Les clients Qt XML essaieront automatiquement de s'attacher aux extensions shell intégrées, mais cela peut être surchargé en définissant la variable d'environnement QT_WAYLAND_SHELL_INTEGRATION avec le nom de l'extension à charger.

export QT_WAYLAND_SHELL_INTEGRATION=example-shell

Et c'est tout ce qu'il y a à faire. L'exemple du Custom Shell est une extension de shell limitée avec seulement quelques fonctionnalités, mais il peut être utilisé comme point de départ pour construire des extensions spécialisées.

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.