summaryrefslogtreecommitdiffstats
path: root/examples/wayland/custom-shell
diff options
context:
space:
mode:
Diffstat (limited to 'examples/wayland/custom-shell')
-rw-r--r--examples/wayland/custom-shell/CMakeLists.txt5
-rw-r--r--examples/wayland/custom-shell/client-plugin/CMakeLists.txt48
-rw-r--r--examples/wayland/custom-shell/client-plugin/client-plugin.pro33
-rw-r--r--examples/wayland/custom-shell/client-plugin/example-shell.json3
-rw-r--r--examples/wayland/custom-shell/client-plugin/exampleshellintegration.cpp22
-rw-r--r--examples/wayland/custom-shell/client-plugin/exampleshellintegration.h23
-rw-r--r--examples/wayland/custom-shell/client-plugin/examplesurface.cpp49
-rw-r--r--examples/wayland/custom-shell/client-plugin/examplesurface.h38
-rw-r--r--examples/wayland/custom-shell/client-plugin/main.cpp32
-rw-r--r--examples/wayland/custom-shell/compositor/CMakeLists.txt57
-rw-r--r--examples/wayland/custom-shell/compositor/compositor.pro29
-rw-r--r--examples/wayland/custom-shell/compositor/compositor.qrc6
-rw-r--r--examples/wayland/custom-shell/compositor/exampleshell.cpp119
-rw-r--r--examples/wayland/custom-shell/compositor/exampleshell.h88
-rw-r--r--examples/wayland/custom-shell/compositor/exampleshellintegration.cpp29
-rw-r--r--examples/wayland/custom-shell/compositor/exampleshellintegration.h26
-rw-r--r--examples/wayland/custom-shell/compositor/images/background.pngbin0 -> 2275 bytes
-rw-r--r--examples/wayland/custom-shell/compositor/main.cpp29
-rw-r--r--examples/wayland/custom-shell/compositor/qml/main.qml72
-rw-r--r--examples/wayland/custom-shell/custom-shell.pro5
-rw-r--r--examples/wayland/custom-shell/doc/images/custom-shell.jpgbin0 -> 47699 bytes
-rw-r--r--examples/wayland/custom-shell/doc/src/custom-shell.qdoc227
-rw-r--r--examples/wayland/custom-shell/protocol/example-shell.xml34
23 files changed, 974 insertions, 0 deletions
diff --git a/examples/wayland/custom-shell/CMakeLists.txt b/examples/wayland/custom-shell/CMakeLists.txt
new file mode 100644
index 000000000..1b0fe1653
--- /dev/null
+++ b/examples/wayland/custom-shell/CMakeLists.txt
@@ -0,0 +1,5 @@
+cmake_minimum_required(VERSION 3.16)
+project(custom-shell)
+
+add_subdirectory(client-plugin)
+add_subdirectory(compositor)
diff --git a/examples/wayland/custom-shell/client-plugin/CMakeLists.txt b/examples/wayland/custom-shell/client-plugin/CMakeLists.txt
new file mode 100644
index 000000000..39569b80f
--- /dev/null
+++ b/examples/wayland/custom-shell/client-plugin/CMakeLists.txt
@@ -0,0 +1,48 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.14)
+project(exampleshellplugin LANGUAGES CXX)
+
+set(CMAKE_AUTOMOC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/wayland/custom-shell/plugins")
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui WaylandClient)
+
+qt_add_plugin(exampleshellplugin)
+target_sources(exampleshellplugin PRIVATE
+ main.cpp
+ exampleshellintegration.cpp exampleshellintegration.h
+ examplesurface.cpp examplesurface.h
+)
+
+qt6_generate_wayland_protocol_client_sources(exampleshellplugin
+ FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/../protocol/example-shell.xml
+)
+
+set_target_properties(exampleshellplugin PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+ LIBRARY_OUTPUT_DIRECTORY "${PROJECT_BINARY_DIR}/../plugins/wayland-shell-integration"
+)
+
+target_link_libraries(exampleshellplugin PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::GuiPrivate
+ Qt::WaylandClient
+ Qt::WaylandClientPrivate
+ Wayland::Client
+)
+
+install(TARGETS exampleshellplugin
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/examples/wayland/custom-shell/client-plugin/client-plugin.pro b/examples/wayland/custom-shell/client-plugin/client-plugin.pro
new file mode 100644
index 000000000..96a01c231
--- /dev/null
+++ b/examples/wayland/custom-shell/client-plugin/client-plugin.pro
@@ -0,0 +1,33 @@
+QT += gui-private waylandclient-private
+CONFIG += plugin wayland-scanner
+TEMPLATE = lib
+
+QMAKE_USE += wayland-client
+
+qtConfig(xkbcommon): \
+ QMAKE_USE += xkbcommon
+
+WAYLANDCLIENTSOURCES += \
+ ../protocol/example-shell.xml
+
+HEADERS += \
+ exampleshellintegration.h \
+ examplesurface.h
+
+SOURCES += \
+ main.cpp \
+ exampleshellintegration.cpp \
+ examplesurface.cpp
+
+OTHER_FILES += \
+ example-shell.json
+
+DESTDIR = ../plugins/wayland-shell-integration
+TARGET = $$qtLibraryTarget(exampleshellplugin)
+
+### Everything below this line is just to make the example work inside the Qt source tree.
+### Do not include the following lines in your own code.
+
+target.path = $$[QT_INSTALL_EXAMPLES]/wayland/customshell/plugins/wayland-shell-integration
+INSTALLS += target
+CONFIG += install_ok
diff --git a/examples/wayland/custom-shell/client-plugin/example-shell.json b/examples/wayland/custom-shell/client-plugin/example-shell.json
new file mode 100644
index 000000000..c36490af5
--- /dev/null
+++ b/examples/wayland/custom-shell/client-plugin/example-shell.json
@@ -0,0 +1,3 @@
+{
+ "Keys":[ "example-shell" ]
+}
diff --git a/examples/wayland/custom-shell/client-plugin/exampleshellintegration.cpp b/examples/wayland/custom-shell/client-plugin/exampleshellintegration.cpp
new file mode 100644
index 000000000..b979640a7
--- /dev/null
+++ b/examples/wayland/custom-shell/client-plugin/exampleshellintegration.cpp
@@ -0,0 +1,22 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "exampleshellintegration.h"
+#include "examplesurface.h"
+
+//! [constructor]
+ExampleShellIntegration::ExampleShellIntegration()
+ : QWaylandShellIntegrationTemplate(/* Supported protocol version */ 1)
+{
+}
+//! [constructor]
+
+//! [createShellSurface]
+QWaylandShellSurface *ExampleShellIntegration::createShellSurface(QWaylandWindow *window)
+{
+ if (!isActive())
+ return nullptr;
+ auto *surface = surface_create(wlSurfaceForWindow(window));
+ return new ExampleShellSurface(surface, window);
+}
+//! [createShellSurface]
diff --git a/examples/wayland/custom-shell/client-plugin/exampleshellintegration.h b/examples/wayland/custom-shell/client-plugin/exampleshellintegration.h
new file mode 100644
index 000000000..f7f9de909
--- /dev/null
+++ b/examples/wayland/custom-shell/client-plugin/exampleshellintegration.h
@@ -0,0 +1,23 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef EXAMPLESHELLINTEGRATION_H
+#define EXAMPLESHELLINTEGRATION_H
+#include <QtWaylandClient/private/qwaylandclientshellapi_p.h>
+#include "qwayland-example-shell.h"
+
+using namespace QtWaylandClient;
+
+//! [shell-integration]
+class Q_WAYLANDCLIENT_EXPORT ExampleShellIntegration
+ : public QWaylandShellIntegrationTemplate<ExampleShellIntegration>
+ , public QtWayland::qt_example_shell
+{
+public:
+ ExampleShellIntegration();
+
+ QWaylandShellSurface *createShellSurface(QWaylandWindow *window) override;
+};
+//! [shell-integration]
+
+#endif // EXAMPLESHELLINTEGRATION_H
diff --git a/examples/wayland/custom-shell/client-plugin/examplesurface.cpp b/examples/wayland/custom-shell/client-plugin/examplesurface.cpp
new file mode 100644
index 000000000..eb17331c3
--- /dev/null
+++ b/examples/wayland/custom-shell/client-plugin/examplesurface.cpp
@@ -0,0 +1,49 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "examplesurface.h"
+#include <qpa/qwindowsysteminterface.h>
+#include <qpa/qplatformwindow.h>
+
+ExampleShellSurface::ExampleShellSurface(struct ::qt_example_shell_surface *shell_surface, QWaylandWindow *window)
+ : QWaylandShellSurface(window)
+ , QtWayland::qt_example_shell_surface(shell_surface)
+{
+}
+
+ExampleShellSurface::~ExampleShellSurface()
+{
+}
+
+bool ExampleShellSurface::wantsDecorations() const
+{
+ return true;
+}
+
+//! [setTitle]
+void ExampleShellSurface::setTitle(const QString &windowTitle)
+{
+ set_window_title(windowTitle);
+}
+//! [setTitle]
+
+void ExampleShellSurface::requestWindowStates(Qt::WindowStates states)
+{
+ set_minimized(states & Qt::WindowMinimized);
+}
+
+//! [applyConfigure]
+void ExampleShellSurface::applyConfigure()
+{
+ if (m_stateChanged)
+ QWindowSystemInterface::handleWindowStateChanged(platformWindow()->window(), m_pendingStates);
+ m_stateChanged = false;
+}
+//! [applyConfigure]
+
+void ExampleShellSurface::example_shell_surface_minimize(uint32_t minimized)
+{
+ m_pendingStates = minimized ? Qt::WindowMinimized : Qt::WindowNoState;
+ m_stateChanged = true;
+ applyConfigureWhenPossible();
+}
diff --git a/examples/wayland/custom-shell/client-plugin/examplesurface.h b/examples/wayland/custom-shell/client-plugin/examplesurface.h
new file mode 100644
index 000000000..e5d6fb92a
--- /dev/null
+++ b/examples/wayland/custom-shell/client-plugin/examplesurface.h
@@ -0,0 +1,38 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef EXAMPLESHELLSURFACE_H
+#define EXAMPLESHELLSURFACE_H
+
+#include <QtWaylandClient/private/qwaylandclientshellapi_p.h>
+#include "qwayland-example-shell.h"
+
+using namespace QtWaylandClient;
+
+//! [ExampleShellSurface]
+class ExampleShellSurface : public QWaylandShellSurface
+ , public QtWayland::qt_example_shell_surface
+//! [ExampleShellSurface]
+{
+public:
+ ExampleShellSurface(struct ::qt_example_shell_surface *shell_surface, QWaylandWindow *window);
+ ~ExampleShellSurface() override;
+
+//! [virtuals]
+ bool wantsDecorations() const override;
+ void setTitle(const QString &) override;
+ void requestWindowStates(Qt::WindowStates states) override;
+ void applyConfigure() override;
+//! [virtuals]
+
+protected:
+//! [events]
+ void example_shell_surface_minimize(uint32_t minimized) override;
+//! [events]
+
+private:
+ Qt::WindowStates m_pendingStates;
+ bool m_stateChanged = false;
+};
+
+#endif // EXAMPLESHELLSURFACE_H
diff --git a/examples/wayland/custom-shell/client-plugin/main.cpp b/examples/wayland/custom-shell/client-plugin/main.cpp
new file mode 100644
index 000000000..4ae488880
--- /dev/null
+++ b/examples/wayland/custom-shell/client-plugin/main.cpp
@@ -0,0 +1,32 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "exampleshellintegration.h"
+
+//! [include]
+#include <QtWaylandClient/private/qwaylandclientshellapi_p.h>
+//! [include]
+
+#include "qwayland-example-shell.h"
+
+using namespace QtWaylandClient;
+
+//! [plugin]
+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();
+}
+//! [plugin]
+
+#include "main.moc"
diff --git a/examples/wayland/custom-shell/compositor/CMakeLists.txt b/examples/wayland/custom-shell/compositor/CMakeLists.txt
new file mode 100644
index 000000000..be1d7f623
--- /dev/null
+++ b/examples/wayland/custom-shell/compositor/CMakeLists.txt
@@ -0,0 +1,57 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.14)
+project(custom-shell-compositor)
+
+set(CMAKE_AUTOMOC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}/wayland/custom-shell/compositor")
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml WaylandCompositor)
+
+qt_add_executable(custom-shell-compositor
+ exampleshell.cpp exampleshell.h
+ exampleshellintegration.cpp exampleshellintegration.h
+ main.cpp
+)
+
+qt6_generate_wayland_protocol_server_sources(custom-shell-compositor
+ FILES
+ ${CMAKE_CURRENT_SOURCE_DIR}/../protocol/example-shell.xml
+)
+
+set_target_properties(custom-shell-compositor PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+
+target_link_libraries(custom-shell-compositor PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::Qml
+ Qt::WaylandCompositor
+)
+
+# Resources:
+set(compositor_resource_files
+ "images/background.png"
+ "qml/main.qml"
+)
+
+qt6_add_resources(custom-shell-compositor "compositor"
+ PREFIX
+ "/"
+ FILES
+ ${compositor_resource_files}
+)
+
+install(TARGETS custom-shell-compositor
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/examples/wayland/custom-shell/compositor/compositor.pro b/examples/wayland/custom-shell/compositor/compositor.pro
new file mode 100644
index 000000000..d64ddac61
--- /dev/null
+++ b/examples/wayland/custom-shell/compositor/compositor.pro
@@ -0,0 +1,29 @@
+QT += core gui qml
+
+QT += waylandcompositor
+
+CONFIG += wayland-scanner
+CONFIG += c++11
+SOURCES += \
+ main.cpp \
+ exampleshell.cpp \
+ exampleshellintegration.cpp \
+
+HEADERS += \
+ exampleshell.h \
+ exampleshellintegration.h
+
+OTHER_FILES = \
+ qml/main.qml \
+ images/background.jpg
+
+WAYLANDSERVERSOURCES += \
+ ../protocol/example-shell.xml
+
+RESOURCES += compositor.qrc
+
+TARGET = custom-shell-compositor
+
+
+target.path = $$[QT_INSTALL_EXAMPLES]/wayland/custom-shell/compositor
+INSTALLS += target
diff --git a/examples/wayland/custom-shell/compositor/compositor.qrc b/examples/wayland/custom-shell/compositor/compositor.qrc
new file mode 100644
index 000000000..59c00f5f9
--- /dev/null
+++ b/examples/wayland/custom-shell/compositor/compositor.qrc
@@ -0,0 +1,6 @@
+<RCC>
+ <qresource prefix="/">
+ <file>images/background.png</file>
+ <file>qml/main.qml</file>
+ </qresource>
+</RCC>
diff --git a/examples/wayland/custom-shell/compositor/exampleshell.cpp b/examples/wayland/custom-shell/compositor/exampleshell.cpp
new file mode 100644
index 000000000..f134a8ba6
--- /dev/null
+++ b/examples/wayland/custom-shell/compositor/exampleshell.cpp
@@ -0,0 +1,119 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "exampleshell.h"
+#include "exampleshellintegration.h"
+
+#include <QtWaylandCompositor/QWaylandCompositor>
+#include <QtWaylandCompositor/QWaylandSurface>
+#include <QtWaylandCompositor/QWaylandResource>
+
+ExampleShell::ExampleShell()
+{
+}
+
+ExampleShell::ExampleShell(QWaylandCompositor *compositor)
+ : QWaylandCompositorExtensionTemplate<ExampleShell>(compositor)
+{
+ this->compositor = compositor;
+}
+
+//! [initialize]
+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);
+}
+//! [initialize]
+
+//! [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);
+}
+//! [surface_create]
+
+ExampleShellSurface::ExampleShellSurface(ExampleShell *shell, QWaylandSurface *surface, const QWaylandResource &resource)
+{
+ m_shell = shell;
+ m_surface = surface;
+ init(resource.resource());
+ setExtensionContainer(surface);
+ QWaylandCompositorExtension::initialize();
+}
+
+QWaylandSurfaceRole ExampleShellSurface::s_role("qt_example_shell_surface");
+
+/*!
+ * Returns the surface role for the ExampleShellSurface.
+ */
+QWaylandSurfaceRole *ExampleShellSurface::role()
+{
+ return &s_role;
+}
+
+/*!
+ * Returns the ExampleShellSurface corresponding to the \a resource.
+ */
+ExampleShellSurface *ExampleShellSurface::fromResource(wl_resource *resource)
+{
+ auto *res = Resource::fromResource(resource);
+ return static_cast<ExampleShellSurface *>(res->object());
+}
+
+QWaylandQuickShellIntegration *ExampleShellSurface::createIntegration(QWaylandQuickShellSurfaceItem *item)
+{
+ return new ExampleShellIntegration(item);
+}
+
+void ExampleShellSurface::example_shell_surface_destroy_resource(Resource *resource)
+{
+ Q_UNUSED(resource);
+ delete this;
+}
+
+void ExampleShellSurface::example_shell_surface_destroy(Resource *resource)
+{
+ wl_resource_destroy(resource->handle);
+}
+
+void ExampleShellSurface::example_shell_surface_set_window_title(Resource *resource, const QString &window_title)
+{
+ Q_UNUSED(resource);
+ m_windowTitle = window_title;
+ emit windowTitleChanged();
+}
+
+void ExampleShellSurface::example_shell_surface_set_minimized(Resource *resource, uint32_t minimized)
+{
+ Q_UNUSED(resource);
+ if (m_minimized != minimized) {
+ m_minimized = minimized;
+ emit minimizedChanged();
+ }
+}
+
+void ExampleShellSurface::setMinimized(bool newMinimized)
+{
+ if (m_minimized == newMinimized)
+ return;
+ m_minimized = newMinimized;
+ send_minimize(newMinimized);
+ emit minimizedChanged();
+}
diff --git a/examples/wayland/custom-shell/compositor/exampleshell.h b/examples/wayland/custom-shell/compositor/exampleshell.h
new file mode 100644
index 000000000..5dd8b6c98
--- /dev/null
+++ b/examples/wayland/custom-shell/compositor/exampleshell.h
@@ -0,0 +1,88 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef EXAMPLESHELL_H
+#define EXAMPLESHELL_H
+
+#include <QtWaylandCompositor/QWaylandCompositorExtension>
+#include <QtWaylandCompositor/QWaylandQuickExtension>
+#include <QtWaylandCompositor/QWaylandSurface>
+#include <QtWaylandCompositor/QWaylandResource>
+#include <QtCore/QSize>
+
+#include <QtWaylandCompositor/QWaylandShellSurface>
+#include "qwayland-server-example-shell.h"
+
+class ExampleShellSurface;
+
+//! [ExampleShell]
+class ExampleShell
+ : public QWaylandCompositorExtensionTemplate<ExampleShell>
+ , QtWaylandServer::qt_example_shell
+//! [ExampleShell]
+{
+ Q_OBJECT
+public:
+ ExampleShell();
+ ExampleShell(QWaylandCompositor *compositor);
+
+ void initialize() override;
+
+ using QtWaylandServer::qt_example_shell::interface;
+ using QtWaylandServer::qt_example_shell::interfaceName;
+
+protected:
+ void example_shell_surface_create(Resource *resource, wl_resource *surfaceResource, uint32_t id) override;
+
+signals:
+ void shellSurfaceCreated(ExampleShellSurface *shellSurface);
+
+private:
+ QWaylandCompositor *compositor;
+};
+
+class ExampleShellSurface :
+ public QWaylandShellSurfaceTemplate<ExampleShellSurface>
+ , public QtWaylandServer::qt_example_shell_surface
+{
+ Q_OBJECT
+ Q_PROPERTY(QString windowTitle READ windowTitle NOTIFY windowTitleChanged)
+ Q_PROPERTY(bool minimized READ minimized WRITE setMinimized NOTIFY minimizedChanged)
+public:
+ ExampleShellSurface(ExampleShell *shell, QWaylandSurface *surface, const QWaylandResource &resource);
+
+ using QtWaylandServer::qt_example_shell_surface::interface;
+ using QtWaylandServer::qt_example_shell_surface::interfaceName;
+ static QWaylandSurfaceRole *role();
+ static ExampleShellSurface *fromResource(::wl_resource *resource);
+
+ QWaylandQuickShellIntegration *createIntegration(QWaylandQuickShellSurfaceItem *item) override;
+
+ QWaylandSurface *surface() const { return m_surface; }
+ const QString &windowTitle() const { return m_windowTitle; }
+ bool minimized() const { return m_minimized; }
+ void setMinimized(bool newMinimized);
+
+signals:
+ void windowTitleChanged();
+ void minimizedChanged();
+
+protected:
+ void example_shell_surface_destroy_resource(Resource *resource) override;
+ void example_shell_surface_destroy(Resource *resource) override;
+ void example_shell_surface_set_window_title(Resource *resource, const QString &window_title) override;
+ void example_shell_surface_set_minimized(Resource *resource, uint32_t minimized) override;
+
+private:
+ static QWaylandSurfaceRole s_role;
+ QWaylandSurface *m_surface = nullptr;
+ ExampleShell *m_shell = nullptr;
+ QString m_windowTitle;
+ bool m_minimized = false;
+};
+
+//! [declare_extension]
+Q_COMPOSITOR_DECLARE_QUICK_EXTENSION_CLASS(ExampleShell)
+//! [declare_extension]
+
+#endif // EXAMPLESHELL_H
diff --git a/examples/wayland/custom-shell/compositor/exampleshellintegration.cpp b/examples/wayland/custom-shell/compositor/exampleshellintegration.cpp
new file mode 100644
index 000000000..840927d0f
--- /dev/null
+++ b/examples/wayland/custom-shell/compositor/exampleshellintegration.cpp
@@ -0,0 +1,29 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "exampleshellintegration.h"
+
+#include <QtWaylandCompositor/QWaylandCompositor>
+#include <QtWaylandCompositor/QWaylandQuickShellSurfaceItem>
+#include <QtWaylandCompositor/QWaylandSeat>
+#include "exampleshell.h"
+
+
+ExampleShellIntegration::ExampleShellIntegration(QWaylandQuickShellSurfaceItem *item)
+ : QWaylandQuickShellIntegration(item)
+ , m_item(item)
+ , m_shellSurface(qobject_cast<ExampleShellSurface *>(item->shellSurface()))
+{
+ m_item->setSurface(m_shellSurface->surface());
+ connect(m_shellSurface, &ExampleShellSurface::destroyed, this, &ExampleShellIntegration::handleExampleShellSurfaceDestroyed);
+}
+
+ExampleShellIntegration::~ExampleShellIntegration()
+{
+ m_item->setSurface(nullptr);
+}
+
+void ExampleShellIntegration::handleExampleShellSurfaceDestroyed()
+{
+ m_shellSurface = nullptr;
+}
diff --git a/examples/wayland/custom-shell/compositor/exampleshellintegration.h b/examples/wayland/custom-shell/compositor/exampleshellintegration.h
new file mode 100644
index 000000000..d03826ef0
--- /dev/null
+++ b/examples/wayland/custom-shell/compositor/exampleshellintegration.h
@@ -0,0 +1,26 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef EXAMPLESHELLINTEGRATION_H
+#define EXAMPLESHELLINTEGRATION_H
+
+#include "exampleshell.h"
+#include <QtWaylandCompositor/QWaylandQuickShellIntegration>
+#include <QtWaylandCompositor/QWaylandQuickShellSurfaceItem>
+
+class ExampleShellIntegration : public QWaylandQuickShellIntegration
+{
+ Q_OBJECT
+public:
+ ExampleShellIntegration(QWaylandQuickShellSurfaceItem *item);
+ ~ExampleShellIntegration() override;
+
+private slots:
+ void handleExampleShellSurfaceDestroyed();
+
+private:
+ QWaylandQuickShellSurfaceItem *m_item = nullptr;
+ ExampleShellSurface *m_shellSurface = nullptr;
+};
+
+#endif // EXAMPLESHELLINTEGRATION_H
diff --git a/examples/wayland/custom-shell/compositor/images/background.png b/examples/wayland/custom-shell/compositor/images/background.png
new file mode 100644
index 000000000..cf264746f
--- /dev/null
+++ b/examples/wayland/custom-shell/compositor/images/background.png
Binary files differ
diff --git a/examples/wayland/custom-shell/compositor/main.cpp b/examples/wayland/custom-shell/compositor/main.cpp
new file mode 100644
index 000000000..72f233e8e
--- /dev/null
+++ b/examples/wayland/custom-shell/compositor/main.cpp
@@ -0,0 +1,29 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QtCore/QUrl>
+#include <QtCore/QDebug>
+#include <QtGui/QGuiApplication>
+#include <QtQml/QQmlApplicationEngine>
+
+#include <QtQml/qqml.h>
+#include <QtQml/QQmlEngine>
+
+#include "exampleshell.h"
+
+static void registerTypes()
+{
+ qmlRegisterType<ExampleShellQuickExtension>("io.qt.examples", 1, 0, "ExampleShell");
+}
+
+int main(int argc, char *argv[])
+{
+ // ShareOpenGLContexts is needed for using the threaded renderer
+ // on Nvidia EGLStreams
+ QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts, true);
+ QGuiApplication app(argc, argv);
+ registerTypes();
+ QQmlApplicationEngine appEngine(QUrl("qrc:///qml/main.qml"));
+
+ return app.exec();
+}
diff --git a/examples/wayland/custom-shell/compositor/qml/main.qml b/examples/wayland/custom-shell/compositor/qml/main.qml
new file mode 100644
index 000000000..149e0e748
--- /dev/null
+++ b/examples/wayland/custom-shell/compositor/qml/main.qml
@@ -0,0 +1,72 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtWayland.Compositor
+import QtQuick.Layouts
+import QtQuick.Controls
+
+import io.qt.examples 1.0
+
+WaylandCompositor {
+ id: comp
+
+ ListModel { id: shellSurfaces }
+
+
+ WaylandOutput {
+ sizeFollowsWindow: true
+ window: Window {
+ width: 1024
+ height: 768
+ visible: true
+
+ ColumnLayout {
+ anchors.fill: parent
+ spacing: 0
+ Image {
+ fillMode: Image.Tile
+ source: "qrc:/images/background.png"
+ smooth: false
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ GridLayout {
+ columns: 3
+ Repeater {
+ model: shellSurfaces
+ ShellSurfaceItem {
+ id: chrome
+ shellSurface: modelData
+ visible: !shellSurface.minimized
+ onSurfaceDestroyed: shellSurfaces.remove(index)
+ }
+ }
+ }
+ }
+ Row {
+ id: taskbar
+ Layout.fillWidth: true
+ Repeater {
+ model: shellSurfaces
+ Button {
+ id: minimizeButton
+ checkable: true
+ text: modelData.windowTitle
+ onCheckedChanged: modelData.minimized = checked
+ checked: modelData.minimized
+ }
+ }
+ }
+ }
+ }
+ }
+
+ //! [ExampleShell]
+ ExampleShell {
+ id: shell
+ onShellSurfaceCreated: (shellSurface) => {
+ shellSurfaces.append({shellSurface: shellSurface});
+ }
+ }
+ //! [ExampleShell]
+}
diff --git a/examples/wayland/custom-shell/custom-shell.pro b/examples/wayland/custom-shell/custom-shell.pro
new file mode 100644
index 000000000..246115d32
--- /dev/null
+++ b/examples/wayland/custom-shell/custom-shell.pro
@@ -0,0 +1,5 @@
+TEMPLATE=subdirs
+
+SUBDIRS += compositor client-plugin
+
+OTHER_FILES += protocol/example-shell.xml
diff --git a/examples/wayland/custom-shell/doc/images/custom-shell.jpg b/examples/wayland/custom-shell/doc/images/custom-shell.jpg
new file mode 100644
index 000000000..81f1ed205
--- /dev/null
+++ b/examples/wayland/custom-shell/doc/images/custom-shell.jpg
Binary files differ
diff --git a/examples/wayland/custom-shell/doc/src/custom-shell.qdoc b/examples/wayland/custom-shell/doc/src/custom-shell.qdoc
new file mode 100644
index 000000000..ba0fd8bf7
--- /dev/null
+++ b/examples/wayland/custom-shell/doc/src/custom-shell.qdoc
@@ -0,0 +1,227 @@
+// 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
+ *
+ * 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}{qt_generate_wayland_protocol_server_sources()} and
+ * \l{qt_generate_wayland_protocol_client_sources}{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}{the 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.
+ */
diff --git a/examples/wayland/custom-shell/protocol/example-shell.xml b/examples/wayland/custom-shell/protocol/example-shell.xml
new file mode 100644
index 000000000..ec01b6d61
--- /dev/null
+++ b/examples/wayland/custom-shell/protocol/example-shell.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<protocol name="example_shell">
+ <copyright>
+ Copyright (C) 2021 The Qt Company Ltd.
+ SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+ </copyright>
+
+ <interface name="qt_example_shell_surface" version="1">
+ <request name="destroy" type="destructor">
+ </request>
+
+ <request name="set_window_title">
+ <arg name="window_title" type="string" />
+ </request>
+
+ <request name="set_minimized">
+ <arg name="minimized" type="uint" />
+ </request>
+ <event name="minimize">
+ <arg name="minimized" type="uint"/>
+ </event>
+ </interface>
+
+ <interface name="qt_example_shell" version="1">
+ <request name="surface_create">
+ <arg name="surface" type="object" interface="wl_surface"/>
+ <arg name="id" type="new_id" interface="qt_example_shell_surface"/>
+ </request>
+ <enum name="error">
+ <entry name="role" value="0" summary="wl_surface already has a different role"/>
+ </enum>
+ </interface>
+
+</protocol>