summaryrefslogtreecommitdiffstats
path: root/examples/wayland/qtshell
diff options
context:
space:
mode:
Diffstat (limited to 'examples/wayland/qtshell')
-rw-r--r--examples/wayland/qtshell/CMakeLists.txt39
-rw-r--r--examples/wayland/qtshell/doc/images/qtshell.jpgbin0 -> 148259 bytes
-rw-r--r--examples/wayland/qtshell/doc/src/qtshell.qdoc150
-rw-r--r--examples/wayland/qtshell/images/background.jpgbin0 -> 30730 bytes
-rw-r--r--examples/wayland/qtshell/main.cpp21
-rw-r--r--examples/wayland/qtshell/qml/Chrome.qml184
-rw-r--r--examples/wayland/qtshell/qml/CompositorScreen.qml108
-rw-r--r--examples/wayland/qtshell/qml/main.qml21
-rw-r--r--examples/wayland/qtshell/qtshell.pro16
-rw-r--r--examples/wayland/qtshell/qtshell.qrc8
10 files changed, 547 insertions, 0 deletions
diff --git a/examples/wayland/qtshell/CMakeLists.txt b/examples/wayland/qtshell/CMakeLists.txt
new file mode 100644
index 000000000..64db8a672
--- /dev/null
+++ b/examples/wayland/qtshell/CMakeLists.txt
@@ -0,0 +1,39 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.14)
+project(qtshell LANGUAGES CXX)
+
+set(CMAKE_AUTOMOC ON)
+
+find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml)
+
+qt_add_executable(qtshell
+ main.cpp
+)
+
+set_target_properties(qtshell PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+
+target_link_libraries(qtshell PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::Qml
+)
+
+# Resources:
+set(qtshell_resource_files
+ "images/background.jpg"
+ "qml/Chrome.qml"
+ "qml/CompositorScreen.qml"
+ "qml/main.qml"
+)
+
+qt6_add_resources(qtshell "qtshell"
+ PREFIX
+ "/"
+ FILES
+ ${qtshell_resource_files}
+)
diff --git a/examples/wayland/qtshell/doc/images/qtshell.jpg b/examples/wayland/qtshell/doc/images/qtshell.jpg
new file mode 100644
index 000000000..53b9aa189
--- /dev/null
+++ b/examples/wayland/qtshell/doc/images/qtshell.jpg
Binary files differ
diff --git a/examples/wayland/qtshell/doc/src/qtshell.qdoc b/examples/wayland/qtshell/doc/src/qtshell.qdoc
new file mode 100644
index 000000000..2da1c1874
--- /dev/null
+++ b/examples/wayland/qtshell/doc/src/qtshell.qdoc
@@ -0,0 +1,150 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ * \title QtShell Compositor
+ * \example qtshell
+ * \examplecategory {Embedded}
+ * \brief QtShell Compositor shows how to use the QtShell shell extension.
+ * \ingroup qtwaylandcompositor-examples
+ *
+ * QtShell Compositor is a desktop-style Wayland compositor example implementing a
+ * complete Qt Wayland Compositor which uses the specialized
+ * \l{Shell Extensions - Qt Wayland Compositor}{shell extension protocol} called \l{QtShell}.
+ *
+ * \image qtshell.jpg
+ *
+ * The compositor is implemented with Qt Quick and QML.
+ *
+ * \section1 Making the Connection
+ *
+ * The example lists QtShell as the only extension to the WaylandCompositor object. This means that
+ * any client connecting to the server must also support this extension, thus they should be Qt
+ * applications running against the same version of Qt as the compositor.
+ *
+ * \snippet qtshell/qml/main.qml shell
+ *
+ * When a client connects to the QtShell interface, it creates a \l{QtShellSurface}. The compositor
+ * is notified of this by the emission of the
+ * \l [QML] {QtShell::}{qtShellSurfaceCreated} signal. The
+ * example then adds the shell surface to a ListModel for easy access later.
+ *
+ * \snippet qtshell/qml/CompositorScreen.qml handleShellSurface
+ *
+ * The ListModel is used as the model for a \l{Repeater} which creates the Qt Quick items required
+ * to display the client contents on screen.
+ *
+ * \snippet qtshell/qml/CompositorScreen.qml repeater
+ *
+ * It uses the local \c Chrome type, which handles window states and decorations.
+ *
+ * \section1 Chrome
+ *
+ * The \c Chrome is the type that ensures the client contents are visible and also handles window
+ * state, position, size, and so on. It uses the built-in QtShellChrome as a basis, which
+ * automatically handles window state (maximized, minimized, fullscreen) and window activation
+ * (ensuring that only a single window is active at the time).
+ *
+ * Its behavior can be customized to some extent, but it is also possible to write the \c Chrome
+ * functionality from scratch, building from a basic \l{Item} type instead. QtShellChrome is a
+ * convenience class which provides typical compositor behavior, and saves us the time of
+ * implementing this logic in the example.
+ *
+ * However the \c Chrome is written, it should have a ShellSurfaceItem to hold the client contents.
+ *
+ * \snippet qtshell/qml/Chrome.qml shellsurfaceitem
+ *
+ * The ShellSurfaceItem is the visual representation of the client's contents in the Qt Quick scene.
+ * Its size should usually match the size of the client's buffer, otherwise it may look stretched or
+ * squeezed. QtShellChrome will automatically be sized to the \l{QtShellSurface}'s
+ * \l{QtShellSurface::windowGeometry}{windowGeometry}, which is size of the
+ * client's buffer plus the size of the frame margins. The frame margins are reserved areas on the
+ * sides of the \c Chrome which can be used to contain window decorations.
+ *
+ * The ShellSurfaceItem is therefore anchored to the window decorations to fill the area reserved
+ * for the client buffer.
+ *
+ * \section1 Window Decorations
+ *
+ * The window decoration is usually a frame around a client's contents which adds information
+ * (such as a window title) and the possibility of user interaction (such as resizing, closing,
+ * moving the window, and so on.)
+ *
+ * With \l{QtShell}, window decorations are always drawn by the compositor and not by the client.
+ * In order for sizes and positions to be communicated correctly, QtShell also needs to know how
+ * much of the window is reserved for these decorations. This can be handled automatically by
+ * QtShellChrome, or manually, by setting
+ * \l{QtShellChrome::frameMarginLeft}{frameMarginLeft},
+ * \l{QtShellChrome::frameMarginRight}{frameMarginRight},
+ * \l{QtShellChrome::frameMarginTop}{frameMarginTop} and
+ * \l{QtShellChrome::frameMarginBottom}{frameMarginBottom}.
+ *
+ * For typical cases where there are resize handles around the window and a title bar at the top,
+ * it is more convenient to rely on the default frame margins. The QtShell Compositor example
+ * does this.
+ *
+ * First, we create Qt Quick items to represent the different parts of the window's decorations.
+ * On the left side, for example, there should be a resize handle that the user can grab and drag in
+ * order to resize the window.
+ *
+ * \snippet qtshell/qml/Chrome.qml leftResizeHandle
+ *
+ * We simply make this a five-pixel wide rectangle in the example, anchored to the top, bottom and
+ * left side of the \c Chrome.
+ *
+ * Similarly, we add Qt Quick items that represent the right, top, bottom, top-left, top-right,
+ * bottom-left and bottom-right resize handles. We also add a title bar. When the decorations have
+ * been created and anchored correctly to the sides of the \c{Chrome}, we set corresponding
+ * properties in QtShellChrome.
+ *
+ * \snippet qtshell/qml/Chrome.qml decorations
+ *
+ * When the decoration properties are set, the default resizing and repositioning behavior will be
+ * added automatically. The user will be able to interact with the resize handles in order to resize
+ * the window, and drag the title bar to reposition it. The frame margins of the QtShellSurface will
+ * also be set automatically to account for the size of the decorations (as long as none of the
+ * frame margins properties have been set explicitly.)
+ *
+ * The visibility of the decorations will be handled automatically by the QtShellChrome based on
+ * the window flags of the QtShellSurface.
+ *
+ * \section1 Window Management
+ *
+ * As part of the decorations, it is common to have tool buttons which manage the window state
+ * and life span. In the example, these are added to the title bar.
+ *
+ * \snippet qtshell/qml/Chrome.qml buttons
+ *
+ * The visibility of each button is conditional on the window flag for that button, and when each
+ * of them is clicked, we simply call the corresponding method in QtShellChrome. The exception is
+ * the "close" button, which calls the
+ * \l{QtWaylandCompositor::QtShellSurface::sendClose()}{sendClose()} method in QtShellSurface.
+ * This instructs the client to close itself, and ensures a graceful shutdown of the application.
+ *
+ * \snippet qtshell/qml/CompositorScreen.qml taskbar
+ *
+ * As an additional window management tool, the example has a "task bar". This is just a row of
+ * tool buttons at the bottom with the window titles. The buttons can be clicked to de-minimize
+ * applications and bring them to the front if they are obscured by other windows. Similarly
+ * to the \c{Chrome}, we use a \l{Repeater} for creating the tool buttons and use the shell surface
+ * list as model for this. For simplicity, the example does not have any handling of overflow (when
+ * there are too many applications for the task bar), but in a proper compositor, this is also
+ * something that should be considered.
+ *
+ * Finally, to avoid maximized applications expanding to fill the area covered by the task bar, we
+ * create a special item to manage the parts of the WaylandOutput real estate that is available to
+ * client windows.
+ *
+ * \snippet qtshell/qml/CompositorScreen.qml usableArea
+ *
+ * It is simply anchored to the sides of the WaylandOutput, but its bottom anchor is at the top
+ * of the task bar.
+ *
+ * In the \c{Chrome}, we use this area to define the \l{QtShellChrome::maximizedRect}{maximizedRect}
+ * of the window.
+ *
+ * \snippet qtshell/qml/Chrome.qml maximizedRect
+ *
+ * By default, this property will match the full WaylandOutput. In our case, however, we do not want
+ * to include the task bar in the available area, so we override the default.
+ */
diff --git a/examples/wayland/qtshell/images/background.jpg b/examples/wayland/qtshell/images/background.jpg
new file mode 100644
index 000000000..445567fbd
--- /dev/null
+++ b/examples/wayland/qtshell/images/background.jpg
Binary files differ
diff --git a/examples/wayland/qtshell/main.cpp b/examples/wayland/qtshell/main.cpp
new file mode 100644
index 000000000..2a7ba661e
--- /dev/null
+++ b/examples/wayland/qtshell/main.cpp
@@ -0,0 +1,21 @@
+// 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>
+
+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);
+
+ QQmlApplicationEngine appEngine(QUrl("qrc:///qml/main.qml"));
+
+ return app.exec();
+}
diff --git a/examples/wayland/qtshell/qml/Chrome.qml b/examples/wayland/qtshell/qml/Chrome.qml
new file mode 100644
index 000000000..a6456744c
--- /dev/null
+++ b/examples/wayland/qtshell/qml/Chrome.qml
@@ -0,0 +1,184 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+import QtWayland.Compositor
+import QtWayland.Compositor.QtShell
+
+QtShellChrome {
+ id: chrome
+
+ property alias shellSurface: shellSurfaceItemId.shellSurface
+
+ //! [maximizedRect]
+ maximizedRect: Qt.rect(usableArea.x,
+ usableArea.y,
+ usableArea.width,
+ usableArea.height)
+ //! [maximizedRect]
+
+ //! [leftResizeHandle]
+ Rectangle {
+ id: leftResizeHandle
+ color: "gray"
+ width: visible ? 5 : 0
+ anchors.topMargin: 5
+ anchors.bottomMargin: 5
+ anchors.left: parent.left
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ }
+ //! [leftResizeHandle]
+
+ Rectangle {
+ id: rightResizeHandle
+ color: "gray"
+ width: visible ? 5 : 0
+ anchors.topMargin: 5
+ anchors.bottomMargin: 5
+ anchors.right: parent.right
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ }
+
+ Rectangle {
+ id: topResizeHandle
+ color: "gray"
+ height: visible ? 5 : 0
+ anchors.leftMargin: 5
+ anchors.rightMargin: 5
+ anchors.left: parent.left
+ anchors.top: parent.top
+ anchors.right: parent.right
+ }
+
+ Rectangle {
+ id: bottomResizeHandle
+ color: "gray"
+ height: visible ? 5 : 0
+ anchors.leftMargin: 5
+ anchors.rightMargin: 5
+ anchors.left: parent.left
+ anchors.bottom: parent.bottom
+ anchors.right: parent.right
+ }
+
+ Rectangle {
+ id: titleBar
+ anchors.top: topResizeHandle.bottom
+ anchors.left: leftResizeHandle.right
+ anchors.right: rightResizeHandle.left
+ height: visible ? xButton.height + 10 : 0
+ color: shellSurface.active ? "cornflowerblue" : "lightgray"
+
+ Text {
+ anchors.left: parent.left
+ anchors.right: rowLayout.left
+ anchors.verticalCenter: parent.verticalCenter
+
+ font.pixelSize: xButton.height
+ text: shellSurface.windowTitle
+ fontSizeMode: Text.Fit
+ }
+
+ //! [buttons]
+ RowLayout {
+ id: rowLayout
+ anchors.right: parent.right
+ anchors.rightMargin: 5
+
+ ToolButton {
+ text: "-"
+ Layout.margins: 5
+ visible: (chrome.windowFlags & Qt.WindowMinimizeButtonHint) != 0
+ onClicked: {
+ chrome.toggleMinimized()
+ }
+ }
+
+ ToolButton {
+ text: "+"
+ Layout.margins: 5
+ visible: (chrome.windowFlags & Qt.WindowMaximizeButtonHint) != 0
+ onClicked: {
+ chrome.toggleMaximized()
+ }
+ }
+
+ ToolButton {
+ id: xButton
+ text: "X"
+ Layout.margins: 5
+ visible: (chrome.windowFlags & Qt.WindowCloseButtonHint) != 0
+ onClicked: shellSurface.sendClose()
+ }
+ }
+ //! [buttons]
+ }
+
+ Rectangle {
+ id: topLeftResizeHandle
+ color: "gray"
+ height: 5
+ width: 5
+ anchors.left: parent.left
+ anchors.top: parent.top
+ }
+
+ Rectangle {
+ id: topRightResizeHandle
+ color: "gray"
+ height: 5
+ width: 5
+ anchors.right: parent.right
+ anchors.top: parent.top
+ }
+
+ Rectangle {
+ id: bottomLeftResizeHandle
+ color: "gray"
+ height: 5
+ width: 5
+ anchors.left: parent.left
+ anchors.bottom: parent.bottom
+ }
+
+ Rectangle {
+ id: bottomRightResizeHandle
+ color: "gray"
+ height: 5
+ width: 5
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+ }
+
+ //! [decorations]
+ leftResizeHandle: leftResizeHandle
+ rightResizeHandle: rightResizeHandle
+ topResizeHandle: topResizeHandle
+ bottomResizeHandle: bottomResizeHandle
+ bottomLeftResizeHandle: bottomLeftResizeHandle
+ bottomRightResizeHandle: bottomRightResizeHandle
+ topLeftResizeHandle: topLeftResizeHandle
+ topRightResizeHandle: topRightResizeHandle
+ titleBar: titleBar
+ //! [decorations]
+
+ //! [shellsurfaceitem]
+ ShellSurfaceItem {
+ id: shellSurfaceItemId
+ anchors.top: titleBar.bottom
+ anchors.bottom: bottomResizeHandle.top
+ anchors.left: leftResizeHandle.right
+ anchors.right: rightResizeHandle.left
+
+ moveItem: chrome
+
+ staysOnBottom: shellSurface.windowFlags & Qt.WindowStaysOnBottomHint
+ staysOnTop: !staysOnBottom && shellSurface.windowFlags & Qt.WindowStaysOnTopHint
+ }
+ shellSurfaceItem: shellSurfaceItemId
+ //! [shellsurfaceitem]
+}
diff --git a/examples/wayland/qtshell/qml/CompositorScreen.qml b/examples/wayland/qtshell/qml/CompositorScreen.qml
new file mode 100644
index 000000000..c41d3258d
--- /dev/null
+++ b/examples/wayland/qtshell/qml/CompositorScreen.qml
@@ -0,0 +1,108 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Window
+import QtQuick.Controls
+import QtWayland.Compositor
+
+WaylandOutput {
+ id: output
+
+ property bool isNestedCompositor: Qt.platform.pluginName.startsWith("wayland") || Qt.platform.pluginName === "xcb"
+
+ //! [handleShellSurface]
+ property ListModel shellSurfaces: ListModel {}
+ function handleShellSurface(shellSurface) {
+ shellSurfaces.append({shellSurface: shellSurface});
+ }
+ //! [handleShellSurface]
+
+ // During development, it can be useful to start the compositor inside X11 or
+ // another Wayland compositor. In such cases, set sizeFollowsWindow to true to
+ // enable resizing of the compositor window to be forwarded to the Wayland clients
+ // as the output (screen) changing resolution. Consider setting it to false if you
+ // are running the compositor using eglfs, linuxfb or similar QPA backends.
+ sizeFollowsWindow: output.isNestedCompositor
+
+ window: Window {
+ width: 1920
+ height: 1080
+ visible: true
+
+ WaylandMouseTracker {
+ id: mouseTracker
+
+ anchors.fill: parent
+
+ // Set this to false to disable the outer mouse cursor when running nested
+ // compositors. Otherwise you would see two mouse cursors, one for each compositor.
+ windowSystemCursorEnabled: output.isNestedCompositor
+
+ Image {
+ id: background
+
+ anchors.fill: parent
+ fillMode: Image.Tile
+ source: "qrc:/images/background.jpg"
+ smooth: true
+
+ //! [repeater]
+ Repeater {
+ id: chromeRepeater
+ model: output.shellSurfaces
+ // Chrome displays a shell surface on the screen (See Chrome.qml)
+ Chrome {
+ shellSurface: modelData
+ onClientDestroyed:
+ {
+ output.shellSurfaces.remove(index)
+ }
+ }
+ }
+ //! [repeater]
+ }
+
+ Rectangle {
+ anchors.fill: taskbar
+ color: "lavenderblush"
+ }
+
+ //! [taskbar]
+ Row {
+ id: taskbar
+ height: 40
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: parent.bottom
+
+ Repeater {
+ anchors.fill: parent
+ model: output.shellSurfaces
+
+ ToolButton {
+ anchors.verticalCenter: parent.verticalCenter
+ text: modelData.windowTitle
+ onClicked: {
+ var item = chromeRepeater.itemAt(index)
+ if ((item.windowState & Qt.WindowMinimized) != 0)
+ item.toggleMinimized()
+ chromeRepeater.itemAt(index).activate()
+ }
+ }
+ }
+ }
+ //! [taskbar]
+
+ //! [usableArea]
+ Item {
+ id: usableArea
+ anchors.top: parent.top
+ anchors.left: parent.left
+ anchors.right: parent.right
+ anchors.bottom: taskbar.top
+ }
+ //! [usableArea]
+ }
+ }
+}
diff --git a/examples/wayland/qtshell/qml/main.qml b/examples/wayland/qtshell/qml/main.qml
new file mode 100644
index 000000000..7a4f505d5
--- /dev/null
+++ b/examples/wayland/qtshell/qml/main.qml
@@ -0,0 +1,21 @@
+// Copyright (C) 2021 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtWayland.Compositor
+import QtWayland.Compositor.QtShell
+
+WaylandCompositor {
+ id: waylandCompositor
+
+ CompositorScreen { id: screen; compositor: waylandCompositor }
+
+ // Shell surface extension. Needed to provide a window concept for Wayland clients.
+ // I.e. requests and events for maximization, minimization, resizing, closing etc.
+
+ //! [shell]
+ QtShell {
+ onQtShellSurfaceCreated: (qtShellSurface) => screen.handleShellSurface(qtShellSurface)
+ }
+ //! [shell]
+}
diff --git a/examples/wayland/qtshell/qtshell.pro b/examples/wayland/qtshell/qtshell.pro
new file mode 100644
index 000000000..c3f927b68
--- /dev/null
+++ b/examples/wayland/qtshell/qtshell.pro
@@ -0,0 +1,16 @@
+QT += gui qml quick quick-private
+
+SOURCES += \
+ main.cpp
+
+OTHER_FILES = \
+ qml/main.qml \
+ qml/CompositorScreen.qml \
+ qml/Chrome.qml \
+ images/background.jpg \
+
+RESOURCES += qtshell.qrc
+
+target.path = $$[QT_INSTALL_EXAMPLES]/wayland/qtshell
+sources.path = $$[QT_INSTALL_EXAMPLES]/wayland/qtshell
+INSTALLS += target sources
diff --git a/examples/wayland/qtshell/qtshell.qrc b/examples/wayland/qtshell/qtshell.qrc
new file mode 100644
index 000000000..7523ebaf4
--- /dev/null
+++ b/examples/wayland/qtshell/qtshell.qrc
@@ -0,0 +1,8 @@
+<RCC>
+ <qresource prefix="/">
+ <file>images/background.jpg</file>
+ <file>qml/main.qml</file>
+ <file>qml/CompositorScreen.qml</file>
+ <file>qml/Chrome.qml</file>
+ </qresource>
+</RCC>