diff options
Diffstat (limited to 'examples/wayland/qtshell')
-rw-r--r-- | examples/wayland/qtshell/CMakeLists.txt | 39 | ||||
-rw-r--r-- | examples/wayland/qtshell/doc/images/qtshell.jpg | bin | 0 -> 148259 bytes | |||
-rw-r--r-- | examples/wayland/qtshell/doc/src/qtshell.qdoc | 150 | ||||
-rw-r--r-- | examples/wayland/qtshell/images/background.jpg | bin | 0 -> 30730 bytes | |||
-rw-r--r-- | examples/wayland/qtshell/main.cpp | 21 | ||||
-rw-r--r-- | examples/wayland/qtshell/qml/Chrome.qml | 184 | ||||
-rw-r--r-- | examples/wayland/qtshell/qml/CompositorScreen.qml | 108 | ||||
-rw-r--r-- | examples/wayland/qtshell/qml/main.qml | 21 | ||||
-rw-r--r-- | examples/wayland/qtshell/qtshell.pro | 16 | ||||
-rw-r--r-- | examples/wayland/qtshell/qtshell.qrc | 8 |
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 Binary files differnew file mode 100644 index 000000000..53b9aa189 --- /dev/null +++ b/examples/wayland/qtshell/doc/images/qtshell.jpg 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 Binary files differnew file mode 100644 index 000000000..445567fbd --- /dev/null +++ b/examples/wayland/qtshell/images/background.jpg 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> |