From e0cff677a587fd7ea3f7dd1fb9bd736570f3fa70 Mon Sep 17 00:00:00 2001 From: outfoxxed Date: Tue, 13 May 2025 14:43:48 -0700 Subject: [PATCH] wayland/layershell: refactor layer shell surface integration In addition to the much needed cleanup: - The bridge/extension type is now directly tied to the QWindow instead of the WlrLayershell object, and is much smaller. - Layer requests are now comitted via polish instead of for each change individually. --- src/wayland/CMakeLists.txt | 1 - src/wayland/init.cpp | 9 +- src/wayland/module.md | 3 +- src/wayland/wlr_layershell/CMakeLists.txt | 2 +- .../wlr_layershell/shell_integration.cpp | 14 +- .../wlr_layershell/shell_integration.hpp | 14 +- src/wayland/wlr_layershell/surface.cpp | 202 +++++++++++------- src/wayland/wlr_layershell/surface.hpp | 72 +++++-- src/wayland/wlr_layershell/window.cpp | 153 ------------- src/wayland/wlr_layershell/window.hpp | 118 ---------- .../{ => wlr_layershell}/wlr_layershell.cpp | 142 +++++------- .../{ => wlr_layershell}/wlr_layershell.hpp | 129 ++++++++--- src/window/proxywindow.hpp | 2 +- 13 files changed, 366 insertions(+), 495 deletions(-) delete mode 100644 src/wayland/wlr_layershell/window.cpp delete mode 100644 src/wayland/wlr_layershell/window.hpp rename src/wayland/{ => wlr_layershell}/wlr_layershell.cpp (61%) rename src/wayland/{ => wlr_layershell}/wlr_layershell.hpp (53%) diff --git a/src/wayland/CMakeLists.txt b/src/wayland/CMakeLists.txt index 3b3d08ae..26d29c8c 100644 --- a/src/wayland/CMakeLists.txt +++ b/src/wayland/CMakeLists.txt @@ -85,7 +85,6 @@ add_library(quickshell-wayland-init OBJECT init.cpp) set(WAYLAND_MODULES) if (WAYLAND_WLR_LAYERSHELL) - target_sources(quickshell-wayland PRIVATE wlr_layershell.cpp) add_subdirectory(wlr_layershell) target_compile_definitions(quickshell-wayland PRIVATE QS_WAYLAND_WLR_LAYERSHELL) target_compile_definitions(quickshell-wayland-init PRIVATE QS_WAYLAND_WLR_LAYERSHELL) diff --git a/src/wayland/init.cpp b/src/wayland/init.cpp index 3f2a18b4..e56eee3f 100644 --- a/src/wayland/init.cpp +++ b/src/wayland/init.cpp @@ -7,7 +7,7 @@ #include "../core/plugin.hpp" #ifdef QS_WAYLAND_WLR_LAYERSHELL -#include "wlr_layershell.hpp" +#include "wlr_layershell/wlr_layershell.hpp" #endif void installPlatformMenuHook(); // NOLINT(misc-use-internal-linkage) @@ -39,7 +39,12 @@ class WaylandPlugin: public QsEnginePlugin { void registerTypes() override { #ifdef QS_WAYLAND_WLR_LAYERSHELL - qmlRegisterType("Quickshell._WaylandOverlay", 1, 0, "PanelWindow"); + qmlRegisterType( + "Quickshell._WaylandOverlay", + 1, + 0, + "PanelWindow" + ); // If any types are defined inside a module using QML_ELEMENT then all QML_ELEMENT types // will not be registered. This can be worked around with a module import which makes diff --git a/src/wayland/module.md b/src/wayland/module.md index db9bfb5a..b9f8f594 100644 --- a/src/wayland/module.md +++ b/src/wayland/module.md @@ -1,8 +1,7 @@ name = "Quickshell.Wayland" description = "Wayland specific Quickshell types" headers = [ - "wlr_layershell/window.hpp", - "wlr_layershell.hpp", + "wlr_layershell/wlr_layershell.hpp", "session_lock.hpp", "toplevel_management/qml.hpp", "screencopy/view.hpp", diff --git a/src/wayland/wlr_layershell/CMakeLists.txt b/src/wayland/wlr_layershell/CMakeLists.txt index 69b24e92..444f512b 100644 --- a/src/wayland/wlr_layershell/CMakeLists.txt +++ b/src/wayland/wlr_layershell/CMakeLists.txt @@ -1,7 +1,7 @@ qt_add_library(quickshell-wayland-layershell STATIC + wlr_layershell.cpp shell_integration.cpp surface.cpp - window.cpp ) qt_add_qml_module(quickshell-wayland-layershell diff --git a/src/wayland/wlr_layershell/shell_integration.cpp b/src/wayland/wlr_layershell/shell_integration.cpp index 8c7bce70..8abfefa5 100644 --- a/src/wayland/wlr_layershell/shell_integration.cpp +++ b/src/wayland/wlr_layershell/shell_integration.cpp @@ -6,16 +6,20 @@ #include "surface.hpp" -QSWaylandLayerShellIntegration::QSWaylandLayerShellIntegration() - : QtWaylandClient::QWaylandShellIntegrationTemplate(4) {} +namespace qs::wayland::layershell { -QSWaylandLayerShellIntegration::~QSWaylandLayerShellIntegration() { +LayerShellIntegration::LayerShellIntegration() + : QtWaylandClient::QWaylandShellIntegrationTemplate(4) {} + +LayerShellIntegration::~LayerShellIntegration() { if (this->isInitialized()) { this->destroy(); } } QtWaylandClient::QWaylandShellSurface* -QSWaylandLayerShellIntegration::createShellSurface(QtWaylandClient::QWaylandWindow* window) { - return new QSWaylandLayerSurface(this, window); +LayerShellIntegration::createShellSurface(QtWaylandClient::QWaylandWindow* window) { + return new LayerSurface(this, window); } + +} // namespace qs::wayland::layershell diff --git a/src/wayland/wlr_layershell/shell_integration.hpp b/src/wayland/wlr_layershell/shell_integration.hpp index 132b0d7a..e92b7c62 100644 --- a/src/wayland/wlr_layershell/shell_integration.hpp +++ b/src/wayland/wlr_layershell/shell_integration.hpp @@ -5,14 +5,18 @@ #include #include -class QSWaylandLayerShellIntegration - : public QtWaylandClient::QWaylandShellIntegrationTemplate +namespace qs::wayland::layershell { + +class LayerShellIntegration + : public QtWaylandClient::QWaylandShellIntegrationTemplate , public QtWayland::zwlr_layer_shell_v1 { public: - QSWaylandLayerShellIntegration(); - ~QSWaylandLayerShellIntegration() override; - Q_DISABLE_COPY_MOVE(QSWaylandLayerShellIntegration); + LayerShellIntegration(); + ~LayerShellIntegration() override; + Q_DISABLE_COPY_MOVE(LayerShellIntegration); QtWaylandClient::QWaylandShellSurface* createShellSurface(QtWaylandClient::QWaylandWindow* window ) override; }; + +} // namespace qs::wayland::layershell diff --git a/src/wayland/wlr_layershell/surface.cpp b/src/wayland/wlr_layershell/surface.cpp index 6aa6c8be..8349dbfc 100644 --- a/src/wayland/wlr_layershell/surface.cpp +++ b/src/wayland/wlr_layershell/surface.cpp @@ -1,6 +1,7 @@ #include "surface.hpp" #include #include +#include #include #include @@ -13,16 +14,20 @@ #include #include #include +#include #include +#include #include "../../window/panelinterface.hpp" #include "shell_integration.hpp" -#include "window.hpp" +#include "wlr_layershell.hpp" #if QT_VERSION < QT_VERSION_CHECK(6, 7, 0) #include #endif +namespace qs::wayland::layershell { + namespace { [[nodiscard]] QtWayland::zwlr_layer_shell_v1::layer toWaylandLayer(const WlrLayer::Enum& layer @@ -69,21 +74,72 @@ toWaylandKeyboardFocus(const WlrKeyboardFocus::Enum& focus) noexcept { } // namespace -QSWaylandLayerSurface::QSWaylandLayerSurface( - QSWaylandLayerShellIntegration* shell, - QtWaylandClient::QWaylandWindow* window -) +void LayerSurfaceBridge::commitState() { + if (this->surface) this->surface->commit(); +} + +LayerSurfaceBridge* LayerSurfaceBridge::get(QWindow* window) { + auto v = window->property("layershell_bridge"); + + if (v.canConvert()) { + return v.value(); + } + + return nullptr; +} + +LayerSurfaceBridge* LayerSurfaceBridge::init(QWindow* window, LayerSurfaceState state) { + auto* bridge = LayerSurfaceBridge::get(window); + + if (!bridge) { + bridge = new LayerSurfaceBridge(window); + window->setProperty("layershell_bridge", QVariant::fromValue(bridge)); + } else if (!bridge->state.isCompatible(state)) { + return nullptr; + } + + if (!bridge->surface) { + // Qt appears to be resetting the window's screen on creation on some systems. This works around it. + auto* screen = window->screen(); + window->create(); + window->setScreen(screen); + + auto* waylandWindow = dynamic_cast(window->handle()); + if (waylandWindow == nullptr) { + qWarning() << window << "is not a wayland window. Cannot create layershell surface."; + return nullptr; + } + + static LayerShellIntegration* layershellIntegration = nullptr; // NOLINT + if (layershellIntegration == nullptr) { + layershellIntegration = new LayerShellIntegration(); + if (!layershellIntegration->initialize(waylandWindow->display())) { + delete layershellIntegration; + layershellIntegration = nullptr; + qWarning() << "Failed to initialize layershell integration"; + } + } + + waylandWindow->setShellIntegration(layershellIntegration); + } + + bridge->state = std::move(state); + bridge->commitState(); + + return bridge; +} + +LayerSurface::LayerSurface(LayerShellIntegration* shell, QtWaylandClient::QWaylandWindow* window) : QtWaylandClient::QWaylandShellSurface(window) { auto* qwindow = window->window(); - this->ext = LayershellWindowExtension::get(qwindow); - if (this->ext == nullptr) { - qFatal() << "QSWaylandLayerSurface created with null LayershellWindowExtension"; - } + this->bridge = LayerSurfaceBridge::get(qwindow); + if (this->bridge == nullptr) qFatal() << "LayerSurface created with null bridge"; + const auto& s = this->bridge->state; wl_output* output = nullptr; // NOLINT (include) - if (this->ext->useWindowScreen) { + if (!s.compositorPickesScreen) { auto* waylandScreen = dynamic_cast(qwindow->screen()->handle()); @@ -98,36 +154,28 @@ QSWaylandLayerSurface::QSWaylandLayerSurface( this->init(shell->get_layer_surface( window->waylandSurface()->object(), output, - toWaylandLayer(this->ext->mLayer), - this->ext->mNamespace + toWaylandLayer(s.layer), + s.mNamespace )); - this->updateAnchors(); - this->updateLayer(); - this->updateMargins(); - this->updateExclusiveZone(); - this->updateKeyboardFocus(); - - // new updates will be sent from the extension - this->ext->surface = this; - - auto size = constrainedSize(this->ext->mAnchors, window->surfaceSize()); + auto size = constrainedSize(s.anchors, QHighDpi::toNativePixels(s.implicitSize, qwindow)); this->set_size(size.width(), size.height()); + this->set_anchor(toWaylandAnchors(s.anchors)); + this->set_margin( + QHighDpi::toNativePixels(s.margins.mTop, qwindow), + QHighDpi::toNativePixels(s.margins.mRight, qwindow), + QHighDpi::toNativePixels(s.margins.mBottom, qwindow), + QHighDpi::toNativePixels(s.margins.mLeft, qwindow) + ); + this->set_exclusive_zone(QHighDpi::toNativePixels(s.exclusiveZone, qwindow)); + this->set_keyboard_interactivity(toWaylandKeyboardFocus(s.keyboardFocus)); + + this->bridge->surface = this; } -QSWaylandLayerSurface::~QSWaylandLayerSurface() { - if (this->ext != nullptr) { - this->ext->surface = nullptr; - } +LayerSurface::~LayerSurface() { this->destroy(); } - this->destroy(); -} - -void QSWaylandLayerSurface::zwlr_layer_surface_v1_configure( - quint32 serial, - quint32 width, - quint32 height -) { +void LayerSurface::zwlr_layer_surface_v1_configure(quint32 serial, quint32 width, quint32 height) { this->ack_configure(serial); this->size = QSize(static_cast(width), static_cast(height)); @@ -146,51 +194,53 @@ void QSWaylandLayerSurface::zwlr_layer_surface_v1_configure( } } -void QSWaylandLayerSurface::zwlr_layer_surface_v1_closed() { this->window()->window()->close(); } +void LayerSurface::zwlr_layer_surface_v1_closed() { this->window()->window()->close(); } -bool QSWaylandLayerSurface::isExposed() const { return this->configured; } +bool LayerSurface::isExposed() const { return this->configured; } -void QSWaylandLayerSurface::applyConfigure() { - this->window()->resizeFromApplyConfigure(this->size); +void LayerSurface::applyConfigure() { this->window()->resizeFromApplyConfigure(this->size); } + +QWindow* LayerSurface::qwindow() { return this->window()->window(); } + +void LayerSurface::commit() { + const auto& p = this->bridge->state; + auto& c = this->committed; + + if (p.implicitSize != c.implicitSize || p.anchors != c.anchors) { + auto size = + constrainedSize(p.anchors, QHighDpi::toNativePixels(p.implicitSize, this->qwindow())); + this->set_size(size.width(), size.height()); + } + + if (p.anchors != c.anchors) { + this->set_anchor(toWaylandAnchors(p.anchors)); + } + + if (p.margins != c.margins) { + this->set_margin( + QHighDpi::toNativePixels(p.margins.mTop, this->qwindow()), + QHighDpi::toNativePixels(p.margins.mRight, this->qwindow()), + QHighDpi::toNativePixels(p.margins.mBottom, this->qwindow()), + QHighDpi::toNativePixels(p.margins.mLeft, this->qwindow()) + ); + } + + if (p.layer != c.layer) { + this->set_layer(p.layer); + } + + if (p.exclusiveZone != c.exclusiveZone) { + this->set_exclusive_zone(QHighDpi::toNativePixels(p.exclusiveZone, this->qwindow())); + } + + if (p.keyboardFocus != c.keyboardFocus) { + this->set_keyboard_interactivity(toWaylandKeyboardFocus(p.keyboardFocus)); + } + + c = p; } -void QSWaylandLayerSurface::setWindowGeometry(const QRect& geometry) { - if (this->ext == nullptr) return; - auto size = constrainedSize(this->ext->mAnchors, geometry.size()); - this->set_size(size.width(), size.height()); -} - -QWindow* QSWaylandLayerSurface::qwindow() { return this->window()->window(); } - -void QSWaylandLayerSurface::updateLayer() { - this->set_layer(toWaylandLayer(this->ext->mLayer)); - this->window()->waylandSurface()->commit(); -} - -void QSWaylandLayerSurface::updateAnchors() { - this->set_anchor(toWaylandAnchors(this->ext->mAnchors)); - this->setWindowGeometry(this->window()->windowContentGeometry()); - this->window()->waylandSurface()->commit(); -} - -void QSWaylandLayerSurface::updateMargins() { - auto& margins = this->ext->mMargins; - this->set_margin(margins.mTop, margins.mRight, margins.mBottom, margins.mLeft); - this->window()->waylandSurface()->commit(); -} - -void QSWaylandLayerSurface::updateExclusiveZone() { - auto nativeZone = QHighDpi::toNativePixels(this->ext->mExclusiveZone, this->window()->window()); - this->set_exclusive_zone(nativeZone); - this->window()->waylandSurface()->commit(); -} - -void QSWaylandLayerSurface::updateKeyboardFocus() { - this->set_keyboard_interactivity(toWaylandKeyboardFocus(this->ext->mKeyboardFocus)); - this->window()->waylandSurface()->commit(); -} - -void QSWaylandLayerSurface::attachPopup(QtWaylandClient::QWaylandShellSurface* popup) { +void LayerSurface::attachPopup(QtWaylandClient::QWaylandShellSurface* popup) { std::any role = popup->surfaceRole(); if (auto* popupRole = std::any_cast<::xdg_popup*>(&role)) { // NOLINT @@ -200,3 +250,5 @@ void QSWaylandLayerSurface::attachPopup(QtWaylandClient::QWaylandShellSurface* p << "as the popup is not an xdg_popup."; } } + +} // namespace qs::wayland::layershell diff --git a/src/wayland/wlr_layershell/surface.hpp b/src/wayland/wlr_layershell/surface.hpp index c58e3490..d782d713 100644 --- a/src/wayland/wlr_layershell/surface.hpp +++ b/src/wayland/wlr_layershell/surface.hpp @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include #include @@ -9,40 +11,76 @@ #include #include "shell_integration.hpp" -#include "window.hpp" +#include "wlr_layershell.hpp" -class QSWaylandLayerSurface +namespace qs::wayland::layershell { + +struct LayerSurfaceState { + QSize implicitSize; + Anchors anchors; + Margins margins; + WlrLayer::Enum layer = WlrLayer::Top; + qint32 exclusiveZone = 0; + WlrKeyboardFocus::Enum keyboardFocus = WlrKeyboardFocus::None; + + bool compositorPickesScreen = true; + QString mNamespace = "quickshell"; + + [[nodiscard]] bool isCompatible(const LayerSurfaceState& other) const { + return other.mNamespace == this->mNamespace; + } +}; + +class LayerSurface; + +class LayerSurfaceBridge: public QObject { +public: + LayerSurfaceState state; + + void commitState(); + + // Returns a bridge if attached, otherwise nullptr. + static LayerSurfaceBridge* get(QWindow* window); + + // Creates or reuses a bridge on the given window and returns if it compatible, otherwise nullptr. + static LayerSurfaceBridge* init(QWindow* window, LayerSurfaceState state); + +private: + explicit LayerSurfaceBridge(QWindow* parent): QObject(parent) {} + + LayerSurface* surface = nullptr; + + friend class LayerSurface; +}; + +class LayerSurface : public QtWaylandClient::QWaylandShellSurface , public QtWayland::zwlr_layer_surface_v1 { public: - QSWaylandLayerSurface( - QSWaylandLayerShellIntegration* shell, - QtWaylandClient::QWaylandWindow* window - ); + LayerSurface(LayerShellIntegration* shell, QtWaylandClient::QWaylandWindow* window); - ~QSWaylandLayerSurface() override; - Q_DISABLE_COPY_MOVE(QSWaylandLayerSurface); + ~LayerSurface() override; + Q_DISABLE_COPY_MOVE(LayerSurface); [[nodiscard]] bool isExposed() const override; void applyConfigure() override; - void setWindowGeometry(const QRect& geometry) override; + void setWindowGeometry(const QRect& /*geometry*/) override {} void attachPopup(QtWaylandClient::QWaylandShellSurface* popup) override; + void commit(); + private: void zwlr_layer_surface_v1_configure(quint32 serial, quint32 width, quint32 height) override; void zwlr_layer_surface_v1_closed() override; QWindow* qwindow(); - void updateLayer(); - void updateAnchors(); - void updateMargins(); - void updateExclusiveZone(); - void updateKeyboardFocus(); - LayershellWindowExtension* ext; - QSize size; + LayerSurfaceBridge* bridge; bool configured = false; + QSize size; - friend class LayershellWindowExtension; + LayerSurfaceState committed; }; + +} // namespace qs::wayland::layershell diff --git a/src/wayland/wlr_layershell/window.cpp b/src/wayland/wlr_layershell/window.cpp deleted file mode 100644 index 8139e841..00000000 --- a/src/wayland/wlr_layershell/window.cpp +++ /dev/null @@ -1,153 +0,0 @@ -#include "window.hpp" -#include - -#include -#include -#include -#include -#include -#include -#include - -#include "../../window/panelinterface.hpp" -#include "shell_integration.hpp" -#include "surface.hpp" - -LayershellWindowExtension::~LayershellWindowExtension() { - if (this->surface != nullptr) { - this->surface->ext = nullptr; - } -} - -LayershellWindowExtension* LayershellWindowExtension::get(QWindow* window) { - auto v = window->property("layershell_ext"); - - if (v.canConvert()) { - return v.value(); - } else { - return nullptr; - } -} - -bool LayershellWindowExtension::attach(QWindow* window) { - if (this->surface != nullptr) - qFatal() << "Cannot change the attached window of a LayershellWindowExtension"; - - auto* current = LayershellWindowExtension::get(window); - - bool hasSurface = false; - - if (current != nullptr) { - if (current->mNamespace != this->mNamespace) return false; - - if (current->surface != nullptr) { - if (current->surface->qwindow()->screen() != window->screen()) return false; - this->surface = current->surface; - - // update window with current settings, leveraging old extension's cached values - current->setAnchors(this->mAnchors); - current->setMargins(this->mMargins); - current->setExclusiveZone(this->mExclusiveZone); - current->setLayer(this->mLayer); - current->setKeyboardFocus(this->mKeyboardFocus); - - this->surface->ext = this; - current->surface = nullptr; - current->deleteLater(); - - hasSurface = true; - } - } - - if (!hasSurface) { - // Qt appears to be resetting the window's screen on creation on some systems. This works around it. - auto* screen = window->screen(); - window->create(); - window->setScreen(screen); - - auto* waylandWindow = dynamic_cast(window->handle()); - if (waylandWindow == nullptr) { - qWarning() << window << "is not a wayland window. Cannot create layershell surface."; - return false; - } - - static QSWaylandLayerShellIntegration* layershellIntegration = nullptr; // NOLINT - if (layershellIntegration == nullptr) { - layershellIntegration = new QSWaylandLayerShellIntegration(); - if (!layershellIntegration->initialize(waylandWindow->display())) { - delete layershellIntegration; - layershellIntegration = nullptr; - qWarning() << "Failed to initialize layershell integration"; - } - } - - waylandWindow->setShellIntegration(layershellIntegration); - } - - window->setProperty("layershell_ext", QVariant::fromValue(this)); - return true; -} - -void LayershellWindowExtension::setAnchors(Anchors anchors) { - if (anchors != this->mAnchors) { - this->mAnchors = anchors; - if (this->surface != nullptr) this->surface->updateAnchors(); - emit this->anchorsChanged(); - } -} - -Anchors LayershellWindowExtension::anchors() const { return this->mAnchors; } - -void LayershellWindowExtension::setMargins(Margins margins) { - if (margins != this->mMargins) { - this->mMargins = margins; - if (this->surface != nullptr) this->surface->updateMargins(); - emit this->marginsChanged(); - } -} - -Margins LayershellWindowExtension::margins() const { return this->mMargins; } - -void LayershellWindowExtension::setExclusiveZone(qint32 exclusiveZone) { - if (exclusiveZone != this->mExclusiveZone) { - this->mExclusiveZone = exclusiveZone; - if (this->surface != nullptr) this->surface->updateExclusiveZone(); - emit this->exclusiveZoneChanged(); - } -} - -qint32 LayershellWindowExtension::exclusiveZone() const { return this->mExclusiveZone; } - -void LayershellWindowExtension::setLayer(WlrLayer::Enum layer) { - if (layer != this->mLayer) { - this->mLayer = layer; - if (this->surface != nullptr) this->surface->updateLayer(); - emit this->layerChanged(); - } -} - -WlrLayer::Enum LayershellWindowExtension::layer() const { return this->mLayer; } - -void LayershellWindowExtension::setKeyboardFocus(WlrKeyboardFocus::Enum focus) { - if (focus != this->mKeyboardFocus) { - this->mKeyboardFocus = focus; - if (this->surface != nullptr) this->surface->updateKeyboardFocus(); - emit this->keyboardFocusChanged(); - } -} - -WlrKeyboardFocus::Enum LayershellWindowExtension::keyboardFocus() const { - return this->mKeyboardFocus; -} - -void LayershellWindowExtension::setUseWindowScreen(bool value) { - this->useWindowScreen = value; // has no effect post configure -} - -void LayershellWindowExtension::setNamespace(QString ns) { - if (!this->isConfigured()) this->mNamespace = std::move(ns); -} - -QString LayershellWindowExtension::ns() const { return this->mNamespace; } - -bool LayershellWindowExtension::isConfigured() const { return this->surface != nullptr; } diff --git a/src/wayland/wlr_layershell/window.hpp b/src/wayland/wlr_layershell/window.hpp deleted file mode 100644 index 050caf66..00000000 --- a/src/wayland/wlr_layershell/window.hpp +++ /dev/null @@ -1,118 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -#include "../../window/panelinterface.hpp" - -///! WlrLayershell layer. -/// See @@WlrLayershell.layer. -namespace WlrLayer { // NOLINT -Q_NAMESPACE; -QML_ELEMENT; - -enum Enum : quint8 { - /// Below bottom - 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); - -} // namespace WlrLayer - -///! WlrLayershell keyboard focus mode -/// See @@WlrLayershell.keyboardFocus. -namespace WlrKeyboardFocus { // NOLINT -Q_NAMESPACE; -QML_ELEMENT; - -enum Enum : quint8 { - /// No keyboard input will be accepted. - None = 0, - /// Exclusive access to the keyboard, locking out all other windows. - /// - /// > [!WARNING] You **CANNOT** use this to make a secure lock screen. - /// > - /// > If you want to make a lock screen, use @@WlSessionLock. - Exclusive = 1, - /// Access to the keyboard as determined by the operating system. - /// - /// > [!WARNING] On some systems, `OnDemand` may cause the shell window to - /// > retain focus over another window unexpectedly. - /// > You should try `None` if you experience issues. - OnDemand = 2, -}; -Q_ENUM_NS(Enum); - -} // namespace WlrKeyboardFocus - -class QSWaylandLayerSurface; - -class LayershellWindowExtension: public QObject { - Q_OBJECT; - -public: - LayershellWindowExtension(QObject* parent = nullptr): QObject(parent) {} - ~LayershellWindowExtension() override; - Q_DISABLE_COPY_MOVE(LayershellWindowExtension); - - // returns the layershell extension if attached, otherwise nullptr - static LayershellWindowExtension* get(QWindow* window); - - // Attach this layershell extension to the given window. - // The extension is reparented to the window and replaces any existing layershell extension. - // Returns false if the window cannot be used. - bool attach(QWindow* window); - - void setAnchors(Anchors anchors); - [[nodiscard]] Anchors anchors() const; - - void setMargins(Margins margins); - [[nodiscard]] Margins margins() const; - - void setExclusiveZone(qint32 exclusiveZone); - [[nodiscard]] qint32 exclusiveZone() const; - - void setLayer(WlrLayer::Enum layer); - [[nodiscard]] WlrLayer::Enum layer() const; - - void setKeyboardFocus(WlrKeyboardFocus::Enum focus); - [[nodiscard]] WlrKeyboardFocus::Enum keyboardFocus() const; - - // no effect if configured - void setUseWindowScreen(bool value); - void setNamespace(QString ns); - [[nodiscard]] QString ns() const; - [[nodiscard]] bool isConfigured() const; - -signals: - void anchorsChanged(); - void marginsChanged(); - void exclusiveZoneChanged(); - void layerChanged(); - void keyboardFocusChanged(); - -private: - // if configured the screen cannot be changed - QSWaylandLayerSurface* surface = nullptr; - - bool useWindowScreen = false; - Anchors mAnchors; - Margins mMargins; - qint32 mExclusiveZone = 0; - WlrLayer::Enum mLayer = WlrLayer::Top; - QString mNamespace = "quickshell"; - WlrKeyboardFocus::Enum mKeyboardFocus = WlrKeyboardFocus::None; - - friend class QSWaylandLayerSurface; -}; diff --git a/src/wayland/wlr_layershell.cpp b/src/wayland/wlr_layershell/wlr_layershell.cpp similarity index 61% rename from src/wayland/wlr_layershell.cpp rename to src/wayland/wlr_layershell/wlr_layershell.cpp index 84d3e147..648e64b6 100644 --- a/src/wayland/wlr_layershell.cpp +++ b/src/wayland/wlr_layershell/wlr_layershell.cpp @@ -1,29 +1,28 @@ #include "wlr_layershell.hpp" -#include #include #include #include #include #include -#include #include -#include "../core/qmlscreen.hpp" -#include "../window/panelinterface.hpp" -#include "../window/proxywindow.hpp" -#include "wlr_layershell/window.hpp" +#include "../../core/qmlscreen.hpp" +#include "../../window/panelinterface.hpp" +#include "../../window/proxywindow.hpp" +#include "surface.hpp" -WlrLayershell::WlrLayershell(QObject* parent) - : ProxyWindowBase(parent) - , ext(new LayershellWindowExtension(this)) {} +namespace qs::wayland::layershell { + +WlrLayershell::WlrLayershell(QObject* parent): ProxyWindowBase(parent) {} ProxiedWindow* WlrLayershell::retrieveWindow(QObject* oldInstance) { auto* old = qobject_cast(oldInstance); auto* window = old == nullptr ? nullptr : old->disownWindow(); if (window != nullptr) { - if (this->ext->attach(window)) { + this->bridge = LayerSurfaceBridge::init(window, this->computeState()); + if (this->bridge) { return window; } else { window->deleteLater(); @@ -36,7 +35,8 @@ ProxiedWindow* WlrLayershell::retrieveWindow(QObject* oldInstance) { ProxiedWindow* WlrLayershell::createQQuickWindow() { auto* window = this->ProxyWindowBase::createQQuickWindow(); - if (!this->ext->attach(window)) { + this->bridge = LayerSurfaceBridge::init(window, this->computeState()); + if (!this->bridge) { qWarning() << "Could not attach Layershell extension to new QQuickWindow. Layer will not " "behave correctly."; } @@ -47,17 +47,14 @@ ProxiedWindow* WlrLayershell::createQQuickWindow() { void WlrLayershell::connectWindow() { this->ProxyWindowBase::connectWindow(); - // clang-format off - QObject::connect(this->ext, &LayershellWindowExtension::layerChanged, this, &WlrLayershell::layerChanged); - QObject::connect(this->ext, &LayershellWindowExtension::keyboardFocusChanged, this, &WlrLayershell::keyboardFocusChanged); - QObject::connect(this->ext, &LayershellWindowExtension::anchorsChanged, this, &WlrLayershell::anchorsChanged); - QObject::connect(this->ext, &LayershellWindowExtension::exclusiveZoneChanged, this, &WlrLayershell::exclusiveZoneChanged); - QObject::connect(this->ext, &LayershellWindowExtension::marginsChanged, this, &WlrLayershell::marginsChanged); - QObject::connect(this, &ProxyWindowBase::widthChanged, this, &WlrLayershell::updateAutoExclusion); - QObject::connect(this, &ProxyWindowBase::heightChanged, this, &WlrLayershell::updateAutoExclusion); - QObject::connect(this, &WlrLayershell::anchorsChanged, this, &WlrLayershell::updateAutoExclusion); - // clang-format on + + QObject::connect( + this, + &ProxyWindowBase::heightChanged, + this, + &WlrLayershell::updateAutoExclusion + ); this->updateAutoExclusion(); } @@ -71,45 +68,24 @@ bool WlrLayershell::deleteOnInvisible() const { return true; } -void WlrLayershell::trySetWidth(qint32 implicitWidth) { - // only update the actual size if not blocked by anchors - if (!this->ext->anchors().horizontalConstraint()) { - this->ProxyWindowBase::trySetWidth(implicitWidth); +void WlrLayershell::onPolished() { + if (this->bridge) { + this->bridge->state = this->computeState(); + this->bridge->commitState(); } + + this->ProxyWindowBase::onPolished(); } -void WlrLayershell::trySetHeight(qint32 implicitHeight) { - // only update the actual size if not blocked by anchors - if (!this->ext->anchors().verticalConstraint()) { - this->ProxyWindowBase::trySetHeight(implicitHeight); - } -} +void WlrLayershell::trySetWidth(qint32 /*implicitWidth*/) { this->onStateChanged(); } +void WlrLayershell::trySetHeight(qint32 /*implicitHeight*/) { this->onStateChanged(); } void WlrLayershell::setScreen(QuickshellScreenInfo* screen) { - this->ext->setUseWindowScreen(screen != nullptr); + this->compositorPicksScreen = screen == nullptr; this->ProxyWindowBase::setScreen(screen); } -// NOLINTBEGIN -#define extPair(type, get, set) \ - type WlrLayershell::get() const { return this->ext->get(); } \ - void WlrLayershell::set(type value) { this->ext->set(value); } - -extPair(WlrLayer::Enum, layer, setLayer); -extPair(WlrKeyboardFocus::Enum, keyboardFocus, setKeyboardFocus); -extPair(Margins, margins, setMargins); -// NOLINTEND - -Anchors WlrLayershell::anchors() const { return this->ext->anchors(); } - -void WlrLayershell::setAnchors(Anchors anchors) { - this->ext->setAnchors(anchors); - if (!this->window) return; - - // explicitly set width values are tracked so the entire screen isn't covered if an anchor is removed. - if (!anchors.horizontalConstraint()) this->ProxyWindowBase::trySetWidth(this->implicitWidth()); - if (!anchors.verticalConstraint()) this->ProxyWindowBase::trySetHeight(this->implicitHeight()); -} +void WlrLayershell::onStateChanged() { this->schedulePolish(); } bool WlrLayershell::aboveWindows() const { return this->layer() > WlrLayer::Bottom; } @@ -123,51 +99,33 @@ void WlrLayershell::setFocusable(bool focusable) { this->setKeyboardFocus(focusable ? WlrKeyboardFocus::OnDemand : WlrKeyboardFocus::None); } -QString WlrLayershell::ns() const { return this->ext->ns(); } - -void WlrLayershell::setNamespace(QString ns) { - this->ext->setNamespace(std::move(ns)); - emit this->namespaceChanged(); +LayerSurfaceState WlrLayershell::computeState() const { + return LayerSurfaceState { + .implicitSize = QSize(this->implicitWidth(), this->implicitHeight()), + .anchors = this->bAnchors, + .margins = this->bMargins, + .layer = this->bLayer, + .exclusiveZone = this->bcExclusiveZone, + .keyboardFocus = this->bKeyboardFocus, + .compositorPickesScreen = this->compositorPicksScreen, + .mNamespace = this->bNamespace, + }; } -qint32 WlrLayershell::exclusiveZone() const { return this->ext->exclusiveZone(); } +qint32 WlrLayershell::computeExclusiveZone() const { + switch (this->bExclusionMode.value()) { + case ExclusionMode::Ignore: return -1; + case ExclusionMode::Normal: return this->bExclusiveZone; + case ExclusionMode::Auto: + const auto anchors = this->bAnchors.value(); -void WlrLayershell::setExclusiveZone(qint32 exclusiveZone) { - this->mExclusiveZone = exclusiveZone; - this->setExclusionMode(ExclusionMode::Normal); - this->ext->setExclusiveZone(exclusiveZone); -} - -ExclusionMode::Enum WlrLayershell::exclusionMode() const { return this->mExclusionMode; } - -void WlrLayershell::setExclusionMode(ExclusionMode::Enum exclusionMode) { - if (exclusionMode == this->mExclusionMode) return; - this->mExclusionMode = exclusionMode; - - if (exclusionMode == ExclusionMode::Normal) { - this->ext->setExclusiveZone(this->mExclusiveZone); - } else if (exclusionMode == ExclusionMode::Ignore) { - this->ext->setExclusiveZone(-1); - } else { - this->setAutoExclusion(); + if (anchors.horizontalConstraint()) return this->height(); + else if (anchors.verticalConstraint()) return this->width(); + else return 0; } } -void WlrLayershell::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 WlrLayershell::updateAutoExclusion() { - if (this->mExclusionMode == ExclusionMode::Auto) { - this->setAutoExclusion(); - } -} +void WlrLayershell::updateAutoExclusion() { this->bcExclusiveZone.notify(); } WlrLayershell* WlrLayershell::qmlAttachedProperties(QObject* object) { if (auto* obj = qobject_cast(object)) { @@ -250,3 +208,5 @@ proxyPair(bool, aboveWindows, setAboveWindows); #undef proxyPair // NOLINTEND + +} // namespace qs::wayland::layershell diff --git a/src/wayland/wlr_layershell.hpp b/src/wayland/wlr_layershell/wlr_layershell.hpp similarity index 53% rename from src/wayland/wlr_layershell.hpp rename to src/wayland/wlr_layershell/wlr_layershell.hpp index c91f98c8..98e8d4b5 100644 --- a/src/wayland/wlr_layershell.hpp +++ b/src/wayland/wlr_layershell/wlr_layershell.hpp @@ -1,16 +1,71 @@ #pragma once +#include #include +#include #include #include #include +#include #include #include -#include "../core/doc.hpp" -#include "../window/panelinterface.hpp" -#include "../window/proxywindow.hpp" -#include "wlr_layershell/window.hpp" +#include "../../core/doc.hpp" +#include "../../core/util.hpp" +#include "../../window/panelinterface.hpp" +#include "../../window/proxywindow.hpp" + +namespace qs::wayland::layershell { + +struct LayerSurfaceState; +class LayerSurfaceBridge; + +///! WlrLayershell layer. +/// See @@WlrLayershell.layer. +namespace WlrLayer { // NOLINT +Q_NAMESPACE; +QML_ELEMENT; + +enum Enum : quint8 { + /// Below bottom + 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); + +} // namespace WlrLayer + +///! WlrLayershell keyboard focus mode +/// See @@WlrLayershell.keyboardFocus. +namespace WlrKeyboardFocus { // NOLINT +Q_NAMESPACE; +QML_ELEMENT; + +enum Enum : quint8 { + /// No keyboard input will be accepted. + None = 0, + /// Exclusive access to the keyboard, locking out all other windows. + /// + /// > [!WARNING] You **CANNOT** use this to make a secure lock screen. + /// > + /// > If you want to make a lock screen, use @@WlSessionLock. + Exclusive = 1, + /// Access to the keyboard as determined by the operating system. + /// + /// > [!WARNING] On some systems, `OnDemand` may cause the shell window to + /// > retain focus over another window unexpectedly. + /// > You should try `None` if you experience issues. + OnDemand = 2, +}; +Q_ENUM_NS(Enum); + +} // namespace WlrKeyboardFocus ///! Wlroots layershell window /// Decorationless window that can be attached to the screen edges using the [zwlr_layer_shell_v1] protocol. @@ -43,13 +98,13 @@ class WlrLayershell: public ProxyWindowBase { // clang-format off Q_OBJECT; /// The shell layer the window sits in. Defaults to `WlrLayer.Top`. - Q_PROPERTY(WlrLayer::Enum layer READ layer WRITE setLayer NOTIFY layerChanged); + Q_PROPERTY(qs::wayland::layershell::WlrLayer::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 NOTIFY namespaceChanged); /// The degree of keyboard focus taken. Defaults to `KeyboardFocus.None`. - Q_PROPERTY(WlrKeyboardFocus::Enum keyboardFocus READ keyboardFocus WRITE setKeyboardFocus NOTIFY keyboardFocusChanged); + Q_PROPERTY(qs::wayland::layershell::WlrKeyboardFocus::Enum keyboardFocus READ keyboardFocus WRITE setKeyboardFocus NOTIFY keyboardFocusChanged); QSDOC_HIDE Q_PROPERTY(Anchors anchors READ anchors WRITE setAnchors NOTIFY anchorsChanged); QSDOC_HIDE Q_PROPERTY(qint32 exclusiveZone READ exclusiveZone WRITE setExclusiveZone NOTIFY exclusiveZoneChanged); @@ -69,31 +124,37 @@ public: void connectWindow() override; [[nodiscard]] bool deleteOnInvisible() const override; + void onPolished() override; void trySetWidth(qint32 implicitWidth) override; void trySetHeight(qint32 implicitHeight) override; void setScreen(QuickshellScreenInfo* screen) override; - [[nodiscard]] WlrLayer::Enum layer() const; - void setLayer(WlrLayer::Enum layer); // NOLINT + [[nodiscard]] WlrLayer::Enum layer() const { return this->bLayer; } + void setLayer(WlrLayer::Enum layer) { this->bLayer = layer; } - [[nodiscard]] QString ns() const; - void setNamespace(QString ns); + [[nodiscard]] QString ns() const { return this->bNamespace; } + void setNamespace(const QString& ns) { this->bNamespace = ns; } - [[nodiscard]] WlrKeyboardFocus::Enum keyboardFocus() const; - void setKeyboardFocus(WlrKeyboardFocus::Enum focus); // NOLINT + [[nodiscard]] WlrKeyboardFocus::Enum keyboardFocus() const { return this->bKeyboardFocus; } + void setKeyboardFocus(WlrKeyboardFocus::Enum focus) { this->bKeyboardFocus = focus; } - [[nodiscard]] Anchors anchors() const; - void setAnchors(Anchors anchors); + [[nodiscard]] Anchors anchors() const { return this->bAnchors; } + void setAnchors(Anchors anchors) { this->bAnchors = anchors; } - [[nodiscard]] qint32 exclusiveZone() const; - void setExclusiveZone(qint32 exclusiveZone); + [[nodiscard]] qint32 exclusiveZone() const { return this->bExclusiveZone; } + void setExclusiveZone(qint32 exclusiveZone) { + Qt::beginPropertyUpdateGroup(); + this->bExclusiveZone = exclusiveZone; + this->bExclusionMode = ExclusionMode::Normal; + Qt::endPropertyUpdateGroup(); + } - [[nodiscard]] ExclusionMode::Enum exclusionMode() const; - void setExclusionMode(ExclusionMode::Enum exclusionMode); + [[nodiscard]] ExclusionMode::Enum exclusionMode() const { return this->bExclusionMode; } + void setExclusionMode(ExclusionMode::Enum exclusionMode) { this->bExclusionMode = exclusionMode; } - [[nodiscard]] Margins margins() const; - void setMargins(Margins margins); // NOLINT + [[nodiscard]] Margins margins() const { return this->bMargins; } + void setMargins(Margins margins) { this->bMargins = margins; } [[nodiscard]] bool aboveWindows() const; void setAboveWindows(bool aboveWindows); @@ -116,12 +177,30 @@ private slots: void updateAutoExclusion(); private: - void setAutoExclusion(); + [[nodiscard]] LayerSurfaceState computeState() const; + [[nodiscard]] qint32 computeExclusiveZone() const; - LayershellWindowExtension* ext; + void onStateChanged(); - ExclusionMode::Enum mExclusionMode = ExclusionMode::Auto; - qint32 mExclusiveZone = 0; + bool compositorPicksScreen = true; + LayerSurfaceBridge* bridge = nullptr; + + // clang-format off + Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(WlrLayershell, WlrLayer::Enum, bLayer, WlrLayer::Top, &WlrLayershell::layerChanged); + Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(WlrLayershell, QString, bNamespace, "quickshell", &WlrLayershell::namespaceChanged); + Q_OBJECT_BINDABLE_PROPERTY(WlrLayershell, Anchors, bAnchors, &WlrLayershell::anchorsChanged); + Q_OBJECT_BINDABLE_PROPERTY(WlrLayershell, Margins, bMargins, &WlrLayershell::marginsChanged); + Q_OBJECT_BINDABLE_PROPERTY(WlrLayershell, qint32, bExclusiveZone, &WlrLayershell::exclusiveZoneChanged); + Q_OBJECT_BINDABLE_PROPERTY(WlrLayershell, WlrKeyboardFocus::Enum, bKeyboardFocus, &WlrLayershell::keyboardFocusChanged); + Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(WlrLayershell, ExclusionMode::Enum, bExclusionMode, ExclusionMode::Auto, &WlrLayershell::exclusionModeChanged); + Q_OBJECT_COMPUTED_PROPERTY(WlrLayershell, qint32, bcExclusiveZone, &WlrLayershell::computeExclusiveZone); + + QS_BINDING_SUBSCRIBE_METHOD(WlrLayershell, bLayer, onStateChanged, onValueChanged); + QS_BINDING_SUBSCRIBE_METHOD(WlrLayershell, bAnchors, onStateChanged, onValueChanged); + QS_BINDING_SUBSCRIBE_METHOD(WlrLayershell, bMargins, onStateChanged, onValueChanged); + QS_BINDING_SUBSCRIBE_METHOD(WlrLayershell, bcExclusiveZone, onStateChanged, onValueChanged); + QS_BINDING_SUBSCRIBE_METHOD(WlrLayershell, bKeyboardFocus, onStateChanged, onValueChanged); + // clang-format on }; class WaylandPanelInterface: public PanelWindowInterface { @@ -194,3 +273,5 @@ private: friend class WlrLayershell; }; + +} // namespace qs::wayland::layershell diff --git a/src/window/proxywindow.hpp b/src/window/proxywindow.hpp index 1cf5a02f..d14f767b 100644 --- a/src/window/proxywindow.hpp +++ b/src/window/proxywindow.hpp @@ -153,7 +153,7 @@ protected slots: void onMaskChanged(); void onMaskDestroyed(); void onScreenDestroyed(); - void onPolished(); + virtual void onPolished(); void runLints(); protected: