From b289bfa504dc786e3ad9535d465431e739cb02d8 Mon Sep 17 00:00:00 2001 From: outfoxxed <outfoxxed@outfoxxed.me> Date: Thu, 23 Jan 2025 14:00:16 -0800 Subject: [PATCH] hyprland/surface: add visibleMask --- .../hyprland/surface/hyprland-surface-v1.xml | 30 +++- src/wayland/hyprland/surface/manager.cpp | 4 +- src/wayland/hyprland/surface/qml.cpp | 160 ++++++++++++++---- src/wayland/hyprland/surface/qml.hpp | 22 +++ src/wayland/hyprland/surface/surface.cpp | 38 ++++- src/wayland/hyprland/surface/surface.hpp | 12 +- 6 files changed, 230 insertions(+), 36 deletions(-) diff --git a/src/wayland/hyprland/surface/hyprland-surface-v1.xml b/src/wayland/hyprland/surface/hyprland-surface-v1.xml index 2f683365..c4b1424f 100644 --- a/src/wayland/hyprland/surface/hyprland-surface-v1.xml +++ b/src/wayland/hyprland/surface/hyprland-surface-v1.xml @@ -34,7 +34,7 @@ This protocol exposes hyprland-specific wl_surface properties. </description> - <interface name="hyprland_surface_manager_v1" version="1"> + <interface name="hyprland_surface_manager_v1" version="2"> <description summary="manager for hyprland surface objects"> This interface allows a client to create hyprland surface objects. </description> @@ -63,7 +63,7 @@ </enum> </interface> - <interface name="hyprland_surface_v1" version="1"> + <interface name="hyprland_surface_v1" version="2"> <description summary="hyprland-specific wl_surface properties"> This interface allows access to hyprland-specific properties of a wl_surface. @@ -96,5 +96,31 @@ <entry name="no_surface" value="0" summary="wl_surface was destroyed"/> <entry name="out_of_range" value="1" summary="given opacity was not in the range 0.0 - 1.0 (inclusive)"/> </enum> + + <request name="set_visible_region" since="2"> + <description summary="set the visible region of the surface"> + This request sets the region of the surface that contains visible content. + Visible content refers to content that has an alpha value greater than zero. + + The visible region is an optimization hint for the compositor that lets it + avoid drawing parts of the surface that are not visible. Setting a visible region + that does not contain all content in the surface may result in missing content + not being drawn. + + The visible region is specified in buffer-local coordinates. + + The compositor ignores the parts of the visible region that fall outside of the surface. + When all parts of the region fall outside of the buffer geometry, the compositor may + avoid rendering the surface entirely. + + The initial value for the visible region is empty. Setting the + visible region has copy semantics, and the wl_region object can be destroyed immediately. + A NULL wl_region causes the visible region to be set to empty. + + Does not take effect until wl_surface.commit is called. + </description> + + <arg name="region" type="object" interface="wl_region" allow-null="true"/> + </request> </interface> </protocol> diff --git a/src/wayland/hyprland/surface/manager.cpp b/src/wayland/hyprland/surface/manager.cpp index 31829bb6..6354255e 100644 --- a/src/wayland/hyprland/surface/manager.cpp +++ b/src/wayland/hyprland/surface/manager.cpp @@ -7,13 +7,13 @@ namespace qs::hyprland::surface::impl { -HyprlandSurfaceManager::HyprlandSurfaceManager(): QWaylandClientExtensionTemplate(1) { +HyprlandSurfaceManager::HyprlandSurfaceManager(): QWaylandClientExtensionTemplate(2) { this->initialize(); } HyprlandSurface* HyprlandSurfaceManager::createHyprlandExtension(QtWaylandClient::QWaylandWindow* surface) { - return new HyprlandSurface(this->get_hyprland_surface(surface->surface())); + return new HyprlandSurface(this->get_hyprland_surface(surface->surface()), surface); } HyprlandSurfaceManager* HyprlandSurfaceManager::instance() { diff --git a/src/wayland/hyprland/surface/qml.cpp b/src/wayland/hyprland/surface/qml.cpp index 5150487f..b00ee33e 100644 --- a/src/wayland/hyprland/surface/qml.cpp +++ b/src/wayland/hyprland/surface/qml.cpp @@ -1,17 +1,20 @@ #include "qml.hpp" #include <memory> +#include <private/qhighdpiscaling_p.h> #include <private/qwaylandwindow_p.h> #include <qlogging.h> #include <qobject.h> #include <qqmlinfo.h> +#include <qregion.h> #include <qtmetamacros.h> #include <qtypes.h> +#include <qvariant.h> #include <qwindow.h> +#include "../../../core/region.hpp" #include "../../../window/proxywindow.hpp" #include "../../../window/windowinterface.hpp" -#include "../../util.hpp" #include "manager.hpp" #include "surface.hpp" @@ -40,6 +43,15 @@ HyprlandWindow::HyprlandWindow(ProxyWindowBase* window): QObject(nullptr), proxy &HyprlandWindow::onWindowConnected ); + QObject::connect(window, &ProxyWindowBase::polished, this, &HyprlandWindow::onWindowPolished); + + QObject::connect( + window, + &ProxyWindowBase::devicePixelRatioChanged, + this, + &HyprlandWindow::updateVisibleMask + ); + QObject::connect(window, &QObject::destroyed, this, &HyprlandWindow::onProxyWindowDestroyed); if (window->backingWindow()) { @@ -60,14 +72,76 @@ void HyprlandWindow::setOpacity(qreal opacity) { this->mOpacity = opacity; - if (this->surface) { - this->surface->setOpacity(opacity); - qs::wayland::util::scheduleCommit(this->proxyWindow); + if (this->surface && this->proxyWindow) { + this->pendingPolish.opacity = true; + this->proxyWindow->schedulePolish(); } emit this->opacityChanged(); } +PendingRegion* HyprlandWindow::visibleMask() const { return this->mVisibleMask; } + +void HyprlandWindow::setVisibleMask(PendingRegion* mask) { + if (mask == this->mVisibleMask) return; + + if (this->mVisibleMask) { + QObject::disconnect(this->mVisibleMask, nullptr, this, nullptr); + } + + this->mVisibleMask = mask; + + if (mask) { + QObject::connect(mask, &QObject::destroyed, this, &HyprlandWindow::onVisibleMaskDestroyed); + QObject::connect(mask, &PendingRegion::changed, this, &HyprlandWindow::updateVisibleMask); + } + + this->updateVisibleMask(); + emit this->visibleMaskChanged(); +} + +void HyprlandWindow::onVisibleMaskDestroyed() { + this->mVisibleMask = nullptr; + this->updateVisibleMask(); + emit this->visibleMaskChanged(); +} + +void HyprlandWindow::updateVisibleMask() { + if (!this->surface || !this->proxyWindow) return; + + this->pendingPolish.visibleMask = true; + this->proxyWindow->schedulePolish(); +} + +void HyprlandWindow::onWindowPolished() { + if (!this->surface) return; + + if (this->pendingPolish.opacity) { + this->surface->setOpacity(this->mOpacity); + this->pendingPolish.opacity = false; + } + + if (this->pendingPolish.visibleMask) { + QRegion mask; + if (this->mVisibleMask != nullptr) { + mask = + this->mVisibleMask->applyTo(QRect(0, 0, this->mWindow->width(), this->mWindow->height())); + } + + auto dpr = this->proxyWindow->devicePixelRatio(); + if (dpr != 1.0) { + mask = QHighDpi::scale(mask, dpr); + } + + if (mask.isEmpty() && this->mVisibleMask) { + mask = QRect(-1, -1, 1, 1); + } + + this->surface->setVisibleRegion(mask); + this->pendingPolish.visibleMask = false; + } +} + void HyprlandWindow::onWindowConnected() { this->mWindow = this->proxyWindow->backingWindow(); // disconnected by destructor @@ -86,33 +160,46 @@ void HyprlandWindow::onWindowVisibleChanged() { if (!this->mWindow->handle()) { this->mWindow->create(); } + } - this->mWaylandWindow = dynamic_cast<QWaylandWindow*>(this->mWindow->handle()); + auto* window = dynamic_cast<QWaylandWindow*>(this->mWindow->handle()); + if (window == this->mWaylandWindow) return; - if (this->mWaylandWindow) { - // disconnected by destructor + if (this->mWaylandWindow) { + QObject::disconnect(this->mWaylandWindow, nullptr, this, nullptr); + } - QObject::connect( - this->mWaylandWindow, - &QWaylandWindow::surfaceCreated, - this, - &HyprlandWindow::onWaylandSurfaceCreated - ); + this->mWaylandWindow = window; + if (!window) return; - QObject::connect( - this->mWaylandWindow, - &QWaylandWindow::surfaceDestroyed, - this, - &HyprlandWindow::onWaylandSurfaceDestroyed - ); + QObject::connect( + this->mWaylandWindow, + &QObject::destroyed, + this, + &HyprlandWindow::onWaylandWindowDestroyed + ); - if (this->mWaylandWindow->surface()) { - this->onWaylandSurfaceCreated(); - } - } + QObject::connect( + this->mWaylandWindow, + &QWaylandWindow::surfaceCreated, + this, + &HyprlandWindow::onWaylandSurfaceCreated + ); + + QObject::connect( + this->mWaylandWindow, + &QWaylandWindow::surfaceDestroyed, + this, + &HyprlandWindow::onWaylandSurfaceDestroyed + ); + + if (this->mWaylandWindow->surface()) { + this->onWaylandSurfaceCreated(); } } +void HyprlandWindow::onWaylandWindowDestroyed() { this->mWaylandWindow = nullptr; } + void HyprlandWindow::onWaylandSurfaceCreated() { auto* manager = impl::HyprlandSurfaceManager::instance(); @@ -122,12 +209,26 @@ void HyprlandWindow::onWaylandSurfaceCreated() { return; } - auto* ext = manager->createHyprlandExtension(this->mWaylandWindow); - this->surface = std::unique_ptr<impl::HyprlandSurface>(ext); + auto v = this->mWaylandWindow->property("hyprland_window_ext"); + if (v.canConvert<HyprlandWindow*>()) { + auto* windowExt = v.value<HyprlandWindow*>(); + if (windowExt != this && windowExt->surface) { + this->surface.swap(windowExt->surface); + } + } - if (this->mOpacity != 1.0) { - this->surface->setOpacity(this->mOpacity); - qs::wayland::util::scheduleCommit(this->proxyWindow); + if (!this->surface) { + auto* ext = manager->createHyprlandExtension(this->mWaylandWindow); + this->surface = std::unique_ptr<impl::HyprlandSurface>(ext); + } + + this->mWaylandWindow->setProperty("hyprland_window_ext", QVariant::fromValue(this)); + + this->pendingPolish.opacity = this->mOpacity != 1.0; + this->pendingPolish.visibleMask = this->mVisibleMask; + + if (this->pendingPolish.opacity || this->pendingPolish.visibleMask) { + this->proxyWindow->schedulePolish(); } } @@ -144,8 +245,9 @@ void HyprlandWindow::onProxyWindowDestroyed() { // Deleting it when the proxy window is deleted will cause a full opacity frame between the destruction of the // hyprland_surface_v1 and wl_surface objects. + this->proxyWindow = nullptr; + if (this->surface == nullptr) { - this->proxyWindow = nullptr; this->deleteLater(); } } diff --git a/src/wayland/hyprland/surface/qml.hpp b/src/wayland/hyprland/surface/qml.hpp index ce32a967..157b8f32 100644 --- a/src/wayland/hyprland/surface/qml.hpp +++ b/src/wayland/hyprland/surface/qml.hpp @@ -9,6 +9,7 @@ #include <qtypes.h> #include <qwindow.h> +#include "../../../core/region.hpp" #include "../../../window/proxywindow.hpp" #include "surface.hpp" @@ -31,11 +32,18 @@ namespace qs::hyprland::surface { /// [hyprland-surface-v1]: https://github.com/hyprwm/hyprland-protocols/blob/main/protocols/hyprland-surface-v1.xml class HyprlandWindow: public QObject { Q_OBJECT; + // clang-format off /// A multiplier for the window's overall opacity, ranging from 1.0 to 0.0. Overall opacity includes the opacity of /// both the window content *and* visual effects such as blur that apply to it. /// /// Default: 1.0 Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged); + /// A hint to the compositor that only certain regions of the surface should be rendered. + /// This can be used to avoid rendering large empty regions of a window which can increase + /// performance, especially if the window is blurred. The mask should include all pixels + /// of the window that do not have an alpha value of 0. + Q_PROPERTY(PendingRegion* visibleMask READ visibleMask WRITE setVisibleMask NOTIFY visibleMaskChanged); + // clang-format on QML_ELEMENT; QML_UNCREATABLE("HyprlandWindow can only be used as an attached object."); QML_ATTACHED(HyprlandWindow); @@ -48,17 +56,25 @@ public: [[nodiscard]] qreal opacity() const; void setOpacity(qreal opacity); + [[nodiscard]] PendingRegion* visibleMask() const; + virtual void setVisibleMask(PendingRegion* mask); + static HyprlandWindow* qmlAttachedProperties(QObject* object); signals: void opacityChanged(); + void visibleMaskChanged(); private slots: void onWindowConnected(); void onWindowVisibleChanged(); + void onWaylandWindowDestroyed(); void onWaylandSurfaceCreated(); void onWaylandSurfaceDestroyed(); void onProxyWindowDestroyed(); + void onVisibleMaskDestroyed(); + void onWindowPolished(); + void updateVisibleMask(); private: void disconnectWaylandWindow(); @@ -67,7 +83,13 @@ private: QWindow* mWindow = nullptr; QtWaylandClient::QWaylandWindow* mWaylandWindow = nullptr; + struct { + bool opacity : 1 = false; + bool visibleMask : 1 = false; + } pendingPolish; + qreal mOpacity = 1.0; + PendingRegion* mVisibleMask = nullptr; std::unique_ptr<impl::HyprlandSurface> surface; }; diff --git a/src/wayland/hyprland/surface/surface.cpp b/src/wayland/hyprland/surface/surface.cpp index d1aa24fb..487da40b 100644 --- a/src/wayland/hyprland/surface/surface.cpp +++ b/src/wayland/hyprland/surface/surface.cpp @@ -1,19 +1,53 @@ #include "surface.hpp" +#include <cmath> +#include <private/qwaylanddisplay_p.h> +#include <private/qwaylandintegration_p.h> +#include <qlogging.h> +#include <qregion.h> #include <qtypes.h> #include <qwayland-hyprland-surface-v1.h> +#include <wayland-client-protocol.h> #include <wayland-hyprland-surface-v1-client-protocol.h> #include <wayland-util.h> namespace qs::hyprland::surface::impl { -HyprlandSurface::HyprlandSurface(::hyprland_surface_v1* surface) - : QtWayland::hyprland_surface_v1(surface) {} +HyprlandSurface::HyprlandSurface( + ::hyprland_surface_v1* surface, + QtWaylandClient::QWaylandWindow* backer +) + : QtWayland::hyprland_surface_v1(surface) + , backer(backer) + , backerSurface(backer->surface()) {} HyprlandSurface::~HyprlandSurface() { this->destroy(); } +bool HyprlandSurface::surfaceEq(wl_surface* surface) const { + return surface == this->backerSurface; +} + void HyprlandSurface::setOpacity(qreal opacity) { this->set_opacity(wl_fixed_from_double(opacity)); } +void HyprlandSurface::setVisibleRegion(const QRegion& region) { + if (this->version() < HYPRLAND_SURFACE_V1_SET_VISIBLE_REGION_SINCE_VERSION) { + qWarning() << "Cannot set hyprland surface visible region: compositor does not support " + "hyprland_surface_v1.set_visible_region"; + return; + } + + if (region.isEmpty()) { + this->set_visible_region(nullptr); + } else { + static const auto* waylandIntegration = QtWaylandClient::QWaylandIntegration::instance(); + auto* display = waylandIntegration->display(); + + auto* wlRegion = display->createRegion(region); + this->set_visible_region(wlRegion); + wl_region_destroy(wlRegion); // NOLINT(misc-include-cleaner) + } +} + } // namespace qs::hyprland::surface::impl diff --git a/src/wayland/hyprland/surface/surface.hpp b/src/wayland/hyprland/surface/surface.hpp index a27e50e3..1c8b5486 100644 --- a/src/wayland/hyprland/surface/surface.hpp +++ b/src/wayland/hyprland/surface/surface.hpp @@ -1,21 +1,31 @@ #pragma once +#include <private/qwaylandwindow_p.h> #include <qobject.h> +#include <qregion.h> #include <qtclasshelpermacros.h> #include <qtmetamacros.h> #include <qtypes.h> #include <qwayland-hyprland-surface-v1.h> +#include <wayland-client-protocol.h> #include <wayland-hyprland-surface-v1-client-protocol.h> namespace qs::hyprland::surface::impl { class HyprlandSurface: public QtWayland::hyprland_surface_v1 { public: - explicit HyprlandSurface(::hyprland_surface_v1* surface); + explicit HyprlandSurface(::hyprland_surface_v1* surface, QtWaylandClient::QWaylandWindow* backer); ~HyprlandSurface() override; Q_DISABLE_COPY_MOVE(HyprlandSurface); + [[nodiscard]] bool surfaceEq(wl_surface* surface) const; + void setOpacity(qreal opacity); + void setVisibleRegion(const QRegion& region); + +private: + QtWaylandClient::QWaylandWindow* backer; + wl_surface* backerSurface = nullptr; }; } // namespace qs::hyprland::surface::impl