diff --git a/src/wayland/hyprland/surface/hyprland-surface-v1.xml b/src/wayland/hyprland/surface/hyprland-surface-v1.xml index 2f68336..c4b1424 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. - + This interface allows a client to create hyprland surface objects. @@ -63,7 +63,7 @@ - + This interface allows access to hyprland-specific properties of a wl_surface. @@ -96,5 +96,31 @@ + + + + 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. + + + + diff --git a/src/wayland/hyprland/surface/manager.cpp b/src/wayland/hyprland/surface/manager.cpp index 31829bb..6354255 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 5150487..b00ee33 100644 --- a/src/wayland/hyprland/surface/qml.cpp +++ b/src/wayland/hyprland/surface/qml.cpp @@ -1,17 +1,20 @@ #include "qml.hpp" #include +#include #include #include #include #include +#include #include #include +#include #include +#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(this->mWindow->handle()); + auto* window = dynamic_cast(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(ext); + auto v = this->mWaylandWindow->property("hyprland_window_ext"); + if (v.canConvert()) { + auto* windowExt = v.value(); + if (windowExt != this && windowExt->surface) { + this->surface.swap(windowExt->surface); + } + } + + if (!this->surface) { + auto* ext = manager->createHyprlandExtension(this->mWaylandWindow); + this->surface = std::unique_ptr(ext); + } - if (this->mOpacity != 1.0) { - this->surface->setOpacity(this->mOpacity); - qs::wayland::util::scheduleCommit(this->proxyWindow); + 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 ce32a96..157b8f3 100644 --- a/src/wayland/hyprland/surface/qml.hpp +++ b/src/wayland/hyprland/surface/qml.hpp @@ -9,6 +9,7 @@ #include #include +#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 @@ class HyprlandWindow: public QObject { [[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 slots: 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 surface; }; diff --git a/src/wayland/hyprland/surface/surface.cpp b/src/wayland/hyprland/surface/surface.cpp index d1aa24f..487da40 100644 --- a/src/wayland/hyprland/surface/surface.cpp +++ b/src/wayland/hyprland/surface/surface.cpp @@ -1,19 +1,53 @@ #include "surface.hpp" +#include +#include +#include +#include +#include #include #include +#include #include #include 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 a27e50e..1c8b548 100644 --- a/src/wayland/hyprland/surface/surface.hpp +++ b/src/wayland/hyprland/surface/surface.hpp @@ -1,21 +1,31 @@ #pragma once +#include #include +#include #include #include #include #include +#include #include 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