Skip to content

Commit

Permalink
hyprland/surface: add visibleMask
Browse files Browse the repository at this point in the history
  • Loading branch information
outfoxxed committed Jan 23, 2025
1 parent cdaff29 commit b289bfa
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 36 deletions.
30 changes: 28 additions & 2 deletions src/wayland/hyprland/surface/hyprland-surface-v1.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -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>
4 changes: 2 additions & 2 deletions src/wayland/hyprland/surface/manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
160 changes: 131 additions & 29 deletions src/wayland/hyprland/surface/qml.cpp
Original file line number Diff line number Diff line change
@@ -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"

Expand Down Expand Up @@ -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()) {
Expand All @@ -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
Expand All @@ -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();

Expand All @@ -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->surface) {
auto* ext = manager->createHyprlandExtension(this->mWaylandWindow);
this->surface = std::unique_ptr<impl::HyprlandSurface>(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();
}
}

Expand All @@ -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();
}
}
Expand Down
22 changes: 22 additions & 0 deletions src/wayland/hyprland/surface/qml.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <qtypes.h>
#include <qwindow.h>

#include "../../../core/region.hpp"
#include "../../../window/proxywindow.hpp"
#include "surface.hpp"

Expand All @@ -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);
Expand All @@ -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();
Expand All @@ -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<impl::HyprlandSurface> surface;
};

Expand Down
Loading

0 comments on commit b289bfa

Please sign in to comment.