1
0
Fork 0

hyprland/surface: add visibleMask

This commit is contained in:
outfoxxed 2025-01-23 14:00:16 -08:00
parent cdaff2967f
commit b289bfa504
Signed by: outfoxxed
GPG key ID: 4C88A185FB89301E
6 changed files with 230 additions and 36 deletions

View file

@ -34,7 +34,7 @@
This protocol exposes hyprland-specific wl_surface properties. This protocol exposes hyprland-specific wl_surface properties.
</description> </description>
<interface name="hyprland_surface_manager_v1" version="1"> <interface name="hyprland_surface_manager_v1" version="2">
<description summary="manager for hyprland surface objects"> <description summary="manager for hyprland surface objects">
This interface allows a client to create hyprland surface objects. This interface allows a client to create hyprland surface objects.
</description> </description>
@ -63,7 +63,7 @@
</enum> </enum>
</interface> </interface>
<interface name="hyprland_surface_v1" version="1"> <interface name="hyprland_surface_v1" version="2">
<description summary="hyprland-specific wl_surface properties"> <description summary="hyprland-specific wl_surface properties">
This interface allows access to hyprland-specific properties of a wl_surface. 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="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)"/> <entry name="out_of_range" value="1" summary="given opacity was not in the range 0.0 - 1.0 (inclusive)"/>
</enum> </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> </interface>
</protocol> </protocol>

View file

@ -7,13 +7,13 @@
namespace qs::hyprland::surface::impl { namespace qs::hyprland::surface::impl {
HyprlandSurfaceManager::HyprlandSurfaceManager(): QWaylandClientExtensionTemplate(1) { HyprlandSurfaceManager::HyprlandSurfaceManager(): QWaylandClientExtensionTemplate(2) {
this->initialize(); this->initialize();
} }
HyprlandSurface* HyprlandSurface*
HyprlandSurfaceManager::createHyprlandExtension(QtWaylandClient::QWaylandWindow* surface) { 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() { HyprlandSurfaceManager* HyprlandSurfaceManager::instance() {

View file

@ -1,17 +1,20 @@
#include "qml.hpp" #include "qml.hpp"
#include <memory> #include <memory>
#include <private/qhighdpiscaling_p.h>
#include <private/qwaylandwindow_p.h> #include <private/qwaylandwindow_p.h>
#include <qlogging.h> #include <qlogging.h>
#include <qobject.h> #include <qobject.h>
#include <qqmlinfo.h> #include <qqmlinfo.h>
#include <qregion.h>
#include <qtmetamacros.h> #include <qtmetamacros.h>
#include <qtypes.h> #include <qtypes.h>
#include <qvariant.h>
#include <qwindow.h> #include <qwindow.h>
#include "../../../core/region.hpp"
#include "../../../window/proxywindow.hpp" #include "../../../window/proxywindow.hpp"
#include "../../../window/windowinterface.hpp" #include "../../../window/windowinterface.hpp"
#include "../../util.hpp"
#include "manager.hpp" #include "manager.hpp"
#include "surface.hpp" #include "surface.hpp"
@ -40,6 +43,15 @@ HyprlandWindow::HyprlandWindow(ProxyWindowBase* window): QObject(nullptr), proxy
&HyprlandWindow::onWindowConnected &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); QObject::connect(window, &QObject::destroyed, this, &HyprlandWindow::onProxyWindowDestroyed);
if (window->backingWindow()) { if (window->backingWindow()) {
@ -60,14 +72,76 @@ void HyprlandWindow::setOpacity(qreal opacity) {
this->mOpacity = opacity; this->mOpacity = opacity;
if (this->surface) { if (this->surface && this->proxyWindow) {
this->surface->setOpacity(opacity); this->pendingPolish.opacity = true;
qs::wayland::util::scheduleCommit(this->proxyWindow); this->proxyWindow->schedulePolish();
} }
emit this->opacityChanged(); 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() { void HyprlandWindow::onWindowConnected() {
this->mWindow = this->proxyWindow->backingWindow(); this->mWindow = this->proxyWindow->backingWindow();
// disconnected by destructor // disconnected by destructor
@ -86,33 +160,46 @@ void HyprlandWindow::onWindowVisibleChanged() {
if (!this->mWindow->handle()) { if (!this->mWindow->handle()) {
this->mWindow->create(); 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) { if (this->mWaylandWindow) {
// disconnected by destructor QObject::disconnect(this->mWaylandWindow, nullptr, this, nullptr);
}
QObject::connect( this->mWaylandWindow = window;
this->mWaylandWindow, if (!window) return;
&QWaylandWindow::surfaceCreated,
this,
&HyprlandWindow::onWaylandSurfaceCreated
);
QObject::connect( QObject::connect(
this->mWaylandWindow, this->mWaylandWindow,
&QWaylandWindow::surfaceDestroyed, &QObject::destroyed,
this, this,
&HyprlandWindow::onWaylandSurfaceDestroyed &HyprlandWindow::onWaylandWindowDestroyed
); );
if (this->mWaylandWindow->surface()) { QObject::connect(
this->onWaylandSurfaceCreated(); 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() { void HyprlandWindow::onWaylandSurfaceCreated() {
auto* manager = impl::HyprlandSurfaceManager::instance(); auto* manager = impl::HyprlandSurfaceManager::instance();
@ -122,12 +209,26 @@ void HyprlandWindow::onWaylandSurfaceCreated() {
return; return;
} }
auto* ext = manager->createHyprlandExtension(this->mWaylandWindow); auto v = this->mWaylandWindow->property("hyprland_window_ext");
this->surface = std::unique_ptr<impl::HyprlandSurface>(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) { if (!this->surface) {
this->surface->setOpacity(this->mOpacity); auto* ext = manager->createHyprlandExtension(this->mWaylandWindow);
qs::wayland::util::scheduleCommit(this->proxyWindow); 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 // 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. // hyprland_surface_v1 and wl_surface objects.
this->proxyWindow = nullptr;
if (this->surface == nullptr) { if (this->surface == nullptr) {
this->proxyWindow = nullptr;
this->deleteLater(); this->deleteLater();
} }
} }

View file

@ -9,6 +9,7 @@
#include <qtypes.h> #include <qtypes.h>
#include <qwindow.h> #include <qwindow.h>
#include "../../../core/region.hpp"
#include "../../../window/proxywindow.hpp" #include "../../../window/proxywindow.hpp"
#include "surface.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 /// [hyprland-surface-v1]: https://github.com/hyprwm/hyprland-protocols/blob/main/protocols/hyprland-surface-v1.xml
class HyprlandWindow: public QObject { class HyprlandWindow: public QObject {
Q_OBJECT; 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 /// 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. /// both the window content *and* visual effects such as blur that apply to it.
/// ///
/// Default: 1.0 /// Default: 1.0
Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged); 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_ELEMENT;
QML_UNCREATABLE("HyprlandWindow can only be used as an attached object."); QML_UNCREATABLE("HyprlandWindow can only be used as an attached object.");
QML_ATTACHED(HyprlandWindow); QML_ATTACHED(HyprlandWindow);
@ -48,17 +56,25 @@ public:
[[nodiscard]] qreal opacity() const; [[nodiscard]] qreal opacity() const;
void setOpacity(qreal opacity); void setOpacity(qreal opacity);
[[nodiscard]] PendingRegion* visibleMask() const;
virtual void setVisibleMask(PendingRegion* mask);
static HyprlandWindow* qmlAttachedProperties(QObject* object); static HyprlandWindow* qmlAttachedProperties(QObject* object);
signals: signals:
void opacityChanged(); void opacityChanged();
void visibleMaskChanged();
private slots: private slots:
void onWindowConnected(); void onWindowConnected();
void onWindowVisibleChanged(); void onWindowVisibleChanged();
void onWaylandWindowDestroyed();
void onWaylandSurfaceCreated(); void onWaylandSurfaceCreated();
void onWaylandSurfaceDestroyed(); void onWaylandSurfaceDestroyed();
void onProxyWindowDestroyed(); void onProxyWindowDestroyed();
void onVisibleMaskDestroyed();
void onWindowPolished();
void updateVisibleMask();
private: private:
void disconnectWaylandWindow(); void disconnectWaylandWindow();
@ -67,7 +83,13 @@ private:
QWindow* mWindow = nullptr; QWindow* mWindow = nullptr;
QtWaylandClient::QWaylandWindow* mWaylandWindow = nullptr; QtWaylandClient::QWaylandWindow* mWaylandWindow = nullptr;
struct {
bool opacity : 1 = false;
bool visibleMask : 1 = false;
} pendingPolish;
qreal mOpacity = 1.0; qreal mOpacity = 1.0;
PendingRegion* mVisibleMask = nullptr;
std::unique_ptr<impl::HyprlandSurface> surface; std::unique_ptr<impl::HyprlandSurface> surface;
}; };

View file

@ -1,19 +1,53 @@
#include "surface.hpp" #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 <qtypes.h>
#include <qwayland-hyprland-surface-v1.h> #include <qwayland-hyprland-surface-v1.h>
#include <wayland-client-protocol.h>
#include <wayland-hyprland-surface-v1-client-protocol.h> #include <wayland-hyprland-surface-v1-client-protocol.h>
#include <wayland-util.h> #include <wayland-util.h>
namespace qs::hyprland::surface::impl { namespace qs::hyprland::surface::impl {
HyprlandSurface::HyprlandSurface(::hyprland_surface_v1* surface) HyprlandSurface::HyprlandSurface(
: QtWayland::hyprland_surface_v1(surface) {} ::hyprland_surface_v1* surface,
QtWaylandClient::QWaylandWindow* backer
)
: QtWayland::hyprland_surface_v1(surface)
, backer(backer)
, backerSurface(backer->surface()) {}
HyprlandSurface::~HyprlandSurface() { this->destroy(); } HyprlandSurface::~HyprlandSurface() { this->destroy(); }
bool HyprlandSurface::surfaceEq(wl_surface* surface) const {
return surface == this->backerSurface;
}
void HyprlandSurface::setOpacity(qreal opacity) { void HyprlandSurface::setOpacity(qreal opacity) {
this->set_opacity(wl_fixed_from_double(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 } // namespace qs::hyprland::surface::impl

View file

@ -1,21 +1,31 @@
#pragma once #pragma once
#include <private/qwaylandwindow_p.h>
#include <qobject.h> #include <qobject.h>
#include <qregion.h>
#include <qtclasshelpermacros.h> #include <qtclasshelpermacros.h>
#include <qtmetamacros.h> #include <qtmetamacros.h>
#include <qtypes.h> #include <qtypes.h>
#include <qwayland-hyprland-surface-v1.h> #include <qwayland-hyprland-surface-v1.h>
#include <wayland-client-protocol.h>
#include <wayland-hyprland-surface-v1-client-protocol.h> #include <wayland-hyprland-surface-v1-client-protocol.h>
namespace qs::hyprland::surface::impl { namespace qs::hyprland::surface::impl {
class HyprlandSurface: public QtWayland::hyprland_surface_v1 { class HyprlandSurface: public QtWayland::hyprland_surface_v1 {
public: public:
explicit HyprlandSurface(::hyprland_surface_v1* surface); explicit HyprlandSurface(::hyprland_surface_v1* surface, QtWaylandClient::QWaylandWindow* backer);
~HyprlandSurface() override; ~HyprlandSurface() override;
Q_DISABLE_COPY_MOVE(HyprlandSurface); Q_DISABLE_COPY_MOVE(HyprlandSurface);
[[nodiscard]] bool surfaceEq(wl_surface* surface) const;
void setOpacity(qreal opacity); void setOpacity(qreal opacity);
void setVisibleRegion(const QRegion& region);
private:
QtWaylandClient::QWaylandWindow* backer;
wl_surface* backerSurface = nullptr;
}; };
} // namespace qs::hyprland::surface::impl } // namespace qs::hyprland::surface::impl