Revision control
Copy as Markdown
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
/*!
* \title Custom Shell
* \example custom-shell
* \examplecategory {Embedded}
* \brief Custom Shell shows how to implement a custom shell extension.
* \ingroup qtwaylandcompositor-examples
*
* \l{Shell Extensions - Qt Wayland Compositor}{Shell extensions} to Wayland are protocols that
* manage window state, position and size. Most compositors will support one or more of built-in
* extensions, but in some circumstances it can be useful to be able to write a custom one which
* contains the exact features your applications need.
*
* \image custom-shell.jpg {Compositor running Qt Quick 3D Baked Lightmap
* Example with 3D room showing red, green, and white walls and
* "Use lightmaps" checkbox}
*
* This requires that you implement the shell extension on both the server-side and client-side
* of the Wayland connection, so it is mainly useful when you are building a platform and are in
* control of both the compositor and its client applications.
*
* The Custom Shell example shows the implementation of a simple shell extension. It is divided into
* three parts:
* \list
* \li A protocol description for a custom shell interface.
* \li A plugin for connecting to the interface in a client application.
* \li An example compositor with a server-side implementation of the interface.
* \endlist
*
* The protocol description follows the standard XML format read by \c{wayland-scanner}. It will
* not be covered in detail here, but it covers the following features:
*
* \list
* \li An interface for creating a shell surfaces for a \c{wl_surface}. This allows the protocol
* to add functionality on top of the existing \c{wl_surface} APIs.
* \li A request to set a window title on the shell surface.
* \li A request to minimize/de-minimize the shell surface.
* \li An event informing the client of the shell surface's current minimized state.
* \endlist
*
* In order to have \c qtwaylandscanner run automatically as part of the build, we use the
* CMake functions \l qt_generate_wayland_protocol_server_sources() and
* \l qt_generate_wayland_protocol_client_sources() for generating the server-side and
* client-side glue code, respectively. (When using \c qmake, the \c WAYLANDSERVERSOURCES and
* \c WAYLANDCLIENTSOURCES variables achieve the same.)
*
* \section1 The Client Plugin
*
* In order for the shell integration to be discovered by a Qt client, we must reimplement
* the QWaylandShellIntegrationPlugin.
*
* \snippet custom-shell/client-plugin/main.cpp plugin
*
* This attaches the "example-shell" key to the shell integration and provides a way for the
* \c ExampleShellIntegration class to be instantiated when a client connects to the interface.
*
* The APIs for creating shell extensions are available in the header \c qwaylandclientshellapi_p.h.
*
* \snippet custom-shell/client-plugin/main.cpp include
*
* This header requires including private API because unlike public Qt APIs, it does not come with
* binary compatibility guarantees. The APIs are still considered stable and will remain source
* compatible, and are similar in this respect to other plugin APIs in Qt.
*
* The \c ExampleShellIntegration is the client-side entry point for creating shell surfaces as
* described above. It extends the QWaylandShellIntegrationTemplate class, using the
* \l{https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern}{Curiously Recurring Template Pattern}.
*
* \snippet custom-shell/client-plugin/exampleshellintegration.h shell-integration
*
* It also inherits from the \c QtWayland::qt_example_shell class, which is generated by
* \c qtwaylandscanner based on the XML description of the protocol.
*
* The constructor specifies the version of the protocol that we support:
*
* \snippet custom-shell/client-plugin/exampleshellintegration.cpp constructor
*
* The example_shell protocol is currently at version one, so we pass a \c{1} to the parent
* class. This is used in protocol negotiation, and makes sure that older clients will continue
* working if the compositor uses a newer version of the protocol.
*
* When the \c ExampleShellIntegration is initialized, the application is connected to the server,
* and has received the broadcast of global interfaces the compositor supports.
* If successful, it can issue requests for the interface. In this
* case, there is only one request to support: Creating a shell surface. It uses the built-in
* function \c wlSurfaceForWindow() to convert the QWaylandWindow to a \c{wl_surface}, then it issues the
* request. It then extends the returned surface with a \c ExampleShellSurface object which will
* handle the requests and events on the \c qt_example_shell_surface interface.
*
* \snippet custom-shell/client-plugin/exampleshellintegration.cpp createShellSurface
*
* The \c ExampleShellSurface extends two classes.
*
* \snippet custom-shell/client-plugin/examplesurface.h ExampleShellSurface
*
* The first is the \c QtWayland::qt_example_shell_surface class which is generated based on the XML
* description of the protocol. This provides virtual functions for events and ordinary member
* functions for the requests in the protocol.
*
* The \c QtWayland::qt_example_shell_surface class only has a single event.
*
* \snippet custom-shell/client-plugin/examplesurface.h events
*
* The \c ExampleShellSurface reimplements this to update its internal window state. When the
* window state is change, it stores the pending state until later and calls
* \c{applyConfigureWhenPossible()} in QWaylandShellSurface. State, size and position changes should
* be organized like this. That way, we ensure that changes do not interfere with rendering to the
* surface, and multiple related changes can easily be applied as one.
*
* When it is safe to reconfigure the surface, the virtual \c applyConfigure() function is called.
*
* \snippet custom-shell/client-plugin/examplesurface.cpp applyConfigure
*
* This is where we actually commit the new (minimized or de-minimized) state to the window.
*
* The second super class is QWaylandShellSurface. This is the interface used by Wayland's QPA
* plugin and QWaylandWindow to communicate with the shell. The \c ExampleShellSurface reimplements
* a few virtual functions from this interface as well.
*
* \snippet custom-shell/client-plugin/examplesurface.h virtuals
*
* For example, when the Qt applications sets the title of a window, this translates into a call to
* the virtual \c setTitle() function.
*
* \snippet custom-shell/client-plugin/examplesurface.cpp setTitle
*
* In the \c ExampleShellSurface this in turn translates to a request on our custom shell surface
* interface.
*
* \section1 The Compositor
*
* The final part of the example is the compositor itself. This has the same general structure as
* the other compositor examples. See the
* \l{Minimal QML}{Minimal QML example} for more details on
* the building blocks of a \l{Qt Wayland Compositor}.
*
* One notable difference in the Custom Shell compositor is the instantiation of the shell
* extension. Where the \l{Minimal QML}{Minimal QML example}
* instantiates the shell extensions \l{IviApplication}, \l{XdgShell} and \l{WlShell}, the
* Custom Shell example only creates an instance of the \c ExampleShell extension.
*
* \snippet custom-shell/compositor/qml/main.qml ExampleShell
*
* We create the instance of the shell extension as a direct child of the WaylandCompositor in
* order to have it registered as a global interface. This will be broadcasted to clients as they
* connect, and they will be able to attach to the interface as outlined in the previous section.
*
* The \c ExampleShell is a subclass of the generated \c QtWaylandServer::qt_example_shell
* interface, which contains the API defined in the protocol XML. It is also a subclass of
* \l{QWaylandCompositorExtensionTemplate}, which ensures the objects are recognized by
* QWaylandCompositor as extensions.
*
* \snippet custom-shell/compositor/exampleshell.h ExampleShell
*
* This dual inheritance is a typical pattern in Qt Wayland Compositor when building extensions.
* The QWaylandCompositorExtensionTemplate class creates the connection between
* QWaylandCompositorExtension and the \c qt_example_shell class generated by \c qtwaylandscanner.
*
* Equivalently, the \c ExampleShellSurface class extends the generated
* \c QtWaylandServer::qt_example_shell_surface class as well as \l{QWaylandShellSurfaceTemplate},
* which makes it a subclass of the ShellSurface class and establishes the connection between
* Qt Wayland Compositor and the generated protocol code.
*
* To make the type available to Qt Quick, we use the \l{Q_COMPOSITOR_DECLARE_QUICK_EXTENSION_CLASS}
* preprocessor macro for the convenience. Among other things, this handles automatically
* initializing the extension when it has been added to the Qt Quick graph.
*
* \snippet custom-shell/compositor/exampleshell.cpp initialize
*
* The default implementation of the \c initialize() function register the extension with the
* compositor. In addition to this, we initialize the protocol extension itself. We do this by
* calling the generated \c init() function in the \c QtWaylandServer::qt_example_shell_surface
* class.
*
* We also reimplement the virtual function generated for the \c surface_create request.
*
* \snippet custom-shell/compositor/exampleshell.cpp surface_create
*
* This virtual function is called whenever a client issues the request on the connection.
*
* Our shell extension only supports a single QWaylandSurfaceRole, but it is still important that
* we assign it to the QWaylandSurface when we create a shell surface for it. The primary reason
* for this is that assigning conflicting roles to the same surface is considered a protocol error,
* and it is the compositor's responsibility to issue this error if it happens. Setting a role on
* the surface when we adopt it, ensures that the protocol error will be issued if the surface is
* reused with a different role later.
*
* We use built-in functions to convert between Wayland and Qt types, and create an
* \c ExampleShellSurface object. When everything is prepared, we emit the \c shellSurfaceCreated()
* signal, which in turn is intercepted in the QML code and added to the list of shell surfaces.
*
* \snippet custom-shell/compositor/qml/main.qml ExampleShell
*
* In \c{ExampleShellSurface}, we equivalently enable the shell surface part of the protocol
* extension.
*
* \section1 Running the example
*
* In order to have a client successfully connect to the new shell extension, there is a couple
* of configuration details to be handled.
*
* First of all, the client has to be able to find the shell extension's plugin. One simple way
* of doing this is to set the \c QT_PLUGIN_PATH to point to the plugin install directory. Since
* Qt will look up plugins by category, the plugin path should point to the parent directory that
* contains the directory for category \c{wayland-shell-integration}. So if the installed file is
* \c{/path/to/build/plugins/wayland-shell-integration/libexampleshellplugin.so}, then you should
* set \c QT_PLUGIN_PATH as follows:
*
* \badcode
* export QT_PLUGIN_PATH=/path/to/build/plugins
* \endcode
*
* For other ways to configure the plugin directory, see the
* \l{Deploying Plugins}{plugin documentation}.
*
* The final step is to make sure the client actually attaches to the correct shell extension.
* Qt clients will automatically try to attach to the built-in shell extensions, but this can be
* overridden by setting the \c QT_WAYLAND_SHELL_INTEGRATION environment variable to the name of
* the extension to load.
*
* \badcode
* export QT_WAYLAND_SHELL_INTEGRATION=example-shell
* \endcode
*
* And that is all there is to it. The Custom Shell example is a limited shell extension with only
* a very few features, but it can be used as a starting point for building specialized extensions.
*/