From c2930783eaa516ce397780adc7eb4d1814223a14 Mon Sep 17 00:00:00 2001 From: outfoxxed Date: Sun, 25 Feb 2024 07:13:54 -0800 Subject: [PATCH] feat(wayland): create cross platform window interfaces Internally this also refactors a ton of code around the wayland layershell. Note that attachment failures are still broken and platform interfaces are hardcoded. --- src/core/CMakeLists.txt | 4 +- src/core/floatingwindow.cpp | 62 ++++++ src/core/floatingwindow.hpp | 56 ++++++ src/core/panelinterface.cpp | 1 + .../{shellwindow.hpp => panelinterface.hpp} | 55 ++---- src/core/proxywindow.cpp | 10 - src/core/proxywindow.hpp | 87 +-------- src/core/shellwindow.cpp | 1 - src/core/windowinterface.cpp | 1 + src/core/windowinterface.hpp | 124 ++++++++++++ src/wayland/CMakeLists.txt | 2 +- src/wayland/layer_surface.cpp | 2 +- src/wayland/layer_surface.hpp | 2 +- src/wayland/layershell.cpp | 2 +- src/wayland/layershell.hpp | 6 +- src/wayland/shell_integration.hpp | 2 +- src/wayland/waylandlayershell.cpp | 179 ++++++++++++++++++ src/wayland/waylandlayershell.hpp | 131 +++++++++++++ src/wayland/waylandshellwindow.cpp | 174 ----------------- src/wayland/waylandshellwindow.hpp | 92 --------- 20 files changed, 591 insertions(+), 402 deletions(-) create mode 100644 src/core/floatingwindow.cpp create mode 100644 src/core/floatingwindow.hpp create mode 100644 src/core/panelinterface.cpp rename src/core/{shellwindow.hpp => panelinterface.hpp} (81%) delete mode 100644 src/core/shellwindow.cpp create mode 100644 src/core/windowinterface.cpp create mode 100644 src/core/windowinterface.hpp create mode 100644 src/wayland/waylandlayershell.cpp create mode 100644 src/wayland/waylandlayershell.hpp delete mode 100644 src/wayland/waylandshellwindow.cpp delete mode 100644 src/wayland/waylandshellwindow.hpp diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index d19c7963..60d2b92b 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -11,7 +11,9 @@ qt_add_executable(quickshell watcher.cpp region.cpp persistentprops.cpp - shellwindow.cpp + windowinterface.cpp + floatingwindow.cpp + panelinterface.cpp ) qt_add_qml_module(quickshell URI QuickShell) diff --git a/src/core/floatingwindow.cpp b/src/core/floatingwindow.cpp new file mode 100644 index 00000000..4bcd74b0 --- /dev/null +++ b/src/core/floatingwindow.cpp @@ -0,0 +1,62 @@ +#include "floatingwindow.hpp" + +#include +#include +#include +#include + +#include "proxywindow.hpp" +#include "windowinterface.hpp" + +void ProxyFloatingWindow::setWidth(qint32 width) { + if (this->window == nullptr || !this->window->isVisible()) { + this->ProxyWindowBase::setWidth(width); + } +} + +void ProxyFloatingWindow::setHeight(qint32 height) { + if (this->window == nullptr || !this->window->isVisible()) { + this->ProxyWindowBase::setHeight(height); + } +} + +// FloatingWindowInterface + +FloatingWindowInterface::FloatingWindowInterface(QObject* parent) + : WindowInterface(parent) + , window(new ProxyFloatingWindow(this)) { + // clang-format off + QObject::connect(this->window, &ProxyWindowBase::windowConnected, this, &FloatingWindowInterface::windowConnected); + QObject::connect(this->window, &ProxyWindowBase::visibleChanged, this, &FloatingWindowInterface::visibleChanged); + QObject::connect(this->window, &ProxyWindowBase::heightChanged, this, &FloatingWindowInterface::heightChanged); + QObject::connect(this->window, &ProxyWindowBase::widthChanged, this, &FloatingWindowInterface::widthChanged); + QObject::connect(this->window, &ProxyWindowBase::screenChanged, this, &FloatingWindowInterface::screenChanged); + QObject::connect(this->window, &ProxyWindowBase::colorChanged, this, &FloatingWindowInterface::colorChanged); + QObject::connect(this->window, &ProxyWindowBase::maskChanged, this, &FloatingWindowInterface::maskChanged); + // clang-format on +} + +void FloatingWindowInterface::onReload(QObject* oldInstance) { + auto* old = qobject_cast(oldInstance); + this->window->onReload(old != nullptr ? old->window : nullptr); +} + +QQmlListProperty FloatingWindowInterface::data() { return this->window->data(); } +QQuickItem* FloatingWindowInterface::contentItem() const { return this->window->contentItem(); } + +// NOLINTBEGIN +#define proxyPair(type, get, set) \ + type FloatingWindowInterface::get() const { return this->window->get(); } \ + void FloatingWindowInterface::set(type value) { this->window->set(value); } + +proxyPair(bool, isVisible, setVisible); +proxyPair(qint32, width, setWidth); +proxyPair(qint32, height, setHeight); +proxyPair(QuickShellScreenInfo*, screen, setScreen); +proxyPair(QColor, color, setColor); +proxyPair(PendingRegion*, mask, setMask); + +#undef proxyPair +#undef proxySet +#undef proxyGet +// NOLINTEND diff --git a/src/core/floatingwindow.hpp b/src/core/floatingwindow.hpp new file mode 100644 index 00000000..66fcc7ea --- /dev/null +++ b/src/core/floatingwindow.hpp @@ -0,0 +1,56 @@ +#pragma once + +#include +#include + +#include "proxywindow.hpp" + +class ProxyFloatingWindow: public ProxyWindowBase { + Q_OBJECT; + +public: + explicit ProxyFloatingWindow(QObject* parent = nullptr): ProxyWindowBase(parent) {} + + // Setting geometry while the window is visible makes the content item shrinks but not the window + // which is awful so we disable it for floating windows. + void setWidth(qint32 width) override; + void setHeight(qint32 height) override; +}; + +///! Standard floating window. +class FloatingWindowInterface: public WindowInterface { + Q_OBJECT; + QML_NAMED_ELEMENT(FloatingWindow); + +public: + explicit FloatingWindowInterface(QObject* parent = nullptr); + + void onReload(QObject* oldInstance) override; + + [[nodiscard]] QQuickItem* contentItem() const override; + + // NOLINTBEGIN + [[nodiscard]] bool isVisible() const override; + void setVisible(bool visible) override; + + [[nodiscard]] qint32 width() const override; + void setWidth(qint32 width) override; + + [[nodiscard]] qint32 height() const override; + void setHeight(qint32 height) override; + + [[nodiscard]] QuickShellScreenInfo* screen() const override; + void setScreen(QuickShellScreenInfo* screen) override; + + [[nodiscard]] QColor color() const override; + void setColor(QColor color) override; + + [[nodiscard]] PendingRegion* mask() const override; + void setMask(PendingRegion* mask) override; + + [[nodiscard]] QQmlListProperty data() override; + // NOLINTEND + +private: + ProxyFloatingWindow* window; +}; diff --git a/src/core/panelinterface.cpp b/src/core/panelinterface.cpp new file mode 100644 index 00000000..c0157c91 --- /dev/null +++ b/src/core/panelinterface.cpp @@ -0,0 +1 @@ +#include "panelinterface.hpp" // NOLINT diff --git a/src/core/shellwindow.hpp b/src/core/panelinterface.hpp similarity index 81% rename from src/core/shellwindow.hpp rename to src/core/panelinterface.hpp index e041a50b..2a2570b0 100644 --- a/src/core/shellwindow.hpp +++ b/src/core/panelinterface.hpp @@ -1,16 +1,8 @@ #pragma once -#include -#include -#include -#include -#include #include -#include -#include -#include -#include "proxywindow.hpp" +#include "windowinterface.hpp" class Anchors { Q_GADGET; @@ -87,7 +79,7 @@ Q_ENUM_NS(Enum); /// The following snippet creates a white bar attached to the bottom of the screen. /// /// ```qml -/// ShellWindow { +/// PanelWindow { /// anchors { /// left: true /// bottom: true @@ -101,7 +93,7 @@ Q_ENUM_NS(Enum); /// } /// } /// ``` -class ProxyShellWindow: public ProxyWindowBase { +class PanelWindowInterface: public WindowInterface { // clang-format off Q_OBJECT; /// Anchors attach a shell window to the sides of the screen. @@ -111,45 +103,36 @@ class ProxyShellWindow: public ProxyWindowBase { /// > (width or height) will be forced to equal the screen width/height. /// > Margins can be used to create anchored windows that are also disconnected from the monitor sides. Q_PROPERTY(Anchors anchors READ anchors WRITE setAnchors NOTIFY anchorsChanged); - /// The amount of space reserved for the shell layer relative to its anchors. - /// - /// > [!INFO] Some systems will require exactly 3 anchors to be attached for the exclusion zone to take - /// > effect. - Q_PROPERTY(qint32 exclusionZone READ exclusiveZone WRITE setExclusiveZone NOTIFY exclusionZoneChanged); - /// Defaults to `ExclusionMode.Normal`. - Q_PROPERTY(ExclusionMode::Enum exclusionMode READ exclusionMode WRITE setExclusionMode NOTIFY exclusionModeChanged); /// Offsets from the sides of the screen. /// /// > [!INFO] Only applies to edges with anchors Q_PROPERTY(Margins margins READ margins WRITE setMargins NOTIFY marginsChanged); + /// The amount of space reserved for the shell layer relative to its anchors. + /// + /// > [!INFO] Either 1 or 3 anchors are required for the zone to take effect. + Q_PROPERTY(qint32 exclusiveZone READ exclusiveZone WRITE setExclusiveZone NOTIFY exclusiveZoneChanged); + /// Defaults to `ExclusionMode.Auto`. + Q_PROPERTY(ExclusionMode::Enum exclusionMode READ exclusionMode WRITE setExclusionMode NOTIFY exclusionModeChanged); // clang-format on public: - explicit ProxyShellWindow(QObject* parent = nullptr): ProxyWindowBase(parent) {} + explicit PanelWindowInterface(QObject* parent = nullptr): WindowInterface(parent) {} - QQmlListProperty data(); - - virtual void setAnchors(Anchors anchors) = 0; [[nodiscard]] virtual Anchors anchors() const = 0; + virtual void setAnchors(Anchors anchors) = 0; - virtual void setExclusiveZone(qint32 zone) = 0; - [[nodiscard]] virtual qint32 exclusiveZone() const = 0; - - virtual void setExclusionMode(ExclusionMode::Enum exclusionMode) = 0; - [[nodiscard]] virtual ExclusionMode::Enum exclusionMode() const = 0; - - virtual void setMargins(Margins margins) = 0; [[nodiscard]] virtual Margins margins() const = 0; + virtual void setMargins(Margins margins) = 0; + + [[nodiscard]] virtual qint32 exclusiveZone() const = 0; + virtual void setExclusiveZone(qint32 exclusiveZone) = 0; + + [[nodiscard]] virtual ExclusionMode::Enum exclusionMode() const = 0; + virtual void setExclusionMode(ExclusionMode::Enum exclusionMode) = 0; signals: void anchorsChanged(); void marginsChanged(); - void exclusionZoneChanged(); + void exclusiveZoneChanged(); void exclusionModeChanged(); - -protected: - ExclusionMode::Enum mExclusionMode = ExclusionMode::Normal; - qint32 mExclusionZone = 0; - Anchors mAnchors; - Margins mMargins; }; diff --git a/src/core/proxywindow.cpp b/src/core/proxywindow.cpp index c6d52067..af360fa8 100644 --- a/src/core/proxywindow.cpp +++ b/src/core/proxywindow.cpp @@ -203,14 +203,4 @@ QQmlListProperty ProxyWindowBase::data() { } void ProxyWindowBase::onWidthChanged() { this->mContentItem->setWidth(this->width()); } - void ProxyWindowBase::onHeightChanged() { this->mContentItem->setHeight(this->height()); } - -void ProxyFloatingWindow::setWidth(qint32 width) { - if (this->window == nullptr || !this->window->isVisible()) this->ProxyWindowBase::setWidth(width); -} - -void ProxyFloatingWindow::setHeight(qint32 height) { - if (this->window == nullptr || !this->window->isVisible()) - this->ProxyWindowBase::setHeight(height); -} diff --git a/src/core/proxywindow.hpp b/src/core/proxywindow.hpp index 1bbe3a06..4abd675e 100644 --- a/src/core/proxywindow.hpp +++ b/src/core/proxywindow.hpp @@ -15,12 +15,12 @@ #include "qmlscreen.hpp" #include "region.hpp" #include "reload.hpp" +#include "windowinterface.hpp" // Proxy to an actual window exposing a limited property set with the ability to // transfer it to a new window. ///! Base class for reloadable windows -/// Base class for reloadable windows. See [ShellWindow] and [FloatingWindow] /// /// [ShellWindow]: ../shellwindow /// [FloatingWindow]: ../floatingwindow @@ -35,76 +35,14 @@ class ProxyWindowBase: public Reloadable { /// > Use **only** if you know what you are doing. Q_PROPERTY(QQuickWindow* _backingWindow READ backingWindow); Q_PROPERTY(QQuickItem* contentItem READ contentItem); - /// If the window is shown or hidden. Defaults to true. Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged); Q_PROPERTY(qint32 width READ width WRITE setWidth NOTIFY widthChanged); Q_PROPERTY(qint32 height READ height WRITE setHeight NOTIFY heightChanged); - /// The screen that the window currently occupies. - /// - /// > [!INFO] This cannot be changed while the window is visible. Q_PROPERTY(QuickShellScreenInfo* screen READ screen WRITE setScreen NOTIFY screenChanged); - /// The background color of the window. Defaults to white. - /// - /// > [!WARNING] This seems to behave weirdly when using transparent colors on some systems. - /// > Using a colored content item over a transparent window is the recommended way to work around this: - /// > ```qml - /// > ProxyWindow { - /// > Rectangle { - /// > anchors.fill: parent - /// > color: "#20ffffff" - /// > - /// > // your content here - /// > } - /// > } - /// > ``` Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged); - /// The clickthrough mask. Defaults to null. - /// - /// If non null then the clickable areas of the window will be determined by the provided region. - /// - /// ```qml - /// ShellWindow { - /// // The mask region is set to `rect`, meaning only `rect` is clickable. - /// // All other clicks pass through the window to ones behind it. - /// mask: Region { item: rect } - /// - /// Rectangle { - /// id: rect - /// - /// anchors.centerIn: parent - /// width: 100 - /// height: 100 - /// } - /// } - /// ``` - /// - /// If the provided region's intersection mode is `Combine` (the default), - /// then the region will be used as is. Otherwise it will be applied on top of the window region. - /// - /// For example, setting the intersection mode to `Xor` will invert the mask and make everything in - /// the mask region not clickable and pass through clicks inside it through the window. - /// - /// ```qml - /// ShellWindow { - /// // The mask region is set to `rect`, but the intersection mode is set to `Xor`. - /// // This inverts the mask causing all clicks inside `rect` to be passed to the window - /// // behind this one. - /// mask: Region { item: rect; intersection: Intersection.Xor } - /// - /// Rectangle { - /// id: rect - /// - /// anchors.centerIn: parent - /// width: 100 - /// height: 100 - /// } - /// } - /// ``` Q_PROPERTY(PendingRegion* mask READ mask WRITE setMask NOTIFY maskChanged); Q_PROPERTY(QQmlListProperty data READ data); Q_CLASSINFO("DefaultProperty", "data"); - QML_ELEMENT; - QML_UNCREATABLE("use ShellWindow or FloatingWindow"); public: explicit ProxyWindowBase(QObject* parent = nullptr); @@ -134,16 +72,16 @@ public: [[nodiscard]] virtual qint32 height() const; virtual void setHeight(qint32 height); + [[nodiscard]] virtual QuickShellScreenInfo* screen() const; virtual void setScreen(QuickShellScreenInfo* screen); - [[nodiscard]] QuickShellScreenInfo* screen() const; [[nodiscard]] QColor color() const; - void setColor(QColor color); + virtual void setColor(QColor color); [[nodiscard]] PendingRegion* mask() const; - void setMask(PendingRegion* mask); + virtual void setMask(PendingRegion* mask); - QQmlListProperty data(); + [[nodiscard]] QQmlListProperty data(); signals: void windowConnected(); @@ -173,18 +111,3 @@ protected: private: void updateMask(); }; - -// qt attempts to resize the window but fails because wayland -// and only resizes the graphics context which looks terrible. - -///! Standard floating window. -class ProxyFloatingWindow: public ProxyWindowBase { - Q_OBJECT; - QML_NAMED_ELEMENT(FloatingWindow); - -public: - // Setting geometry while the window is visible makes the content item shrink but not the window - // which is awful so we disable it for floating windows. - void setWidth(qint32 width) override; - void setHeight(qint32 height) override; -}; diff --git a/src/core/shellwindow.cpp b/src/core/shellwindow.cpp deleted file mode 100644 index 6b36f7e2..00000000 --- a/src/core/shellwindow.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "shellwindow.hpp" // NOLINT diff --git a/src/core/windowinterface.cpp b/src/core/windowinterface.cpp new file mode 100644 index 00000000..a29bd599 --- /dev/null +++ b/src/core/windowinterface.cpp @@ -0,0 +1 @@ +#include "windowinterface.hpp" // NOLINT diff --git a/src/core/windowinterface.hpp b/src/core/windowinterface.hpp new file mode 100644 index 00000000..d61dea2e --- /dev/null +++ b/src/core/windowinterface.hpp @@ -0,0 +1,124 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "qmlscreen.hpp" +#include "region.hpp" +#include "reload.hpp" + +class WindowInterface: public Reloadable { + Q_OBJECT; + // clang-format off + Q_PROPERTY(QQuickItem* contentItem READ contentItem); + /// If the window is shown or hidden. Defaults to true. + Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged); + Q_PROPERTY(qint32 width READ width WRITE setWidth NOTIFY widthChanged); + Q_PROPERTY(qint32 height READ height WRITE setHeight NOTIFY heightChanged); + /// The screen that the window currently occupies. + /// + /// > [!INFO] This cannot be changed after windowConnected. + Q_PROPERTY(QuickShellScreenInfo* screen READ screen WRITE setScreen NOTIFY screenChanged); + /// The background color of the window. Defaults to white. + /// + /// > [!WARNING] This seems to behave weirdly when using transparent colors on some systems. + /// > Using a colored content item over a transparent window is the recommended way to work around this: + /// > ```qml + /// > ProxyWindow { + /// > Rectangle { + /// > anchors.fill: parent + /// > color: "#20ffffff" + /// > + /// > // your content here + /// > } + /// > } + /// > ``` + Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged); + /// The clickthrough mask. Defaults to null. + /// + /// If non null then the clickable areas of the window will be determined by the provided region. + /// + /// ```qml + /// ShellWindow { + /// // The mask region is set to `rect`, meaning only `rect` is clickable. + /// // All other clicks pass through the window to ones behind it. + /// mask: Region { item: rect } + /// + /// Rectangle { + /// id: rect + /// + /// anchors.centerIn: parent + /// width: 100 + /// height: 100 + /// } + /// } + /// ``` + /// + /// If the provided region's intersection mode is `Combine` (the default), + /// then the region will be used as is. Otherwise it will be applied on top of the window region. + /// + /// For example, setting the intersection mode to `Xor` will invert the mask and make everything in + /// the mask region not clickable and pass through clicks inside it through the window. + /// + /// ```qml + /// ShellWindow { + /// // The mask region is set to `rect`, but the intersection mode is set to `Xor`. + /// // This inverts the mask causing all clicks inside `rect` to be passed to the window + /// // behind this one. + /// mask: Region { item: rect; intersection: Intersection.Xor } + /// + /// Rectangle { + /// id: rect + /// + /// anchors.centerIn: parent + /// width: 100 + /// height: 100 + /// } + /// } + /// ``` + Q_PROPERTY(PendingRegion* mask READ mask WRITE setMask NOTIFY maskChanged); + Q_PROPERTY(QQmlListProperty data READ data); + // clang-format on + Q_CLASSINFO("DefaultProperty", "data"); + QML_NAMED_ELEMENT(QSWindow); + QML_UNCREATABLE("uncreatable base class"); + +public: + explicit WindowInterface(QObject* parent = nullptr): Reloadable(parent) {} + + [[nodiscard]] virtual QQuickItem* contentItem() const = 0; + + [[nodiscard]] virtual bool isVisible() const = 0; + virtual void setVisible(bool visible) = 0; + + [[nodiscard]] virtual qint32 width() const = 0; + virtual void setWidth(qint32 width) = 0; + + [[nodiscard]] virtual qint32 height() const = 0; + virtual void setHeight(qint32 height) = 0; + + [[nodiscard]] virtual QuickShellScreenInfo* screen() const = 0; + virtual void setScreen(QuickShellScreenInfo* screen) = 0; + + [[nodiscard]] virtual QColor color() const = 0; + virtual void setColor(QColor color) = 0; + + [[nodiscard]] virtual PendingRegion* mask() const = 0; + virtual void setMask(PendingRegion* mask) = 0; + + [[nodiscard]] virtual QQmlListProperty data() = 0; + +signals: + void windowConnected(); + void visibleChanged(); + void widthChanged(); + void heightChanged(); + void screenChanged(); + void colorChanged(); + void maskChanged(); +}; diff --git a/src/wayland/CMakeLists.txt b/src/wayland/CMakeLists.txt index 477f1359..8d95ac71 100644 --- a/src/wayland/CMakeLists.txt +++ b/src/wayland/CMakeLists.txt @@ -2,7 +2,7 @@ qt_add_library(quickshell-wayland STATIC shell_integration.cpp layer_surface.cpp layershell.cpp - waylandshellwindow.cpp + waylandlayershell.cpp ) qt_add_qml_module(quickshell-wayland URI QuickShell.Wayland) diff --git a/src/wayland/layer_surface.cpp b/src/wayland/layer_surface.cpp index 116fc81e..777a38f6 100644 --- a/src/wayland/layer_surface.cpp +++ b/src/wayland/layer_surface.cpp @@ -12,7 +12,7 @@ #include #include -#include "../core/shellwindow.hpp" +#include "../core/panelinterface.hpp" #include "layershell.hpp" #include "shell_integration.hpp" diff --git a/src/wayland/layer_surface.hpp b/src/wayland/layer_surface.hpp index 3f50513a..2518e74b 100644 --- a/src/wayland/layer_surface.hpp +++ b/src/wayland/layer_surface.hpp @@ -10,7 +10,7 @@ #include "layershell.hpp" #include "shell_integration.hpp" -class Q_WAYLANDCLIENT_EXPORT QSWaylandLayerSurface +class QSWaylandLayerSurface : public QtWaylandClient::QWaylandShellSurface , public QtWayland::zwlr_layer_surface_v1 { public: diff --git a/src/wayland/layershell.cpp b/src/wayland/layershell.cpp index 8a12f27c..1fa9711a 100644 --- a/src/wayland/layershell.cpp +++ b/src/wayland/layershell.cpp @@ -9,7 +9,7 @@ #include #include -#include "../core/shellwindow.hpp" +#include "../core/panelinterface.hpp" #include "layer_surface.hpp" #include "shell_integration.hpp" diff --git a/src/wayland/layershell.hpp b/src/wayland/layershell.hpp index 56e5dc43..fe88f74a 100644 --- a/src/wayland/layershell.hpp +++ b/src/wayland/layershell.hpp @@ -6,7 +6,7 @@ #include #include -#include "../core/shellwindow.hpp" +#include "../core/panelinterface.hpp" namespace Layer { // NOLINT Q_NAMESPACE; @@ -14,8 +14,12 @@ QML_ELEMENT; enum Enum { Background = 0, + /// Above background, usually below windows Bottom = 1, + /// Commonly used for panels, app launchers, and docks. + /// Usually renders over normal windows and below fullscreen windows. Top = 2, + /// Usually renders over fullscreen windows Overlay = 3, }; Q_ENUM_NS(Enum); diff --git a/src/wayland/shell_integration.hpp b/src/wayland/shell_integration.hpp index 1630ec0d..1004e430 100644 --- a/src/wayland/shell_integration.hpp +++ b/src/wayland/shell_integration.hpp @@ -5,7 +5,7 @@ #include #include -class Q_WAYLANDCLIENT_EXPORT QSWaylandLayerShellIntegration +class QSWaylandLayerShellIntegration : public QtWaylandClient::QWaylandShellIntegrationTemplate , public QtWayland::zwlr_layer_shell_v1 { public: diff --git a/src/wayland/waylandlayershell.cpp b/src/wayland/waylandlayershell.cpp new file mode 100644 index 00000000..7fa52066 --- /dev/null +++ b/src/wayland/waylandlayershell.cpp @@ -0,0 +1,179 @@ +#include "waylandlayershell.hpp" +#include + +#include +#include +#include +#include +#include +#include + +#include "../core/panelinterface.hpp" +#include "../core/proxywindow.hpp" +#include "../core/qmlscreen.hpp" +#include "layershell.hpp" + +WaylandLayershell::WaylandLayershell(QObject* parent) + : ProxyWindowBase(parent) + , ext(new LayershellWindowExtension(this)) {} + +void WaylandLayershell::setupWindow() { + this->ProxyWindowBase::setupWindow(); + + // clang-format off + QObject::connect(this->ext, &LayershellWindowExtension::layerChanged, this, &WaylandLayershell::layerChanged); + QObject::connect(this->ext, &LayershellWindowExtension::keyboardFocusChanged, this, &WaylandLayershell::keyboardFocusChanged); + QObject::connect(this->ext, &LayershellWindowExtension::anchorsChanged, this, &WaylandLayershell::anchorsChanged); + QObject::connect(this->ext, &LayershellWindowExtension::exclusiveZoneChanged, this, &WaylandLayershell::exclusiveZoneChanged); + QObject::connect(this->ext, &LayershellWindowExtension::marginsChanged, this, &WaylandLayershell::marginsChanged); + + QObject::connect(this, &ProxyWindowBase::widthChanged, this, &WaylandLayershell::updateAutoExclusion); + QObject::connect(this, &ProxyWindowBase::heightChanged, this, &WaylandLayershell::updateAutoExclusion); + QObject::connect(this, &WaylandLayershell::anchorsChanged, this, &WaylandLayershell::updateAutoExclusion); + QObject::connect(this, &WaylandLayershell::marginsChanged, this, &WaylandLayershell::updateAutoExclusion); + // clang-format on + + if (!this->ext->attach(this->window)) { + // todo: discard window + } +} + +void WaylandLayershell::setWidth(qint32 width) { + this->mWidth = width; + + // only update the actual size if not blocked by anchors + if (!this->ext->anchors().horizontalConstraint()) { + this->ProxyWindowBase::setWidth(width); + } +} + +void WaylandLayershell::setHeight(qint32 height) { + this->mHeight = height; + + // only update the actual size if not blocked by anchors + if (!this->ext->anchors().verticalConstraint()) { + this->ProxyWindowBase::setHeight(height); + } +} + +void WaylandLayershell::setScreen(QuickShellScreenInfo* screen) { + this->ProxyWindowBase::setScreen(screen); + this->ext->setUseWindowScreen(screen != nullptr); +} + +// NOLINTBEGIN +#define extPair(type, get, set) \ + type WaylandLayershell::get() const { return this->ext->get(); } \ + void WaylandLayershell::set(type value) { this->ext->set(value); } + +extPair(Layer::Enum, layer, setLayer); +extPair(KeyboardFocus::Enum, keyboardFocus, setKeyboardFocus); +extPair(Margins, margins, setMargins); +// NOLINTEND + +Anchors WaylandLayershell::anchors() const { return this->ext->anchors(); } + +void WaylandLayershell::setAnchors(Anchors anchors) { + this->ext->setAnchors(anchors); + + // explicitly set width values are tracked so the entire screen isn't covered if an anchor is removed. + if (!anchors.horizontalConstraint()) this->ProxyWindowBase::setWidth(this->mWidth); + if (!anchors.verticalConstraint()) this->ProxyWindowBase::setHeight(this->mHeight); +} + +QString WaylandLayershell::ns() const { return this->ext->ns(); } + +void WaylandLayershell::setNamespace(QString ns) { + this->ext->setNamespace(std::move(ns)); + emit this->namespaceChanged(); +} + +qint32 WaylandLayershell::exclusiveZone() const { return this->ext->exclusiveZone(); } + +void WaylandLayershell::setExclusiveZone(qint32 exclusiveZone) { + qDebug() << "set exclusion" << exclusiveZone; + this->mExclusiveZone = exclusiveZone; + + if (this->mExclusionMode == ExclusionMode::Normal) { + this->ext->setExclusiveZone(exclusiveZone); + } +} + +ExclusionMode::Enum WaylandLayershell::exclusionMode() const { return this->mExclusionMode; } + +void WaylandLayershell::setExclusionMode(ExclusionMode::Enum exclusionMode) { + this->mExclusionMode = exclusionMode; + if (exclusionMode == ExclusionMode::Normal) { + this->ext->setExclusiveZone(this->mExclusiveZone); + } else if (exclusionMode == ExclusionMode::Ignore) { + this->ext->setExclusiveZone(-1); + } else { + this->setAutoExclusion(); + } +} + +void WaylandLayershell::setAutoExclusion() { + const auto anchors = this->anchors(); + auto zone = 0; + + if (anchors.horizontalConstraint()) zone = this->height(); + else if (anchors.verticalConstraint()) zone = this->width(); + + this->ext->setExclusiveZone(zone); +} + +void WaylandLayershell::updateAutoExclusion() { + if (this->mExclusionMode == ExclusionMode::Auto) { + this->setAutoExclusion(); + } +} + +// WaylandPanelInterface + +WaylandPanelInterface::WaylandPanelInterface(QObject* parent) + : PanelWindowInterface(parent) + , layer(new WaylandLayershell(this)) { + + // clang-format off + QObject::connect(this->layer, &ProxyWindowBase::windowConnected, this, &WaylandPanelInterface::windowConnected); + QObject::connect(this->layer, &ProxyWindowBase::visibleChanged, this, &WaylandPanelInterface::visibleChanged); + QObject::connect(this->layer, &ProxyWindowBase::heightChanged, this, &WaylandPanelInterface::heightChanged); + QObject::connect(this->layer, &ProxyWindowBase::widthChanged, this, &WaylandPanelInterface::widthChanged); + QObject::connect(this->layer, &ProxyWindowBase::screenChanged, this, &WaylandPanelInterface::screenChanged); + QObject::connect(this->layer, &ProxyWindowBase::colorChanged, this, &WaylandPanelInterface::colorChanged); + QObject::connect(this->layer, &ProxyWindowBase::maskChanged, this, &WaylandPanelInterface::maskChanged); + + // panel specific + QObject::connect(this->layer, &WaylandLayershell::anchorsChanged, this, &WaylandPanelInterface::anchorsChanged); + QObject::connect(this->layer, &WaylandLayershell::marginsChanged, this, &WaylandPanelInterface::marginsChanged); + QObject::connect(this->layer, &WaylandLayershell::exclusiveZoneChanged, this, &WaylandPanelInterface::exclusiveZoneChanged); + QObject::connect(this->layer, &WaylandLayershell::exclusionModeChanged, this, &WaylandPanelInterface::exclusionModeChanged); + // clang-format on +} + +void WaylandPanelInterface::onReload(QObject* oldInstance) { + auto* old = qobject_cast(oldInstance); + this->layer->onReload(old != nullptr ? old->layer : nullptr); +} + +QQmlListProperty WaylandPanelInterface::data() { return this->layer->data(); } +QQuickItem* WaylandPanelInterface::contentItem() const { return this->layer->contentItem(); } + +// NOLINTBEGIN +#define proxyPair(type, get, set) \ + type WaylandPanelInterface::get() const { return this->layer->get(); } \ + void WaylandPanelInterface::set(type value) { this->layer->set(value); } + +proxyPair(bool, isVisible, setVisible); +proxyPair(qint32, width, setWidth); +proxyPair(qint32, height, setHeight); +proxyPair(QuickShellScreenInfo*, screen, setScreen); +proxyPair(QColor, color, setColor); +proxyPair(PendingRegion*, mask, setMask); + +// panel specific +proxyPair(Anchors, anchors, setAnchors); +proxyPair(Margins, margins, setMargins); +proxyPair(qint32, exclusiveZone, setExclusiveZone); +proxyPair(ExclusionMode::Enum, exclusionMode, setExclusionMode); +// NOLINTEND diff --git a/src/wayland/waylandlayershell.hpp b/src/wayland/waylandlayershell.hpp new file mode 100644 index 00000000..9c5ad144 --- /dev/null +++ b/src/wayland/waylandlayershell.hpp @@ -0,0 +1,131 @@ +#pragma once + +#include +#include +#include +#include + +#include "../core/proxywindow.hpp" +#include "layershell.hpp" + +class WaylandLayershell: public ProxyWindowBase { + // clang-format off + Q_OBJECT; + /// The shell layer the window sits in. Defaults to `Layer.Top`. + Q_PROPERTY(Layer::Enum layer READ layer WRITE setLayer NOTIFY layerChanged); + /// Similar to the class property of windows. Can be used to identify the window to external tools. + /// + /// Cannot be set after windowConnected. + Q_PROPERTY(QString namespace READ ns WRITE setNamespace); + /// The degree of keyboard focus taken. Defaults to `KeyboardFocus.None`. + Q_PROPERTY(KeyboardFocus::Enum keyboardFocus READ keyboardFocus WRITE setKeyboardFocus NOTIFY keyboardFocusChanged); + + Q_PROPERTY(Anchors anchors READ anchors WRITE setAnchors NOTIFY anchorsChanged); + Q_PROPERTY(qint32 exclusiveZone READ exclusiveZone WRITE setExclusiveZone NOTIFY exclusiveZoneChanged); + Q_PROPERTY(ExclusionMode::Enum exclusionMode READ exclusionMode WRITE setExclusionMode NOTIFY exclusionModeChanged); + Q_PROPERTY(Margins margins READ margins WRITE setMargins NOTIFY marginsChanged); + QML_ELEMENT; + // clang-format on + +public: + explicit WaylandLayershell(QObject* parent = nullptr); + + void setupWindow() override; + + void setWidth(qint32 width) override; + void setHeight(qint32 height) override; + + void setScreen(QuickShellScreenInfo* screen) override; + + [[nodiscard]] Layer::Enum layer() const; + void setLayer(Layer::Enum layer); // NOLINT + + [[nodiscard]] QString ns() const; + void setNamespace(QString ns); + + [[nodiscard]] KeyboardFocus::Enum keyboardFocus() const; + void setKeyboardFocus(KeyboardFocus::Enum focus); // NOLINT + + [[nodiscard]] Anchors anchors() const; + void setAnchors(Anchors anchors); + + [[nodiscard]] qint32 exclusiveZone() const; + void setExclusiveZone(qint32 exclusiveZone); + + [[nodiscard]] ExclusionMode::Enum exclusionMode() const; + void setExclusionMode(ExclusionMode::Enum exclusionMode); + + [[nodiscard]] Margins margins() const; + void setMargins(Margins margins); // NOLINT + +signals: + void layerChanged(); + void namespaceChanged(); + void keyboardFocusChanged(); + void anchorsChanged(); + void exclusiveZoneChanged(); + void exclusionModeChanged(); + void marginsChanged(); + +private slots: + void updateAutoExclusion(); + +private: + void setAutoExclusion(); + + LayershellWindowExtension* ext; + + ExclusionMode::Enum mExclusionMode = ExclusionMode::Auto; + qint32 mExclusiveZone = 0; +}; + +class WaylandPanelInterface: public PanelWindowInterface { + Q_OBJECT; + QML_NAMED_ELEMENT(PanelWindow); // temp + +public: + explicit WaylandPanelInterface(QObject* parent = nullptr); + + void onReload(QObject* oldInstance) override; + + [[nodiscard]] QQuickItem* contentItem() const override; + + // NOLINTBEGIN + [[nodiscard]] bool isVisible() const override; + void setVisible(bool visible) override; + + [[nodiscard]] qint32 width() const override; + void setWidth(qint32 width) override; + + [[nodiscard]] qint32 height() const override; + void setHeight(qint32 height) override; + + [[nodiscard]] QuickShellScreenInfo* screen() const override; + void setScreen(QuickShellScreenInfo* screen) override; + + [[nodiscard]] QColor color() const override; + void setColor(QColor color) override; + + [[nodiscard]] PendingRegion* mask() const override; + void setMask(PendingRegion* mask) override; + + [[nodiscard]] QQmlListProperty data() override; + + // panel specific + + [[nodiscard]] Anchors anchors() const override; + void setAnchors(Anchors anchors) override; + + [[nodiscard]] Margins margins() const override; + void setMargins(Margins margins) override; + + [[nodiscard]] qint32 exclusiveZone() const override; + void setExclusiveZone(qint32 exclusiveZone) override; + + [[nodiscard]] ExclusionMode::Enum exclusionMode() const override; + void setExclusionMode(ExclusionMode::Enum exclusionMode) override; + // NOLINTEND + +private: + WaylandLayershell* layer; +}; diff --git a/src/wayland/waylandshellwindow.cpp b/src/wayland/waylandshellwindow.cpp deleted file mode 100644 index 93071539..00000000 --- a/src/wayland/waylandshellwindow.cpp +++ /dev/null @@ -1,174 +0,0 @@ -#include "waylandshellwindow.hpp" - -#include -#include -#include -#include -#include - -#include "../core/proxywindow.hpp" -#include "../core/shellwindow.hpp" -#include "layershell.hpp" - -WaylandShellWindow::WaylandShellWindow(QObject* parent) - : ProxyShellWindow(parent) - , mWayland(new WaylandShellWindowExtensions(this)) - , windowExtension(new LayershellWindowExtension(this)) {} - -void WaylandShellWindow::setupWindow() { - this->ProxyShellWindow::setupWindow(); - - // clang-format off - QObject::connect(this->windowExtension, &LayershellWindowExtension::anchorsChanged, this, &ProxyShellWindow::anchorsChanged); - QObject::connect(this->windowExtension, &LayershellWindowExtension::marginsChanged, this, &ProxyShellWindow::marginsChanged); - QObject::connect(this->windowExtension, &LayershellWindowExtension::exclusiveZoneChanged, this, &ProxyShellWindow::exclusionZoneChanged); - - QObject::connect( - this->windowExtension, &LayershellWindowExtension::layerChanged, - this->mWayland, &WaylandShellWindowExtensions::layerChanged - ); - QObject::connect( - this->windowExtension, &LayershellWindowExtension::keyboardFocusChanged, - this->mWayland, &WaylandShellWindowExtensions::keyboardFocusChanged - ); - - QObject::connect(this->window, &QWindow::widthChanged, this, &WaylandShellWindow::updateExclusionZone); - QObject::connect(this->window, &QWindow::heightChanged, this, &WaylandShellWindow::updateExclusionZone); - QObject::connect(this, &ProxyShellWindow::anchorsChanged, this, &WaylandShellWindow::updateExclusionZone); - QObject::connect(this, &ProxyShellWindow::marginsChanged, this, &WaylandShellWindow::updateExclusionZone); - // clang-format on - - this->setMargins(this->mMargins); - this->setExclusionMode(this->mExclusionMode); // also sets exclusion zone - - if (!this->windowExtension->attach(this->window)) { - // todo: discard window - } - - this->connected = true; -} - -QQuickWindow* WaylandShellWindow::disownWindow() { return this->ProxyWindowBase::disownWindow(); } - -void WaylandShellWindow::setWidth(qint32 width) { - this->mWidth = width; - - // only update the actual size if not blocked by anchors - if (!this->windowExtension->anchors().horizontalConstraint()) { - this->ProxyShellWindow::setWidth(width); - } -} - -void WaylandShellWindow::onWidthChanged() { - this->ProxyShellWindow::onWidthChanged(); - - // change the remembered width only when not horizontally constrained. - if (this->window != nullptr && !this->windowExtension->anchors().horizontalConstraint()) { - this->mWidth = this->window->width(); - } -} - -void WaylandShellWindow::setHeight(qint32 height) { - this->mHeight = height; - - // only update the actual size if not blocked by anchors - if (!this->windowExtension->anchors().verticalConstraint()) { - this->ProxyShellWindow::setHeight(height); - } -} - -void WaylandShellWindow::onHeightChanged() { - this->ProxyShellWindow::onHeightChanged(); - - // change the remembered height only when not vertically constrained. - if (this->window != nullptr && !this->windowExtension->anchors().verticalConstraint()) { - this->mHeight = this->window->height(); - } -} - -void WaylandShellWindow::setAnchors(Anchors anchors) { - this->windowExtension->setAnchors(anchors); - this->setHeight(this->mHeight); - this->setWidth(this->mWidth); -} - -Anchors WaylandShellWindow::anchors() const { return this->windowExtension->anchors(); } - -void WaylandShellWindow::setExclusiveZone(qint32 zone) { - this->windowExtension->setExclusiveZone(zone); -} - -qint32 WaylandShellWindow::exclusiveZone() const { return this->windowExtension->exclusiveZone(); } - -ExclusionMode::Enum WaylandShellWindow::exclusionMode() const { return this->mExclusionMode; } - -void WaylandShellWindow::setExclusionMode(ExclusionMode::Enum exclusionMode) { - if (this->connected && exclusionMode == this->mExclusionMode) return; - this->mExclusionMode = exclusionMode; - - if (this->window != nullptr) { - if (exclusionMode == ExclusionMode::Normal) { - this->windowExtension->setExclusiveZone(this->mExclusionZone); - } else if (exclusionMode == ExclusionMode::Ignore) { - this->windowExtension->setExclusiveZone(-1); - } else { - this->updateExclusionZone(); - } - } -} - -void WaylandShellWindow::setMargins(Margins margins) { this->windowExtension->setMargins(margins); } - -Margins WaylandShellWindow::margins() const { return this->windowExtension->margins(); } - -void WaylandShellWindowExtensions::setLayer(Layer::Enum layer) { - this->window->windowExtension->setLayer(layer); -} - -Layer::Enum WaylandShellWindowExtensions::layer() const { - return this->window->windowExtension->layer(); -} - -void WaylandShellWindowExtensions::setNamespace(const QString& ns) { - if (this->window->windowExtension->isConfigured()) { - auto* context = QQmlEngine::contextForObject(this); - if (context != nullptr) { - context->engine()->throwError(QString("Cannot change shell window namespace post-configure.") - ); - } - - return; - } - - this->window->windowExtension->setNamespace(ns); -} - -QString WaylandShellWindowExtensions::ns() const { return this->window->windowExtension->ns(); } - -void WaylandShellWindowExtensions::setKeyboardFocus(KeyboardFocus::Enum focus) { - this->window->windowExtension->setKeyboardFocus(focus); -} - -KeyboardFocus::Enum WaylandShellWindowExtensions::keyboardFocus() const { - return this->window->windowExtension->keyboardFocus(); -} - -void WaylandShellWindow::updateExclusionZone() { - if (this->window != nullptr && this->exclusionMode() == ExclusionMode::Auto) { - auto anchors = this->anchors(); - - auto zone = -1; - - if (anchors.mTop && anchors.mBottom) { - if (anchors.mLeft) zone = this->width() + this->margins().mLeft; - else if (anchors.mRight) zone = this->width() + this->margins().mRight; - } else if (anchors.mLeft && anchors.mRight) { - if (anchors.mTop) zone = this->height() + this->margins().mTop; - else if (anchors.mBottom) zone = this->height() + this->margins().mBottom; - } - - if (zone != -1) { - this->windowExtension->setExclusiveZone(zone); - } - } -} diff --git a/src/wayland/waylandshellwindow.hpp b/src/wayland/waylandshellwindow.hpp deleted file mode 100644 index 878b4caf..00000000 --- a/src/wayland/waylandshellwindow.hpp +++ /dev/null @@ -1,92 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "../core/shellwindow.hpp" -#include "layershell.hpp" - -class WaylandShellWindowExtensions; - -class WaylandShellWindow: public ProxyShellWindow { - Q_OBJECT; - Q_PROPERTY(WaylandShellWindowExtensions* wayland MEMBER mWayland CONSTANT); - QML_NAMED_ELEMENT(ShellWindow); - -public: - explicit WaylandShellWindow(QObject* parent = nullptr); - - WaylandShellWindowExtensions* wayland(); - - void setupWindow() override; - QQuickWindow* disownWindow() override; - - void setWidth(qint32 width) override; - void setHeight(qint32 height) override; - - void setAnchors(Anchors anchors) override; - [[nodiscard]] Anchors anchors() const override; - - void setExclusiveZone(qint32 zone) override; - [[nodiscard]] qint32 exclusiveZone() const override; - - void setExclusionMode(ExclusionMode::Enum exclusionMode) override; - [[nodiscard]] ExclusionMode::Enum exclusionMode() const override; - - void setMargins(Margins margins) override; - [[nodiscard]] Margins margins() const override; - -protected slots: - void updateExclusionZone(); - void onWidthChanged() override; - void onHeightChanged() override; - -private: - WaylandShellWindowExtensions* mWayland = nullptr; - - LayershellWindowExtension* windowExtension; - bool connected = false; - - friend class WaylandShellWindowExtensions; -}; - -class WaylandShellWindowExtensions: public QObject { - Q_OBJECT; - // clang-format off - /// The shell layer the window sits in. Defaults to `Layer.Top`. - Q_PROPERTY(Layer::Enum layer READ layer WRITE setLayer NOTIFY layerChanged); - /// Similar to the class property of windows. Can be used to identify the window to external tools. - Q_PROPERTY(QString namespace READ ns WRITE setNamespace); - /// The degree of keyboard focus taken. Defaults to `KeyboardFocus.None`. - Q_PROPERTY(KeyboardFocus::Enum keyboardFocus READ keyboardFocus WRITE setKeyboardFocus NOTIFY keyboardFocusChanged); - // clang-format on - QML_ELEMENT; - QML_UNCREATABLE("WaylandShellWindowExtensions cannot be created"); - -public: - explicit WaylandShellWindowExtensions(WaylandShellWindow* window) - : QObject(window) - , window(window) {} - - void setLayer(Layer::Enum layer); - [[nodiscard]] Layer::Enum layer() const; - - void setNamespace(const QString& ns); - [[nodiscard]] QString ns() const; - - void setKeyboardFocus(KeyboardFocus::Enum focus); - [[nodiscard]] KeyboardFocus::Enum keyboardFocus() const; - - void setScreenConfiguration(ScreenConfiguration::Enum configuration); - [[nodiscard]] ScreenConfiguration::Enum screenConfiguration() const; - -signals: - void layerChanged(); - void keyboardFocusChanged(); - -private: - WaylandShellWindow* window; - - friend class WaylandShellWindow; -};