Skip to content

Commit b289bfa

Browse files
committed
hyprland/surface: add visibleMask
1 parent cdaff29 commit b289bfa

File tree

6 files changed

+230
-36
lines changed

6 files changed

+230
-36
lines changed

src/wayland/hyprland/surface/hyprland-surface-v1.xml

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
This protocol exposes hyprland-specific wl_surface properties.
3535
</description>
3636

37-
<interface name="hyprland_surface_manager_v1" version="1">
37+
<interface name="hyprland_surface_manager_v1" version="2">
3838
<description summary="manager for hyprland surface objects">
3939
This interface allows a client to create hyprland surface objects.
4040
</description>
@@ -63,7 +63,7 @@
6363
</enum>
6464
</interface>
6565

66-
<interface name="hyprland_surface_v1" version="1">
66+
<interface name="hyprland_surface_v1" version="2">
6767
<description summary="hyprland-specific wl_surface properties">
6868
This interface allows access to hyprland-specific properties of a wl_surface.
6969

@@ -96,5 +96,31 @@
9696
<entry name="no_surface" value="0" summary="wl_surface was destroyed"/>
9797
<entry name="out_of_range" value="1" summary="given opacity was not in the range 0.0 - 1.0 (inclusive)"/>
9898
</enum>
99+
100+
<request name="set_visible_region" since="2">
101+
<description summary="set the visible region of the surface">
102+
This request sets the region of the surface that contains visible content.
103+
Visible content refers to content that has an alpha value greater than zero.
104+
105+
The visible region is an optimization hint for the compositor that lets it
106+
avoid drawing parts of the surface that are not visible. Setting a visible region
107+
that does not contain all content in the surface may result in missing content
108+
not being drawn.
109+
110+
The visible region is specified in buffer-local coordinates.
111+
112+
The compositor ignores the parts of the visible region that fall outside of the surface.
113+
When all parts of the region fall outside of the buffer geometry, the compositor may
114+
avoid rendering the surface entirely.
115+
116+
The initial value for the visible region is empty. Setting the
117+
visible region has copy semantics, and the wl_region object can be destroyed immediately.
118+
A NULL wl_region causes the visible region to be set to empty.
119+
120+
Does not take effect until wl_surface.commit is called.
121+
</description>
122+
123+
<arg name="region" type="object" interface="wl_region" allow-null="true"/>
124+
</request>
99125
</interface>
100126
</protocol>

src/wayland/hyprland/surface/manager.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@
77

88
namespace qs::hyprland::surface::impl {
99

10-
HyprlandSurfaceManager::HyprlandSurfaceManager(): QWaylandClientExtensionTemplate(1) {
10+
HyprlandSurfaceManager::HyprlandSurfaceManager(): QWaylandClientExtensionTemplate(2) {
1111
this->initialize();
1212
}
1313

1414
HyprlandSurface*
1515
HyprlandSurfaceManager::createHyprlandExtension(QtWaylandClient::QWaylandWindow* surface) {
16-
return new HyprlandSurface(this->get_hyprland_surface(surface->surface()));
16+
return new HyprlandSurface(this->get_hyprland_surface(surface->surface()), surface);
1717
}
1818

1919
HyprlandSurfaceManager* HyprlandSurfaceManager::instance() {

src/wayland/hyprland/surface/qml.cpp

Lines changed: 131 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
#include "qml.hpp"
22
#include <memory>
33

4+
#include <private/qhighdpiscaling_p.h>
45
#include <private/qwaylandwindow_p.h>
56
#include <qlogging.h>
67
#include <qobject.h>
78
#include <qqmlinfo.h>
9+
#include <qregion.h>
810
#include <qtmetamacros.h>
911
#include <qtypes.h>
12+
#include <qvariant.h>
1013
#include <qwindow.h>
1114

15+
#include "../../../core/region.hpp"
1216
#include "../../../window/proxywindow.hpp"
1317
#include "../../../window/windowinterface.hpp"
14-
#include "../../util.hpp"
1518
#include "manager.hpp"
1619
#include "surface.hpp"
1720

@@ -40,6 +43,15 @@ HyprlandWindow::HyprlandWindow(ProxyWindowBase* window): QObject(nullptr), proxy
4043
&HyprlandWindow::onWindowConnected
4144
);
4245

46+
QObject::connect(window, &ProxyWindowBase::polished, this, &HyprlandWindow::onWindowPolished);
47+
48+
QObject::connect(
49+
window,
50+
&ProxyWindowBase::devicePixelRatioChanged,
51+
this,
52+
&HyprlandWindow::updateVisibleMask
53+
);
54+
4355
QObject::connect(window, &QObject::destroyed, this, &HyprlandWindow::onProxyWindowDestroyed);
4456

4557
if (window->backingWindow()) {
@@ -60,14 +72,76 @@ void HyprlandWindow::setOpacity(qreal opacity) {
6072

6173
this->mOpacity = opacity;
6274

63-
if (this->surface) {
64-
this->surface->setOpacity(opacity);
65-
qs::wayland::util::scheduleCommit(this->proxyWindow);
75+
if (this->surface && this->proxyWindow) {
76+
this->pendingPolish.opacity = true;
77+
this->proxyWindow->schedulePolish();
6678
}
6779

6880
emit this->opacityChanged();
6981
}
7082

83+
PendingRegion* HyprlandWindow::visibleMask() const { return this->mVisibleMask; }
84+
85+
void HyprlandWindow::setVisibleMask(PendingRegion* mask) {
86+
if (mask == this->mVisibleMask) return;
87+
88+
if (this->mVisibleMask) {
89+
QObject::disconnect(this->mVisibleMask, nullptr, this, nullptr);
90+
}
91+
92+
this->mVisibleMask = mask;
93+
94+
if (mask) {
95+
QObject::connect(mask, &QObject::destroyed, this, &HyprlandWindow::onVisibleMaskDestroyed);
96+
QObject::connect(mask, &PendingRegion::changed, this, &HyprlandWindow::updateVisibleMask);
97+
}
98+
99+
this->updateVisibleMask();
100+
emit this->visibleMaskChanged();
101+
}
102+
103+
void HyprlandWindow::onVisibleMaskDestroyed() {
104+
this->mVisibleMask = nullptr;
105+
this->updateVisibleMask();
106+
emit this->visibleMaskChanged();
107+
}
108+
109+
void HyprlandWindow::updateVisibleMask() {
110+
if (!this->surface || !this->proxyWindow) return;
111+
112+
this->pendingPolish.visibleMask = true;
113+
this->proxyWindow->schedulePolish();
114+
}
115+
116+
void HyprlandWindow::onWindowPolished() {
117+
if (!this->surface) return;
118+
119+
if (this->pendingPolish.opacity) {
120+
this->surface->setOpacity(this->mOpacity);
121+
this->pendingPolish.opacity = false;
122+
}
123+
124+
if (this->pendingPolish.visibleMask) {
125+
QRegion mask;
126+
if (this->mVisibleMask != nullptr) {
127+
mask =
128+
this->mVisibleMask->applyTo(QRect(0, 0, this->mWindow->width(), this->mWindow->height()));
129+
}
130+
131+
auto dpr = this->proxyWindow->devicePixelRatio();
132+
if (dpr != 1.0) {
133+
mask = QHighDpi::scale(mask, dpr);
134+
}
135+
136+
if (mask.isEmpty() && this->mVisibleMask) {
137+
mask = QRect(-1, -1, 1, 1);
138+
}
139+
140+
this->surface->setVisibleRegion(mask);
141+
this->pendingPolish.visibleMask = false;
142+
}
143+
}
144+
71145
void HyprlandWindow::onWindowConnected() {
72146
this->mWindow = this->proxyWindow->backingWindow();
73147
// disconnected by destructor
@@ -86,33 +160,46 @@ void HyprlandWindow::onWindowVisibleChanged() {
86160
if (!this->mWindow->handle()) {
87161
this->mWindow->create();
88162
}
163+
}
89164

90-
this->mWaylandWindow = dynamic_cast<QWaylandWindow*>(this->mWindow->handle());
165+
auto* window = dynamic_cast<QWaylandWindow*>(this->mWindow->handle());
166+
if (window == this->mWaylandWindow) return;
91167

92-
if (this->mWaylandWindow) {
93-
// disconnected by destructor
168+
if (this->mWaylandWindow) {
169+
QObject::disconnect(this->mWaylandWindow, nullptr, this, nullptr);
170+
}
94171

95-
QObject::connect(
96-
this->mWaylandWindow,
97-
&QWaylandWindow::surfaceCreated,
98-
this,
99-
&HyprlandWindow::onWaylandSurfaceCreated
100-
);
172+
this->mWaylandWindow = window;
173+
if (!window) return;
101174

102-
QObject::connect(
103-
this->mWaylandWindow,
104-
&QWaylandWindow::surfaceDestroyed,
105-
this,
106-
&HyprlandWindow::onWaylandSurfaceDestroyed
107-
);
175+
QObject::connect(
176+
this->mWaylandWindow,
177+
&QObject::destroyed,
178+
this,
179+
&HyprlandWindow::onWaylandWindowDestroyed
180+
);
108181

109-
if (this->mWaylandWindow->surface()) {
110-
this->onWaylandSurfaceCreated();
111-
}
112-
}
182+
QObject::connect(
183+
this->mWaylandWindow,
184+
&QWaylandWindow::surfaceCreated,
185+
this,
186+
&HyprlandWindow::onWaylandSurfaceCreated
187+
);
188+
189+
QObject::connect(
190+
this->mWaylandWindow,
191+
&QWaylandWindow::surfaceDestroyed,
192+
this,
193+
&HyprlandWindow::onWaylandSurfaceDestroyed
194+
);
195+
196+
if (this->mWaylandWindow->surface()) {
197+
this->onWaylandSurfaceCreated();
113198
}
114199
}
115200

201+
void HyprlandWindow::onWaylandWindowDestroyed() { this->mWaylandWindow = nullptr; }
202+
116203
void HyprlandWindow::onWaylandSurfaceCreated() {
117204
auto* manager = impl::HyprlandSurfaceManager::instance();
118205

@@ -122,12 +209,26 @@ void HyprlandWindow::onWaylandSurfaceCreated() {
122209
return;
123210
}
124211

125-
auto* ext = manager->createHyprlandExtension(this->mWaylandWindow);
126-
this->surface = std::unique_ptr<impl::HyprlandSurface>(ext);
212+
auto v = this->mWaylandWindow->property("hyprland_window_ext");
213+
if (v.canConvert<HyprlandWindow*>()) {
214+
auto* windowExt = v.value<HyprlandWindow*>();
215+
if (windowExt != this && windowExt->surface) {
216+
this->surface.swap(windowExt->surface);
217+
}
218+
}
219+
220+
if (!this->surface) {
221+
auto* ext = manager->createHyprlandExtension(this->mWaylandWindow);
222+
this->surface = std::unique_ptr<impl::HyprlandSurface>(ext);
223+
}
127224

128-
if (this->mOpacity != 1.0) {
129-
this->surface->setOpacity(this->mOpacity);
130-
qs::wayland::util::scheduleCommit(this->proxyWindow);
225+
this->mWaylandWindow->setProperty("hyprland_window_ext", QVariant::fromValue(this));
226+
227+
this->pendingPolish.opacity = this->mOpacity != 1.0;
228+
this->pendingPolish.visibleMask = this->mVisibleMask;
229+
230+
if (this->pendingPolish.opacity || this->pendingPolish.visibleMask) {
231+
this->proxyWindow->schedulePolish();
131232
}
132233
}
133234

@@ -144,8 +245,9 @@ void HyprlandWindow::onProxyWindowDestroyed() {
144245
// Deleting it when the proxy window is deleted will cause a full opacity frame between the destruction of the
145246
// hyprland_surface_v1 and wl_surface objects.
146247

248+
this->proxyWindow = nullptr;
249+
147250
if (this->surface == nullptr) {
148-
this->proxyWindow = nullptr;
149251
this->deleteLater();
150252
}
151253
}

src/wayland/hyprland/surface/qml.hpp

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include <qtypes.h>
1010
#include <qwindow.h>
1111

12+
#include "../../../core/region.hpp"
1213
#include "../../../window/proxywindow.hpp"
1314
#include "surface.hpp"
1415

@@ -31,11 +32,18 @@ namespace qs::hyprland::surface {
3132
/// [hyprland-surface-v1]: https://github.com/hyprwm/hyprland-protocols/blob/main/protocols/hyprland-surface-v1.xml
3233
class HyprlandWindow: public QObject {
3334
Q_OBJECT;
35+
// clang-format off
3436
/// A multiplier for the window's overall opacity, ranging from 1.0 to 0.0. Overall opacity includes the opacity of
3537
/// both the window content *and* visual effects such as blur that apply to it.
3638
///
3739
/// Default: 1.0
3840
Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged);
41+
/// A hint to the compositor that only certain regions of the surface should be rendered.
42+
/// This can be used to avoid rendering large empty regions of a window which can increase
43+
/// performance, especially if the window is blurred. The mask should include all pixels
44+
/// of the window that do not have an alpha value of 0.
45+
Q_PROPERTY(PendingRegion* visibleMask READ visibleMask WRITE setVisibleMask NOTIFY visibleMaskChanged);
46+
// clang-format on
3947
QML_ELEMENT;
4048
QML_UNCREATABLE("HyprlandWindow can only be used as an attached object.");
4149
QML_ATTACHED(HyprlandWindow);
@@ -48,17 +56,25 @@ class HyprlandWindow: public QObject {
4856
[[nodiscard]] qreal opacity() const;
4957
void setOpacity(qreal opacity);
5058

59+
[[nodiscard]] PendingRegion* visibleMask() const;
60+
virtual void setVisibleMask(PendingRegion* mask);
61+
5162
static HyprlandWindow* qmlAttachedProperties(QObject* object);
5263

5364
signals:
5465
void opacityChanged();
66+
void visibleMaskChanged();
5567

5668
private slots:
5769
void onWindowConnected();
5870
void onWindowVisibleChanged();
71+
void onWaylandWindowDestroyed();
5972
void onWaylandSurfaceCreated();
6073
void onWaylandSurfaceDestroyed();
6174
void onProxyWindowDestroyed();
75+
void onVisibleMaskDestroyed();
76+
void onWindowPolished();
77+
void updateVisibleMask();
6278

6379
private:
6480
void disconnectWaylandWindow();
@@ -67,7 +83,13 @@ private slots:
6783
QWindow* mWindow = nullptr;
6884
QtWaylandClient::QWaylandWindow* mWaylandWindow = nullptr;
6985

86+
struct {
87+
bool opacity : 1 = false;
88+
bool visibleMask : 1 = false;
89+
} pendingPolish;
90+
7091
qreal mOpacity = 1.0;
92+
PendingRegion* mVisibleMask = nullptr;
7193
std::unique_ptr<impl::HyprlandSurface> surface;
7294
};
7395

0 commit comments

Comments
 (0)