diff options
Diffstat (limited to 'examples/wayland/custom-shell')
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 ¶mList) override; +}; + +QWaylandShellIntegration *QWaylandExampleShellIntegrationPlugin::create(const QString &key, const QStringList ¶mList) +{ + 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 Binary files differnew file mode 100644 index 000000000..cf264746f --- /dev/null +++ b/examples/wayland/custom-shell/compositor/images/background.png 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 Binary files differnew file mode 100644 index 000000000..81f1ed205 --- /dev/null +++ b/examples/wayland/custom-shell/doc/images/custom-shell.jpg 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> |