diff --git a/.gitignore b/.gitignore index bb5ba9e9b..c333f4e91 100644 --- a/.gitignore +++ b/.gitignore @@ -5,5 +5,6 @@ build*/ .vscode/settings.json *.user +.cache/ */utils/dbus/xml2cpp/*_interface.* diff --git a/.reuse/dep5 b/.reuse/dep5 index c8b2eb211..8d3554065 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -47,3 +47,8 @@ License: GPL-3.0-or-later Files: *.json Copyright: None License: CC0-1.0 + +# wayland protocols +Files: frame/layershell/protocol/wlr-layer-shell-unstable-v1.xml +Copyright: 2017 Drew DeVault +License: MIT diff --git a/CMakeLists.txt b/CMakeLists.txt index 7cc02dac7..5ab4d00dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,7 @@ project(DDEShell VERSION "${DS_VERSION}" DESCRIPTION "dde-shell" HOMEPAGE_URL "https://github.com/linuxdeepin/dde-shell" - LANGUAGES CXX + LANGUAGES CXX C ) set(CMAKE_CXX_STANDARD 17) @@ -44,11 +44,13 @@ set(INCLUDE_INSTALL_DIR "${CMAKE_INSTALL_INCLUDEDIR}/dde-shell" CACHE STRING "He set(CONFIG_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/cmake/DDEShell" CACHE STRING "CMake config file install directory") set(QML_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/qt${QT_VERSION_MAJOR}/qml" CACHE STRING "Qml plugin install directory") -list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") +find_package(ECM REQUIRED MO_MODULE) +set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake;${CMAKE_MODULE_PATH};${ECM_MODULE_PATH};${PROJECT_SOURCE_DIR}/cmake") include(DDEShellPackageMacros) -find_package(Qt${QT_VERSION_MAJOR} ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS Core Gui Concurrent Quick) +find_package(Qt${QT_VERSION_MAJOR} ${REQUIRED_QT_VERSION} REQUIRED COMPONENTS Core Gui Concurrent Quick WaylandClient) find_package(Dtk${DTK_VERSION_MAJOR} REQUIRED COMPONENTS Core Gui) +find_package(WaylandProtocols REQUIRED) add_subdirectory(frame) add_subdirectory(shell) diff --git a/LICENSES/MIT.txt b/LICENSES/MIT.txt new file mode 100644 index 000000000..4bd7e7cab --- /dev/null +++ b/LICENSES/MIT.txt @@ -0,0 +1,24 @@ +MIT "Expat" License + +Copyright (c) 2017 Drew DeVault + +Permission to use, copy, modify, distribute, and sell this +software and its documentation for any purpose is hereby granted +without fee, provided that the above copyright notice appear in +all copies and that both that copyright notice and this permission +notice appear in supporting documentation, and that the name of +the copyright holders not be used in advertising or publicity +pertaining to distribution of the software without specific, +written prior permission. The copyright holders make no +representations about the suitability of this software for any +purpose. It is provided "as is" without express or implied +warranty. + +THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS +SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND +FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY +SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN +AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. \ No newline at end of file diff --git a/debian/control b/debian/control index 1423ee043..8e32e8624 100644 --- a/debian/control +++ b/debian/control @@ -9,9 +9,13 @@ Build-Depends: qt6-declarative-dev, qt6-base-dev-tools, qt6-tools-dev, + qt6-wayland-dev(>=6.5), + qt6-wayland-dev-tools, + qt6-wayland-private-dev, libdtk6gui-dev, libdtk6core-dev, libdtkcommon-dev, + extra-cmake-modules, Standards-Version: 3.9.8 Homepage: http://www.deepin.org diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index fa0a90eda..aeef68da7 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -8,3 +8,4 @@ add_subdirectory(applet-example) add_subdirectory(applet-example-data) add_subdirectory(containment-example) add_subdirectory(corona-example) +add_subdirectory(layershell-example) diff --git a/example/corona-example/package/main.qml b/example/corona-example/package/main.qml index 90b48d2dd..4a9f4d386 100644 --- a/example/corona-example/package/main.qml +++ b/example/corona-example/package/main.qml @@ -15,6 +15,7 @@ Window { width: Screen.width height: 200 D.DWindow.enabled: true + DLayerShellWindow.anchors: DLayerShellWindow.AnchorBottom Control { anchors.fill: parent diff --git a/example/layershell-example/CMakeLists.txt b/example/layershell-example/CMakeLists.txt new file mode 100644 index 000000000..1388005af --- /dev/null +++ b/example/layershell-example/CMakeLists.txt @@ -0,0 +1,6 @@ +# SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +# +# SPDX-License-Identifier: GPL-3.0-or-later + +add_executable(layershell-example main.cpp qml.qrc) +target_link_libraries(layershell-example PRIVATE Qt::Gui Qt::Qml) diff --git a/example/layershell-example/main.cpp b/example/layershell-example/main.cpp new file mode 100644 index 000000000..b80be1db0 --- /dev/null +++ b/example/layershell-example/main.cpp @@ -0,0 +1,15 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include +#include + +int main(int argc, char* argv[]) +{ + QGuiApplication app(argc, argv); + QQmlApplicationEngine engine; + engine.addImportPath(QCoreApplication::applicationDirPath() + "/../../plugins/"); + engine.load("qrc:///org/deepin/layershell/main.qml"); + return app.exec(); +} diff --git a/example/layershell-example/main.qml b/example/layershell-example/main.qml new file mode 100644 index 000000000..a8307d927 --- /dev/null +++ b/example/layershell-example/main.qml @@ -0,0 +1,182 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +import QtQuick 2.15 +import QtQuick.Controls +import QtQuick.Layouts +import org.deepin.ds + +Window { + id: root + height: 150 + width: 780 + visible: true + DLayerShellWindow.anchors: DLayerShellWindow.AnchorLeft + DLayerShellWindow.layer: DLayerShellWindow.LayerTop + DLayerShellWindow.exclusionZone: excludezone.value + DLayerShellWindow.keyboardInteractivity: DLayerShellWindow.KeyboardInteractivityExclusive + DLayerShellWindow.leftMargin: left.value + DLayerShellWindow.rightMargin: right.value + DLayerShellWindow.topMargin: top.value + DLayerShellWindow.bottomMargin: bottom.value + + Column { + id: comboBox + ComboBox { + id: layerCombox + width: 100 + textRole: "text" + anchors.left: parent.left + Component.onCompleted: currentIndex = root.DLayerShellWindow.layer + onActivated: { + root.DLayerShellWindow.layer = model.get(layerCombox.currentIndex).layer + } + model: ListModel { + ListElement { text: "LayerBackground"; layer: DLayerShellWindow.LayerBackground } + ListElement { text: "LayerButtom"; layer: DLayerShellWindow.LayerButtom } + ListElement { text: "LayerTop"; layer: DLayerShellWindow.LayerTop } + ListElement { text: "LayerOverlay"; layer: DLayerShellWindow.LayerOverlay } + } + } + + ComboBox { + id: keyboardInteractivityCombox + width: 200 + textRole: "text" + anchors.left: parent.left + Component.onCompleted: currentIndex = root.DLayerShellWindow.keyboardInteractivity + onActivated: { + root.DLayerShellWindow.keyboardInteractivity = model.get(keyboardInteractivityCombox.currentIndex).keyboardInteractivity + } + model: ListModel { + ListElement { text: "KeyboardInteractivityNone"; keyboardInteractivity: DLayerShellWindow.KeyboardInteractivityNone } + ListElement { text: "KeyboardInteractivityExclusive"; keyboardInteractivity: DLayerShellWindow.KeyboardInteractivityExclusive } + ListElement { text: "KeyboardInteractivityOnDemand"; keyboardInteractivity: DLayerShellWindow.KeyboardInteractivityOnDemand } + } + } + } + + Item { + id: steerwheel + anchors.left: comboBox.right + anchors.leftMargin: 20 + width: 100 + Column { + spacing: 20 + Button { + id: buttonTop + text: "Top" + width: 40 + anchors.leftMargin: 30 + anchors.left: parent.left + checkable: true + checked: root.DLayerShellWindow.anchors & DLayerShellWindow.AnchorTop + onCheckedChanged: { + if (checked) + root.DLayerShellWindow.anchors = root.DLayerShellWindow.anchors | DLayerShellWindow.AnchorTop + else + root.DLayerShellWindow.anchors = root.DLayerShellWindow.anchors ^ DLayerShellWindow.AnchorTop + } + } + Row { + spacing: 20 + Button { + id: buttonLeft + text: "Left" + width: 40 + checkable: true + checked: root.DLayerShellWindow.anchors & DLayerShellWindow.AnchorLeft + onCheckedChanged: { + if (checked) + root.DLayerShellWindow.anchors = root.DLayerShellWindow.anchors | DLayerShellWindow.AnchorLeft + else + root.DLayerShellWindow.anchors = root.DLayerShellWindow.anchors ^ DLayerShellWindow.AnchorLeft + } + } + Button { + id: buttonRight + text: "Right" + width: 40 + checkable: true + checked: root.DLayerShellWindow.anchors & DLayerShellWindow.AnchorRight + onCheckedChanged: { + if (checked) + root.DLayerShellWindow.anchors = root.DLayerShellWindow.anchors | DLayerShellWindow.AnchorRight + else + root.DLayerShellWindow.anchors = root.DLayerShellWindow.anchors ^ DLayerShellWindow.AnchorRight + } + } + } + Button { + id: buttonBottom + text: "Buttom" + width: 40 + anchors.leftMargin: 30 + anchors.left: parent.left + checkable: true + checked: root.DLayerShellWindow.anchors & DLayerShellWindow.AnchorBottom + onCheckedChanged: { + if (checked) + root.DLayerShellWindow.anchors = root.DLayerShellWindow.anchors | DLayerShellWindow.AnchorBottom + else + root.DLayerShellWindow.anchors = root.DLayerShellWindow.anchors ^ DLayerShellWindow.AnchorBottom + } + } + } + } + + GridLayout { + id: input + anchors.left: steerwheel.right + anchors.leftMargin: 20 + columns: 4 + Text { + text: "LeftMargin: " + } + SpinBox { + id: left + stepSize: 10 + editable: true + value: root.DLayerShellWindow.leftMargin + } + Text { + text: "TopMargin: " + } + SpinBox { + id: top + stepSize: 10 + editable: true + value: root.DLayerShellWindow.topMargin + } + Text { + text: "RightMargin: " + } + SpinBox { + id: right + stepSize: 10 + editable: true + value: root.DLayerShellWindow.rightMargin + } + Text { + text: "BottomMargin: " + } + SpinBox { + id: bottom + stepSize: 10 + editable: true + value: root.DLayerShellWindow.bottomMargin + } + Text { + text: "excludezone: " + } + + SpinBox { + id: excludezone + editable: true + from: -1 + to: 1080 + value: -1 + } + } +} diff --git a/example/layershell-example/qml.qrc b/example/layershell-example/qml.qrc new file mode 100644 index 000000000..840377037 --- /dev/null +++ b/example/layershell-example/qml.qrc @@ -0,0 +1,5 @@ + + + main.qml + + diff --git a/frame/CMakeLists.txt b/frame/CMakeLists.txt index 0303cc21e..00623ad90 100644 --- a/frame/CMakeLists.txt +++ b/frame/CMakeLists.txt @@ -1,7 +1,6 @@ # SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. # # SPDX-License-Identifier: CC0-1.0 - set(PUBLIC_HEADERS dsglobal.h dstypes.h @@ -12,6 +11,7 @@ set(PUBLIC_HEADERS containment.h corona.h qmlengine.h + layershell/dlayershellwindow.h ) add_library(dde-shell-frame SHARED ${PUBLIC_HEADERS} @@ -31,6 +31,14 @@ add_library(dde-shell-frame SHARED appletitem.cpp containmentitem.cpp qmlengine.cpp + layershell/dlayershellwindow.cpp + layershell/qwaylandlayershellsurface.cpp + layershell/qwaylandlayershellintegration.cpp +) + +qt_generate_wayland_protocol_client_sources(dde-shell-frame FILES + ${CMAKE_CURRENT_SOURCE_DIR}/layershell/protocol/wlr-layer-shell-unstable-v1.xml + ${WaylandProtocols_DATADIR}/stable/xdg-shell/xdg-shell.xml ) set_target_properties(dde-shell-frame PROPERTIES @@ -45,6 +53,8 @@ PUBLIC Qt${QT_VERSION_MAJOR}::Core Qt${QT_VERSION_MAJOR}::Gui Qt${QT_VERSION_MAJOR}::Quick +PRIVATE + Qt${QT_VERSION_MAJOR}::WaylandClientPrivate ) target_include_directories(dde-shell-frame INTERFACE $ diff --git a/frame/layershell/dlayershellwindow.cpp b/frame/layershell/dlayershellwindow.cpp new file mode 100644 index 000000000..358f42bf5 --- /dev/null +++ b/frame/layershell/dlayershellwindow.cpp @@ -0,0 +1,243 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "dsglobal.h" +#include "dlayershellwindow.h" +#include "qwaylandlayershellintegration_p.h" + +#include +#include +#include +#include + +#include + +Q_LOGGING_CATEGORY(layershellwindow, "dde.shell.layershell.window") + +DS_BEGIN_NAMESPACE + +class DLayerShellWindowPrivate +{ +public: + explicit DLayerShellWindowPrivate(QWindow* window) + : parentWindow(window) + { + + } + + QWindow* parentWindow; + QString scope = QStringLiteral("window"); + DLayerShellWindow::Anchors anchors = {DLayerShellWindow::AnchorTop | DLayerShellWindow::AnchorBottom | DLayerShellWindow::AnchorLeft | DLayerShellWindow::AnchorRight}; + int32_t exclusionZone = 0; + DLayerShellWindow::KeyboardInteractivity keyboardInteractivity = DLayerShellWindow::KeyboardInteractivityExclusive; + DLayerShellWindow::Layer layer = DLayerShellWindow::LayerTop; + int leftMargin = 0; + int rightMargin = 0; + int topMargin = 0; + int bottomMargin = 0; + DLayerShellWindow::ScreenConfiguration screenConfiguration = DLayerShellWindow::ScreenFromQWindow; + bool closeOnDismissed = true; +}; + +class DLayerShellWindowWaylandPrivate : public DLayerShellWindowPrivate +{ +public: + explicit DLayerShellWindowWaylandPrivate(QWindow * window) + : DLayerShellWindowPrivate(window) + { + QtWaylandClient::QWaylandWindow* waylandWindow = dynamic_cast(window->handle()); + static QWaylandLayerShellIntegration *shellIntegration = nullptr; + if (!shellIntegration) { + shellIntegration = new QWaylandLayerShellIntegration(); + if (!shellIntegration->initialize(waylandWindow->display())) { + delete shellIntegration; + shellIntegration = nullptr; + qCWarning(layershellwindow) << "failed to init dlayershell intergration"; + return; + } + } + waylandWindow->setShellIntegration(shellIntegration); + } +}; +void DLayerShellWindow::setAnchors(DLayerShellWindow::Anchors anchors) +{ + if (anchors != d->anchors) { + d->anchors = anchors; + Q_EMIT anchorsChanged(); + } +} + +DLayerShellWindow::Anchors DLayerShellWindow::anchors() const +{ + return d->anchors; +} + +void DLayerShellWindow::setExclusiveZone(int32_t zone) +{ + if (zone != d->exclusionZone) { + d->exclusionZone = zone; + Q_EMIT exclusionZoneChanged(); + } +} + +int32_t DLayerShellWindow::exclusionZone() const +{ + return d->exclusionZone; +} + +void DLayerShellWindow::setLeftMargin(const int &marginLeft) +{ + if (marginLeft != d->leftMargin) { + d->leftMargin = marginLeft; + Q_EMIT marginsChanged(); + } +} + +int DLayerShellWindow::leftMargin() const +{ + return d->leftMargin; +} + +void DLayerShellWindow::setRightMargin(const int &marginRight) +{ + if (marginRight != d->rightMargin) { + d->rightMargin = marginRight; + Q_EMIT marginsChanged(); + } +} + +int DLayerShellWindow::rightMargin() const +{ + return d->rightMargin; +} + +void DLayerShellWindow::setTopMargin(const int &marginTop) +{ + if (marginTop != d->topMargin) { + d->topMargin = marginTop; + Q_EMIT marginsChanged(); + } +} + +int DLayerShellWindow::topMargin() const +{ + return d->topMargin; +} + +void DLayerShellWindow::setBottomMargin(const int &marginBottom) +{ + if (marginBottom != d->bottomMargin) { + d->bottomMargin = marginBottom; + Q_EMIT marginsChanged(); + } +} + +int DLayerShellWindow::bottomMargin() const +{ + return d->bottomMargin; +} + +void DLayerShellWindow::setKeyboardInteractivity(DLayerShellWindow::KeyboardInteractivity interactivity) +{ + if (interactivity != d->keyboardInteractivity) { + d->keyboardInteractivity = interactivity; + Q_EMIT keyboardInteractivityChanged(); + } + +} + +DLayerShellWindow::KeyboardInteractivity DLayerShellWindow::keyboardInteractivity() const +{ + return d->keyboardInteractivity; +} + +void DLayerShellWindow::setLayer(DLayerShellWindow::Layer layer) +{ + if (layer != d->layer) { + d->layer = layer; + Q_EMIT layerChanged(); + } +} + +DLayerShellWindow::ScreenConfiguration DLayerShellWindow::screenConfiguration() const +{ + return d->screenConfiguration; +} + +void DLayerShellWindow::setScreenConfiguration(DLayerShellWindow::ScreenConfiguration screenConfiguration) +{ + if (screenConfiguration != d->screenConfiguration) { + d->screenConfiguration = screenConfiguration; + } +} + +bool DLayerShellWindow::closeOnDismissed() const +{ + return d->closeOnDismissed; +} + +void DLayerShellWindow::setCloseOnDismissed(bool close) +{ + if (close != d->closeOnDismissed) { + d->closeOnDismissed = close; + } +} + +void DLayerShellWindow::setScope(const QString& scope) +{ + if (scope != d->scope) { + d->scope = scope; + Q_EMIT scopeChanged(); + } +} + +QString DLayerShellWindow::scope() const +{ + return d->scope; +} + +DLayerShellWindow::Layer DLayerShellWindow::layer() const +{ + return d->layer; +} + +static QMap s_map; + +DLayerShellWindow::~DLayerShellWindow() +{ + s_map.remove(d->parentWindow); +} + +DLayerShellWindow::DLayerShellWindow(QWindow* window) + : QObject(window) +{ + s_map.insert(window, this); + window->create(); + auto waylandWindow = dynamic_cast(window->handle()); + if (waylandWindow) { + d.reset(new DLayerShellWindowWaylandPrivate(window)); + } else { + qCWarning(layershellwindow) << "not a wayland window, will not create zwlr_layer_surface"; + d.reset(new DLayerShellWindowPrivate(window)); + } +} + +DLayerShellWindow* DLayerShellWindow::get(QWindow* window) +{ + auto dlayerShellWindow = s_map.value(window); + if (dlayerShellWindow) { + return dlayerShellWindow; + } + return new DLayerShellWindow(window); +} + +DLayerShellWindow* DLayerShellWindow::qmlAttachedProperties(QObject *object) +{ + auto window = qobject_cast(object); + if (window) + return get(window); + qCWarning(layershellwindow) << "not a qwindow unable to create DLayerShellWindow"; + return nullptr; +} +DS_END_NAMESPACE diff --git a/frame/layershell/dlayershellwindow.h b/frame/layershell/dlayershellwindow.h new file mode 100644 index 000000000..0df888491 --- /dev/null +++ b/frame/layershell/dlayershellwindow.h @@ -0,0 +1,152 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "dsglobal.h" + +#include +#include +#include +#include + +DS_BEGIN_NAMESPACE + +class DLayerShellWindowPrivate; + +class Q_DECL_EXPORT DLayerShellWindow : public QObject +{ + Q_OBJECT + Q_PROPERTY(Anchors anchors READ anchors WRITE setAnchors NOTIFY anchorsChanged) + Q_PROPERTY(QString scope READ scope WRITE setScope) + Q_PROPERTY(int leftMargin READ leftMargin WRITE setLeftMargin NOTIFY marginsChanged) + Q_PROPERTY(int rightMargin READ rightMargin WRITE setRightMargin NOTIFY marginsChanged) + Q_PROPERTY(int topMargin READ topMargin WRITE setTopMargin NOTIFY marginsChanged) + Q_PROPERTY(int bottomMargin READ bottomMargin WRITE setBottomMargin NOTIFY marginsChanged) + Q_PROPERTY(uint32_t exclusionZone READ exclusionZone WRITE setExclusiveZone NOTIFY exclusionZoneChanged) + Q_PROPERTY(Layer layer READ layer WRITE setLayer NOTIFY layerChanged) + Q_PROPERTY(KeyboardInteractivity keyboardInteractivity READ keyboardInteractivity WRITE setKeyboardInteractivity NOTIFY keyboardInteractivityChanged) + Q_PROPERTY(ScreenConfiguration screenConfiguration READ screenConfiguration WRITE setScreenConfiguration) + +public: + ~DLayerShellWindow() override; + + /** + * This enum type is used to specify the position where to anchor + */ + enum Anchor { + AnchorNone = 0, + AnchorTop = 1, + AnchorBottom = 2, + AnchorLeft = 4, + AnchorRight = 8 + }; + + Q_ENUM(Anchor) + Q_DECLARE_FLAGS(Anchors, Anchor) + + /** + * This enum type is used to specify the layer where a surface can be put in. + */ + enum Layer { + LayerBackground = 0, + LayerButtom = 1, + LayerTop = 2, + LayerOverlay = 3, + }; + + Q_ENUM(Layer) + + /** + * This enum type is used to specify how the layer surface handles keyboard focus. + */ + enum KeyboardInteractivity { + // this surface is not interested in keyboard events and the compositor should never assign it the keyboard focus. + KeyboardInteractivityNone = 0, + // Request exclusive keyboard focus if this surface is above the shell surface layer. + KeyboardInteractivityExclusive = 1, + // This requests the compositor to allow this surface to be focused and unfocused by the user in an implementation-defined manner. + KeyboardInteractivityOnDemand = 2, + }; + + /** + * This enum type is used to specify which screen to place the surface on. + * ScreenFromQWindow (the default) reads QWindow::screen() while ScreenFromCompositor + * passes nil and lets the compositor decide. + */ + enum ScreenConfiguration { + ScreenFromQWindow = 0, + ScreenFromCompositor = 1, + }; + + Q_ENUM(KeyboardInteractivity) + + void setAnchors(Anchors anchor); + Anchors anchors() const; + + void setExclusiveZone(int32_t zone); + int32_t exclusionZone() const; + + void setLeftMargin(const int &marginLeft); + int leftMargin() const; + + void setRightMargin(const int &marginRight); + int rightMargin() const; + + void setTopMargin(const int &marginTop); + int topMargin() const; + + void setBottomMargin(const int &marginBottom); + int bottomMargin() const; + + void setKeyboardInteractivity(KeyboardInteractivity interactivity); + KeyboardInteractivity keyboardInteractivity() const; + + void setLayer(Layer layer); + Layer layer() const; + + void setScreenConfiguration(ScreenConfiguration screenConfiguration); + ScreenConfiguration screenConfiguration() const; + + /** + * Sets a string based identifier for this window. + * This may be used by a compositor to determine stacking + * order within a given layer. + * + * May also be referred to as a role + */ + void setScope(const QString &scope); + QString scope() const; + + /** + * Whether the QWindow should be closed when the layer surface is dismissed by the compositor. + * For example, if the associated screen has been removed. + * + * This can be used to map the window on another screen. + */ + void setCloseOnDismissed(bool close); + bool closeOnDismissed() const; + + /** + * Gets the LayerShell Window for a given Qt Window + * Ownership is not transferred + */ + static DLayerShellWindow* get(QWindow* window); + + static DLayerShellWindow *qmlAttachedProperties(QObject *object); + +Q_SIGNALS: + void anchorsChanged(); + void exclusionZoneChanged(); + void marginsChanged(); + void keyboardInteractivityChanged(); + void layerChanged(); + void scopeChanged(); + +private: + DLayerShellWindow(QWindow* window); + QScopedPointer d; +}; + +DS_END_NAMESPACE diff --git a/frame/layershell/protocol/wlr-layer-shell-unstable-v1.xml b/frame/layershell/protocol/wlr-layer-shell-unstable-v1.xml new file mode 100644 index 000000000..b53f7a827 --- /dev/null +++ b/frame/layershell/protocol/wlr-layer-shell-unstable-v1.xml @@ -0,0 +1,390 @@ + + + + Copyright © 2017 Drew DeVault + + Permission to use, copy, modify, distribute, and sell this + software and its documentation for any purpose is hereby granted + without fee, provided that the above copyright notice appear in + all copies and that both that copyright notice and this permission + notice appear in supporting documentation, and that the name of + the copyright holders not be used in advertising or publicity + pertaining to distribution of the software without specific, + written prior permission. The copyright holders make no + representations about the suitability of this software for any + purpose. It is provided "as is" without express or implied + warranty. + + THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS + SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND + FITNESS, IN NO EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY + SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN + AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, + ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF + THIS SOFTWARE. + + + + + Clients can use this interface to assign the surface_layer role to + wl_surfaces. Such surfaces are assigned to a "layer" of the output and + rendered with a defined z-depth respective to each other. They may also be + anchored to the edges and corners of a screen and specify input handling + semantics. This interface should be suitable for the implementation of + many desktop shell components, and a broad number of other applications + that interact with the desktop. + + + + + Create a layer surface for an existing surface. This assigns the role of + layer_surface, or raises a protocol error if another role is already + assigned. + + Creating a layer surface from a wl_surface which has a buffer attached + or committed is a client error, and any attempts by a client to attach + or manipulate a buffer prior to the first layer_surface.configure call + must also be treated as errors. + + After creating a layer_surface object and setting it up, the client + must perform an initial commit without any buffer attached. + The compositor will reply with a layer_surface.configure event. + The client must acknowledge it and is then allowed to attach a buffer + to map the surface. + + You may pass NULL for output to allow the compositor to decide which + output to use. Generally this will be the one that the user most + recently interacted with. + + Clients can specify a namespace that defines the purpose of the layer + surface. + + + + + + + + + + + + + + + + + These values indicate which layers a surface can be rendered in. They + are ordered by z depth, bottom-most first. Traditional shell surfaces + will typically be rendered between the bottom and top layers. + Fullscreen shell surfaces are typically rendered at the top layer. + Multiple surfaces can share a single layer, and ordering within a + single layer is undefined. + + + + + + + + + + + + + This request indicates that the client will not use the layer_shell + object any more. Objects that have been created through this instance + are not affected. + + + + + + + An interface that may be implemented by a wl_surface, for surfaces that + are designed to be rendered as a layer of a stacked desktop-like + environment. + + Layer surface state (layer, size, anchor, exclusive zone, + margin, interactivity) is double-buffered, and will be applied at the + time wl_surface.commit of the corresponding wl_surface is called. + + Attaching a null buffer to a layer surface unmaps it. + + Unmapping a layer_surface means that the surface cannot be shown by the + compositor until it is explicitly mapped again. The layer_surface + returns to the state it had right after layer_shell.get_layer_surface. + The client can re-map the surface by performing a commit without any + buffer attached, waiting for a configure event and handling it as usual. + + + + + Sets the size of the surface in surface-local coordinates. The + compositor will display the surface centered with respect to its + anchors. + + If you pass 0 for either value, the compositor will assign it and + inform you of the assignment in the configure event. You must set your + anchor to opposite edges in the dimensions you omit; not doing so is a + protocol error. Both values are 0 by default. + + Size is double-buffered, see wl_surface.commit. + + + + + + + + Requests that the compositor anchor the surface to the specified edges + and corners. If two orthogonal edges are specified (e.g. 'top' and + 'left'), then the anchor point will be the intersection of the edges + (e.g. the top left corner of the output); otherwise the anchor point + will be centered on that edge, or in the center if none is specified. + + Anchor is double-buffered, see wl_surface.commit. + + + + + + + Requests that the compositor avoids occluding an area with other + surfaces. The compositor's use of this information is + implementation-dependent - do not assume that this region will not + actually be occluded. + + A positive value is only meaningful if the surface is anchored to one + edge or an edge and both perpendicular edges. If the surface is not + anchored, anchored to only two perpendicular edges (a corner), anchored + to only two parallel edges or anchored to all edges, a positive value + will be treated the same as zero. + + A positive zone is the distance from the edge in surface-local + coordinates to consider exclusive. + + Surfaces that do not wish to have an exclusive zone may instead specify + how they should interact with surfaces that do. If set to zero, the + surface indicates that it would like to be moved to avoid occluding + surfaces with a positive exclusive zone. If set to -1, the surface + indicates that it would not like to be moved to accommodate for other + surfaces, and the compositor should extend it all the way to the edges + it is anchored to. + + For example, a panel might set its exclusive zone to 10, so that + maximized shell surfaces are not shown on top of it. A notification + might set its exclusive zone to 0, so that it is moved to avoid + occluding the panel, but shell surfaces are shown underneath it. A + wallpaper or lock screen might set their exclusive zone to -1, so that + they stretch below or over the panel. + + The default value is 0. + + Exclusive zone is double-buffered, see wl_surface.commit. + + + + + + + Requests that the surface be placed some distance away from the anchor + point on the output, in surface-local coordinates. Setting this value + for edges you are not anchored to has no effect. + + The exclusive zone includes the margin. + + Margin is double-buffered, see wl_surface.commit. + + + + + + + + + + Types of keyboard interaction possible for layer shell surfaces. The + rationale for this is twofold: (1) some applications are not interested + in keyboard events and not allowing them to be focused can improve the + desktop experience; (2) some applications will want to take exclusive + keyboard focus. + + + + + This value indicates that this surface is not interested in keyboard + events and the compositor should never assign it the keyboard focus. + + This is the default value, set for newly created layer shell surfaces. + + This is useful for e.g. desktop widgets that display information or + only have interaction with non-keyboard input devices. + + + + + Request exclusive keyboard focus if this surface is above the shell surface layer. + + For the top and overlay layers, the seat will always give + exclusive keyboard focus to the top-most layer which has keyboard + interactivity set to exclusive. If this layer contains multiple + surfaces with keyboard interactivity set to exclusive, the compositor + determines the one receiving keyboard events in an implementation- + defined manner. In this case, no guarantee is made when this surface + will receive keyboard focus (if ever). + + For the bottom and background layers, the compositor is allowed to use + normal focus semantics. + + This setting is mainly intended for applications that need to ensure + they receive all keyboard events, such as a lock screen or a password + prompt. + + + + + This requests the compositor to allow this surface to be focused and + unfocused by the user in an implementation-defined manner. The user + should be able to unfocus this surface even regardless of the layer + it is on. + + Typically, the compositor will want to use its normal mechanism to + manage keyboard focus between layer shell surfaces with this setting + and regular toplevels on the desktop layer (e.g. click to focus). + Nevertheless, it is possible for a compositor to require a special + interaction to focus or unfocus layer shell surfaces (e.g. requiring + a click even if focus follows the mouse normally, or providing a + keybinding to switch focus between layers). + + This setting is mainly intended for desktop shell components (e.g. + panels) that allow keyboard interaction. Using this option can allow + implementing a desktop shell that can be fully usable without the + mouse. + + + + + + + Set how keyboard events are delivered to this surface. By default, + layer shell surfaces do not receive keyboard events; this request can + be used to change this. + + This setting is inherited by child surfaces set by the get_popup + request. + + Layer surfaces receive pointer, touch, and tablet events normally. If + you do not want to receive them, set the input region on your surface + to an empty region. + + Keyboard interactivity is double-buffered, see wl_surface.commit. + + + + + + + This assigns an xdg_popup's parent to this layer_surface. This popup + should have been created via xdg_surface::get_popup with the parent set + to NULL, and this request must be invoked before committing the popup's + initial state. + + See the documentation of xdg_popup for more details about what an + xdg_popup is and how it is used. + + + + + + + When a configure event is received, if a client commits the + surface in response to the configure event, then the client + must make an ack_configure request sometime before the commit + request, passing along the serial of the configure event. + + If the client receives multiple configure events before it + can respond to one, it only has to ack the last configure event. + + A client is not required to commit immediately after sending + an ack_configure request - it may even ack_configure several times + before its next surface commit. + + A client may send multiple ack_configure requests before committing, but + only the last request sent before a commit indicates which configure + event the client really is responding to. + + + + + + + This request destroys the layer surface. + + + + + + The configure event asks the client to resize its surface. + + Clients should arrange their surface for the new states, and then send + an ack_configure request with the serial sent in this configure event at + some point before committing the new surface. + + The client is free to dismiss all but the last configure event it + received. + + The width and height arguments specify the size of the window in + surface-local coordinates. + + The size is a hint, in the sense that the client is free to ignore it if + it doesn't resize, pick a smaller size (to satisfy aspect ratio or + resize in steps of NxM pixels). If the client picks a smaller size and + is anchored to two opposite anchors (e.g. 'top' and 'bottom'), the + surface will be centered on this axis. + + If the width or height arguments are zero, it means the client should + decide its own window dimension. + + + + + + + + + The closed event is sent by the compositor when the surface will no + longer be shown. The output may have been destroyed or the user may + have asked for it to be removed. Further changes to the surface will be + ignored. The client should destroy the resource after receiving this + event, and create a new surface if they so choose. + + + + + + + + + + + + + + + + + + + + + + Change the layer that the surface is rendered on. + + Layer is double-buffered, see wl_surface.commit. + + + + + diff --git a/frame/layershell/qwaylandlayershellintegration.cpp b/frame/layershell/qwaylandlayershellintegration.cpp new file mode 100644 index 000000000..97b7cf388 --- /dev/null +++ b/frame/layershell/qwaylandlayershellintegration.cpp @@ -0,0 +1,28 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "dsglobal.h" +#include "qwaylandlayershellintegration_p.h" +#include "qwaylandlayershellsurface_p.h" + +DS_BEGIN_NAMESPACE + +QWaylandLayerShellIntegration::QWaylandLayerShellIntegration() + : QWaylandShellIntegrationTemplate(4) +{ +} + +QWaylandLayerShellIntegration::~QWaylandLayerShellIntegration() +{ + if (object() && zwlr_layer_shell_v1_get_version(object()) >= ZWLR_LAYER_SHELL_V1_DESTROY_SINCE_VERSION) { + zwlr_layer_shell_v1_destroy(object()); + } +} + +QtWaylandClient::QWaylandShellSurface *QWaylandLayerShellIntegration::createShellSurface(QtWaylandClient::QWaylandWindow *window) +{ + return new QWaylandLayeShellSurface(this, window); +} + +DS_END_NAMESPACE diff --git a/frame/layershell/qwaylandlayershellintegration_p.h b/frame/layershell/qwaylandlayershellintegration_p.h new file mode 100644 index 000000000..b9ee22f0c --- /dev/null +++ b/frame/layershell/qwaylandlayershellintegration_p.h @@ -0,0 +1,25 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "dsglobal.h" + +#include + +#include + +DS_BEGIN_NAMESPACE + +class QWaylandLayerShellIntegration : public QtWaylandClient::QWaylandShellIntegrationTemplate, public QtWayland::zwlr_layer_shell_v1 +{ + +public: + QWaylandLayerShellIntegration(); + ~QWaylandLayerShellIntegration() override; + + QtWaylandClient::QWaylandShellSurface *createShellSurface(QtWaylandClient::QWaylandWindow *window) override; +}; + +DS_END_NAMESPACE diff --git a/frame/layershell/qwaylandlayershellsurface.cpp b/frame/layershell/qwaylandlayershellsurface.cpp new file mode 100644 index 000000000..b148d8d64 --- /dev/null +++ b/frame/layershell/qwaylandlayershellsurface.cpp @@ -0,0 +1,127 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "dsglobal.h" +#include "dlayershellwindow.h" +#include "qwaylandlayershellsurface_p.h" + +#include +#include + +#include + +#include +#include +#include + +Q_LOGGING_CATEGORY(layershellsurface, "dde.shell.layershell.surface") + +DS_BEGIN_NAMESPACE + +QWaylandLayeShellSurface::QWaylandLayeShellSurface(QtWayland::zwlr_layer_shell_v1 *shell, QtWaylandClient::QWaylandWindow *window) + : QtWaylandClient::QWaylandShellSurface(window) + , QtWayland::zwlr_layer_surface_v1() + , m_dlayerShellWindow(DLayerShellWindow::get(window->window())) +{ + + wl_output *output = nullptr; + if (m_dlayerShellWindow->screenConfiguration() == DLayerShellWindow::ScreenFromQWindow) { + auto waylandScreen = dynamic_cast(window->window()->screen()->handle()); + if (!waylandScreen) { + qCWarning(layershellsurface) << "failed to get screen for wayland"; + } else { + output = waylandScreen->output(); + } + } + + init(shell->get_layer_surface(window->waylandSurface()->object(), output, m_dlayerShellWindow->layer(), m_dlayerShellWindow->scope())); + + set_layer(m_dlayerShellWindow->layer()); + connect(m_dlayerShellWindow, &DLayerShellWindow::layerChanged, this, [this](){ + set_layer(m_dlayerShellWindow->layer()); + }); + + set_anchor(m_dlayerShellWindow->anchors()); + connect(m_dlayerShellWindow, &DLayerShellWindow::anchorsChanged, this,[this](){ + set_anchor(m_dlayerShellWindow->anchors()); + }); + + set_exclusive_zone(m_dlayerShellWindow->exclusionZone()); + connect(m_dlayerShellWindow, &DLayerShellWindow::exclusionZoneChanged, this,[this](){ + set_exclusive_zone(m_dlayerShellWindow->exclusionZone()); + }); + + set_margin(m_dlayerShellWindow->topMargin(), m_dlayerShellWindow->rightMargin(), m_dlayerShellWindow->bottomMargin(), m_dlayerShellWindow->leftMargin()); + connect(m_dlayerShellWindow, &DLayerShellWindow::marginsChanged, this, [this](){ + set_margin(m_dlayerShellWindow->topMargin(), m_dlayerShellWindow->rightMargin(), m_dlayerShellWindow->bottomMargin(), m_dlayerShellWindow->leftMargin()); + }); + + set_keyboard_interactivity(m_dlayerShellWindow->keyboardInteractivity()); + connect(m_dlayerShellWindow, &DLayerShellWindow::keyboardInteractivityChanged, this, [this](){ + set_keyboard_interactivity(m_dlayerShellWindow->keyboardInteractivity()); + }); + + + QSize size = window->surfaceSize(); + const DLayerShellWindow::Anchors anchors = m_dlayerShellWindow->anchors(); + if ((anchors & DLayerShellWindow::AnchorLeft) && (anchors & DLayerShellWindow::AnchorRight)) { + size.setWidth(0); + } + + if ((anchors & DLayerShellWindow::AnchorTop) && (anchors & DLayerShellWindow::AnchorBottom )) { + size.setHeight(0); + } + + if (size.isValid() && !size.isEmpty()) { + set_size(size.width(), size.height()); + } +} + +QWaylandLayeShellSurface::~QWaylandLayeShellSurface() +{ + destroy(); +} + +void QWaylandLayeShellSurface::zwlr_layer_surface_v1_closed() +{ + if (m_dlayerShellWindow->closeOnDismissed()) { + window()->window()->close(); + } +} + +void QWaylandLayeShellSurface::zwlr_layer_surface_v1_configure(uint32_t serial, uint32_t width, uint32_t height) +{ + ack_configure(serial); + m_pendingSize = QSize(width, height); + + if (!m_configured) { + m_configured = true; + window()->resizeFromApplyConfigure(m_pendingSize); + window()->handleExpose(QRect(QPoint(), m_pendingSize)); + } else { + window()->applyConfigureWhenPossible(); + } +} + +void QWaylandLayeShellSurface::applyConfigure() +{ + window()->resizeFromApplyConfigure(m_pendingSize); +} + +void QWaylandLayeShellSurface::setWindowGeometry(const QRect &geometry) +{ + const bool horizontallyConstrained = m_dlayerShellWindow->anchors() & (DLayerShellWindow::AnchorLeft & DLayerShellWindow::AnchorRight); + const bool verticallyConstrained = m_dlayerShellWindow->anchors() & (DLayerShellWindow::AnchorTop & DLayerShellWindow::AnchorBottom); + + QSize size = geometry.size(); + if (horizontallyConstrained) { + size.setWidth(0); + } + if (verticallyConstrained) { + size.setHeight(0); + } + set_size(size.width(), size.height()); +} + +DS_END_NAMESPACE diff --git a/frame/layershell/qwaylandlayershellsurface_p.h b/frame/layershell/qwaylandlayershellsurface_p.h new file mode 100644 index 000000000..28f56db32 --- /dev/null +++ b/frame/layershell/qwaylandlayershellsurface_p.h @@ -0,0 +1,40 @@ +// SPDX-FileCopyrightText: 2023 UnionTech Software Technology Co., Ltd. +// +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include "dsglobal.h" +#include "dlayershellwindow.h" + +#include +#include +#include + +DS_BEGIN_NAMESPACE + +class QWaylandLayeShellSurface : public QtWaylandClient::QWaylandShellSurface, public QtWayland::zwlr_layer_surface_v1 +{ + Q_OBJECT +public: + QWaylandLayeShellSurface(QtWayland::zwlr_layer_shell_v1 *shell, QtWaylandClient::QWaylandWindow *window); + ~QWaylandLayeShellSurface() override; + + bool isExposed() const override + { + return m_configured; + } + + void applyConfigure() override; + void setWindowGeometry(const QRect &geometry) override; + +private: + void zwlr_layer_surface_v1_configure(uint32_t serial, uint32_t width, uint32_t height) override; + void zwlr_layer_surface_v1_closed() override; + + DLayerShellWindow* m_dlayerShellWindow; + QSize m_pendingSize; + bool m_configured = false; +}; + +DS_END_NAMESPACE diff --git a/frame/plugin/qmlplugin.cpp b/frame/plugin/qmlplugin.cpp index eb173576c..214c04796 100644 --- a/frame/plugin/qmlplugin.cpp +++ b/frame/plugin/qmlplugin.cpp @@ -7,6 +7,9 @@ #include "appletitem.h" #include "containmentitem.h" #include "dstypes.h" +#include "layershell/dlayershellwindow.h" + +QML_DECLARE_TYPEINFO(DS_NAMESPACE::DLayerShellWindow, QML_HAS_ATTACHED_PROPERTIES) DS_BEGIN_NAMESPACE @@ -22,6 +25,8 @@ void QmlpluginPlugin::registerTypes(const char *uri) qmlRegisterUncreatableType(uri, 1, 0, "Applet", "Applet Attached"); qmlRegisterType(uri, 1, 0, "ContainmentItem"); qmlRegisterUncreatableType(uri, 1, 0, "Containment", "Containment Attached"); + qmlRegisterType(uri, 1, 0, "DLayerShellWindow"); + qmlRegisterUncreatableType(uri, 1, 0, "DLayerShellWindow","LayerShell Attached"); } void QmlpluginPlugin::initializeEngine(QQmlEngine *engine, const char *uri)