Coque personnalisée
La section « Shell personnalisé » explique comment implémenter une extension de shell personnalisée.
Les extensions du shell pour Wayland sont des protocoles qui gèrent l'état, la position et la taille des fenêtres. La plupart des compositeurs prennent en charge une ou plusieurs extensions intégrées, mais dans certaines circonstances, il peut être utile de pouvoir en écrire une personnalisée qui offre exactement les fonctionnalités dont vos applications ont besoin.

Cela nécessite d’implémenter l’extension Shell à la fois du côté serveur et du côté client de la connexion Wayland ; elle s’avère donc particulièrement utile lorsque vous développez une plateforme et que vous contrôlez à la fois le compositeur et ses applications clientes.
L'exemple « Custom Shell » présente l'implémentation d'une extension de shell simple. Il est divisé en trois parties :
- Une description du protocole pour une interface shell personnalisée.
- Un plugin permettant de se connecter à l’interface depuis une application cliente.
- Un exemple de compositeur avec une implémentation côté serveur de l'interface.
La description du protocole respecte le format XML standard lu par wayland-scanner. Elle ne sera pas abordée en détail ici, mais elle couvre les fonctionnalités suivantes :
- Une interface permettant de créer des surfaces de shell pour un
wl_surface. Cela permet au protocole d’ajouter des fonctionnalités en complément des API existantes d’wl_surface. - Une requête permettant de définir un titre de fenêtre sur la surface du shell.
- Une requête permettant de réduire/restaurer la surface du shell.
- Un événement informant le client de l’état de minimisation actuel de la surface du shell.
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.)
Le plugin client
Pour que l’intégration au shell soit détectée par un client Qt, il faut réimplémenter le plugin 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 ¶mList) override;
};
QWaylandShellIntegration *QWaylandExampleShellIntegrationPlugin::create(const QString &key, const QStringList ¶mList)
{
Q_UNUSED(key);
Q_UNUSED(paramList);
return new ExampleShellIntegration();
}Cela associe la clé « example-shell » à l'intégration du shell et permet d'instancier la classe ` ExampleShellIntegration ` lorsqu'un client se connecte à l'interface.
Les API permettant de créer des extensions de 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’API privées car, contrairement aux API publiques de Qt, il n’offre aucune garantie de compatibilité binaire. Ces API sont néanmoins considérées comme stables et resteront compatibles au niveau du code source ; à cet égard, elles sont similaires aux autres API de plug-ins de Qt.
La classe ` ExampleShellIntegration ` constitue le point d’entrée côté client pour la création de surfaces de shell, comme décrit ci-dessus. Elle étend la classe `QWaylandShellIntegrationTemplate` en utilisant le modèle de conception « 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 à partir 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 en est actuellement à la version 1 ; nous transmettons donc un objet ` 1 ` à la classe parente. Cela sert à la négociation du protocole et garantit que les anciens clients continueront de fonctionner si le compositeur utilise une version plus récente du protocole.
Une fois l’ ExampleShellIntegration 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 concernant l’interface. Dans ce cas précis, une seule requête doit être prise en charge : la création d’une surface shell. Elle utilise la fonction intégrée ` wlSurfaceForWindow() ` pour convertir la `QWaylandWindow` en une ` wl_surface`, puis envoie la requête. Elle étend ensuite la surface renvoyée avec un objet ` ExampleShellSurface ` qui gérera les requêtes 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);
}La classe ` ExampleShellSurface ` hérite de deux classes.
class ExampleShellSurface : public QWaylandShellSurface
, public QtWayland::qt_example_shell_surfaceLa première est la classe « QtWayland::qt_example_shell_surface », générée à partir de la description XML du protocole. Elle fournit des fonctions virtuelles pour les événements et des fonctions membres ordinaires pour les requêtes du protocole.
La classe ` QtWayland::qt_example_shell_surface ` ne comporte qu’un seul événement.
void example_shell_surface_minimize(uint32_t minimized) override;La classe ` ExampleShellSurface ` la réimplémente pour mettre à jour l'état interne de sa fenêtre. Lorsque l'état de la fenêtre change, elle stocke l'état en attente pour plus tard et appelle la méthode ` applyConfigureWhenPossible() ` dans ` QWaylandShellSurface`. Les changements d'état, de taille et de position doivent être organisés de cette manière. Ainsi, nous nous assurons que les modifications n'interfèrent pas avec le rendu à l'écran, et que plusieurs modifications liées peuvent facilement être appliquées en une seule opération.
Lorsqu’il est possible de reconfigurer la surface en toute sécurité, la fonction ` applyConfigure() ` est appelée.
void ExampleShellSurface::applyConfigure()
{
if (m_stateChanged)
QWindowSystemInterface::handleWindowStateChanged(platformWindow()->window(), m_pendingStates);
m_stateChanged = false;
}C’est à ce moment-là que nous appliquons effectivement le nouvel état (réduit ou restauré) à la fenêtre.
La deuxième superclasse est ` QWaylandShellSurface`. Il s’agit de l’interface utilisée par le plugin QPA de Wayland et par `QWaylandWindow` pour communiquer avec le shell. La classe ` ExampleShellSurface ` réimplémente é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 l’application Qt définit 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 l’ ExampleShellSurface, cela se traduit à son tour par une requête adressée à notre interface de surface shell personnalisée.
Le compositeur
La dernière partie de l’exemple est le compositeur lui-même. Celui-ci présente la même structure générale que les autres exemples de compositeurs. Consultez l’exemple Minimal QML pour plus de détails sur les éléments constitutifs d’un Qt Wayland Compositor.
Une différence notable dans le compositeur « Custom Shell » réside dans l’instanciation de l’extension de shell. Alors que l’exemple « Minimal QML » instancie les extensions de 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 du 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 dès leur connexion, et ceux-ci pourront s’y connecter comme indiqué dans la section précédente.
L’ ExampleShell est une sous-classe de l’interface générée QtWaylandServer::qt_example_shell, qui contient l’API définie dans le fichier XML du protocole. Il s’agit également d’une sous-classe de QWaylandCompositorExtensionTemplate, ce qui garantit que les objets sont reconnus par QWaylandCompositor en tant qu’extensions.
class ExampleShell
: public QWaylandCompositorExtensionTemplate<ExampleShell>
, QtWaylandServer::qt_example_shellCet héritage double est un modèle courant dans Qt Wayland Compositor lors de la création d’extensions. La classe QWaylandCompositorExtensionTemplate établit le lien 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 accessible à Qt Quick, nous utilisons la macro de préprocesseur Q_COMPOSITOR_DECLARE_QUICK_EXTENSION_CLASS pour plus de commodité. Cela permet notamment de gérer automatiquement l’initialisation de l’extension lorsqu’elle a été ajoutée au graphe Qt Quick.
void ExampleShell::initialize()
{
QWaylandCompositorExtensionTemplate::initialize();
QWaylandCompositor*compositor = 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 auprès du compositeur. De plus, nous initialisons l'extension de protocole elle-même. Pour ce faire, nous appelons la fonction générée ` init() ` 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 envoie cette requête sur la connexion.
Notre extension de shell ne prend en charge qu’un seul QWaylandSurfaceRole, mais il est tout de même important de l’attribuer à l’ QWaylandSurface lorsque nous créons une surface de shell pour celle-ci. La raison principale en est que l'attribution de rôles conflictuels à une même surface est considérée comme une erreur de protocole, et qu'il incombe au compositeur de signaler cette erreur si elle se produit. Définir un rôle sur la surface au moment de son adoption garantit que l'erreur de protocole sera signalée si la surface est réutilisée ultérieurement avec un rôle différent.
Nous utilisons des fonctions intégrées pour effectuer la conversion entre les types Wayland et Qt, puis créons un objet ` ExampleShellSurface `. Une fois tout préparé, nous émettons le signal ` shellSurfaceCreated() `, qui est ensuite 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 relative à la surface shell de l’extension de protocole.
Exécution de l’exemple
Pour qu’un client puisse se connecter correctement à la nouvelle extension de shell, quelques détails de configuration doivent être pris en compte.
Tout d'abord, le client doit pouvoir trouver le plugin de l'extension de shell. Une méthode simple consiste à définir la variable d’environnement ` QT_PLUGIN_PATH ` de manière à ce qu’elle pointe vers le répertoire d’installation du plugin. Étant donné que Qt recherche les plugins par catégorie, le chemin d’accès au plugin doit pointer vers le répertoire parent contenant 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 la variable d’environnement QT_PLUGIN_PATH comme suit :
export QT_PLUGIN_PATH=/path/to/build/pluginsPour connaître d’autres méthodes de configuration du répertoire des plugins, consultez la documentation du plugin.
La dernière étape consiste à s'assurer que le client se connecte bien à la bonne extension de shell. Les clients Qt tentent automatiquement de se connecter aux extensions de shell intégrées, mais il est possible de contourner ce comportement en définissant la variable d'environnement ` QT_WAYLAND_SHELL_INTEGRATION ` sur le nom de l'extension à charger.
export QT_WAYLAND_SHELL_INTEGRATION=example-shellEt c'est tout ce qu'il y a à faire. L'exemple « Custom Shell » est une extension de shell limitée ne proposant que très peu de fonctionnalités, mais il peut servir de point de départ pour créer des extensions spécialisées.
© 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.