Skip to content

Commit 3a40174

Browse files
committed
hyprland/surface: add hyprland surface opacity support
1 parent 08836ca commit 3a40174

File tree

14 files changed

+476
-2
lines changed

14 files changed

+476
-2
lines changed

CMakeLists.txt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,10 @@ boption(HYPRLAND " Hyprland" ON REQUIRES WAYLAND)
5555
boption(HYPRLAND_IPC " Hyprland IPC" ON REQUIRES HYPRLAND)
5656
boption(HYPRLAND_GLOBAL_SHORTCUTS " Hyprland Global Shortcuts" ON REQUIRES HYPRLAND)
5757
boption(HYPRLAND_FOCUS_GRAB " Hyprland Focus Grabbing" ON REQUIRES HYPRLAND)
58-
boption(I3 " I3/Sway" ON)
59-
boption(I3_IPC " I3/Sway IPC" ON REQUIRES I3)
58+
boption(HYPRLAND_SURFACE_EXTENSIONS " Hyprland Surface Extensions" ON REQUIRES HYPRLAND)
6059
boption(X11 "X11" ON)
60+
boption(I3 "I3/Sway" ON)
61+
boption(I3_IPC " I3/Sway IPC" ON REQUIRES I3)
6162
boption(SERVICE_STATUS_NOTIFIER "System Tray" ON)
6263
boption(SERVICE_PIPEWIRE "PipeWire" ON)
6364
boption(SERVICE_MPRIS "Mpris" ON)

src/wayland/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ qt_add_library(quickshell-wayland STATIC
7777
platformmenu.cpp
7878
popupanchor.cpp
7979
xdgshell.cpp
80+
util.cpp
8081
)
8182

8283
# required to make sure the constructor is linked

src/wayland/hyprland/CMakeLists.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,11 @@ if (HYPRLAND_GLOBAL_SHORTCUTS)
1919
list(APPEND HYPRLAND_MODULES Quickshell.Hyprland._GlobalShortcuts)
2020
endif()
2121

22+
if (HYPRLAND_SURFACE_EXTENSIONS)
23+
add_subdirectory(surface)
24+
list(APPEND HYPRLAND_MODULES Quickshell.Hyprland._SurfaceExtensions)
25+
endif()
26+
2227
qt_add_qml_module(quickshell-hyprland
2328
URI Quickshell.Hyprland
2429
VERSION 0.1

src/wayland/hyprland/module.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ headers = [
77
"ipc/qml.hpp",
88
"focus_grab/qml.hpp",
99
"global_shortcuts/qml.hpp",
10+
"surface/qml.hpp",
1011
]
1112
-----
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
qt_add_library(quickshell-hyprland-surface-extensions STATIC
2+
qml.cpp
3+
manager.cpp
4+
surface.cpp
5+
)
6+
7+
qt_add_qml_module(quickshell-hyprland-surface-extensions
8+
URI Quickshell.Hyprland._SurfaceExtensions
9+
VERSION 0.1
10+
DEPENDENCIES QtQml
11+
)
12+
13+
install_qml_module(quickshell-hyprland-surface-extensions)
14+
15+
wl_proto(quickshell-hyprland-surface-extensions
16+
hyprland-surface-v1
17+
"${CMAKE_CURRENT_SOURCE_DIR}/hyprland-surface-v1.xml"
18+
)
19+
20+
target_link_libraries(quickshell-hyprland-surface-extensions PRIVATE
21+
Qt::Quick Qt::WaylandClient Qt::WaylandClientPrivate wayland-client
22+
)
23+
24+
qs_module_pch(quickshell-hyprland-surface-extensions)
25+
26+
target_link_libraries(quickshell PRIVATE quickshell-hyprland-surface-extensionsplugin)
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<protocol name="hyprland_surface_v1">
3+
<copyright>
4+
Copyright © 2025 outfoxxed
5+
All rights reserved.
6+
7+
Redistribution and use in source and binary forms, with or without
8+
modification, are permitted provided that the following conditions are met:
9+
10+
1. Redistributions of source code must retain the above copyright notice, this
11+
list of conditions and the following disclaimer.
12+
13+
2. Redistributions in binary form must reproduce the above copyright notice,
14+
this list of conditions and the following disclaimer in the documentation
15+
and/or other materials provided with the distribution.
16+
17+
3. Neither the name of the copyright holder nor the names of its
18+
contributors may be used to endorse or promote products derived from
19+
this software without specific prior written permission.
20+
21+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
25+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
27+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
29+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31+
</copyright>
32+
33+
<description summary="hyprland-specific wl_surface extensions">
34+
This protocol exposes hyprland-specific wl_surface properties.
35+
</description>
36+
37+
<interface name="hyprland_surface_manager_v1" version="1">
38+
<description summary="manager for hyprland surface objects">
39+
This interface allows a client to create hyprland surface objects.
40+
</description>
41+
42+
<request name="get_hyprland_surface">
43+
<description summary="create a hyprland surface object">
44+
Create a hyprland surface object for the given wayland surface.
45+
46+
If the wl_surface already has an associated hyprland_surface_v1 object,
47+
even from a different manager, creation is a protocol error.
48+
</description>
49+
50+
<arg name="id" type="new_id" interface="hyprland_surface_v1"/>
51+
<arg name="surface" type="object" interface="wl_surface"/>
52+
</request>
53+
54+
<request name="destroy" type="destructor">
55+
<description summary="destroy the hyprland surface manager">
56+
Destroy the surface manager.
57+
This does not destroy existing surface objects.
58+
</description>
59+
</request>
60+
61+
<enum name="error">
62+
<entry name="already_constructed" value="0" summary="wl_surface already has a hyprland surface object"/>
63+
</enum>
64+
</interface>
65+
66+
<interface name="hyprland_surface_v1" version="1">
67+
<description summary="hyprland-specific wl_surface properties">
68+
This interface allows access to hyprland-specific properties of a wl_surface.
69+
70+
Once the wl_surface has been destroyed, the hyprland surface object must be
71+
destroyed as well. All other operations are a protocol error.
72+
</description>
73+
74+
<request name="set_opacity">
75+
<description summary="set the overall opacity of the surface">
76+
Sets a multiplier for the overall opacity of the surface.
77+
This multiplier applies to visual effects such as blur behind the surface
78+
in addition to the surface's content.
79+
80+
The default value is 1.0.
81+
Setting a value outside of the range 0.0 - 1.0 (inclusive) is a protocol error.
82+
Does not take effect until wl_surface.commit is called.
83+
</description>
84+
85+
<arg name="opacity" type="fixed"/>
86+
</request>
87+
88+
<request name="destroy" type="destructor">
89+
<description summary="destroy the hyprland surface interface">
90+
Destroy the hyprland surface object, resetting properties provided
91+
by this interface to their default values on the next wl_surface.commit.
92+
</description>
93+
</request>
94+
95+
<enum name="error">
96+
<entry name="no_surface" value="0" summary="wl_surface was destroyed"/>
97+
<entry name="out_of_range" value="1" summary="given opacity was not in the range 0.0 - 1.0 (inclusive)"/>
98+
</enum>
99+
</interface>
100+
</protocol>
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
#include "manager.hpp"
2+
3+
#include <private/qwaylandwindow_p.h>
4+
#include <qwaylandclientextension.h>
5+
6+
#include "surface.hpp"
7+
8+
namespace qs::hyprland::surface::impl {
9+
10+
HyprlandSurfaceManager::HyprlandSurfaceManager(): QWaylandClientExtensionTemplate(1) {
11+
this->initialize();
12+
}
13+
14+
HyprlandSurface*
15+
HyprlandSurfaceManager::createHyprlandExtension(QtWaylandClient::QWaylandWindow* surface) {
16+
return new HyprlandSurface(this->get_hyprland_surface(surface->surface()));
17+
}
18+
19+
HyprlandSurfaceManager* HyprlandSurfaceManager::instance() {
20+
static auto* instance = new HyprlandSurfaceManager();
21+
return instance;
22+
}
23+
24+
} // namespace qs::hyprland::surface::impl
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#pragma once
2+
3+
#include <private/qwaylandwindow_p.h>
4+
#include <qwayland-hyprland-surface-v1.h>
5+
#include <qwaylandclientextension.h>
6+
7+
#include "surface.hpp"
8+
9+
namespace qs::hyprland::surface::impl {
10+
11+
class HyprlandSurfaceManager
12+
: public QWaylandClientExtensionTemplate<HyprlandSurfaceManager>
13+
, public QtWayland::hyprland_surface_manager_v1 {
14+
public:
15+
explicit HyprlandSurfaceManager();
16+
17+
HyprlandSurface* createHyprlandExtension(QtWaylandClient::QWaylandWindow* surface);
18+
19+
static HyprlandSurfaceManager* instance();
20+
};
21+
22+
} // namespace qs::hyprland::surface::impl

src/wayland/hyprland/surface/qml.cpp

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
#include "qml.hpp"
2+
#include <memory>
3+
4+
#include <private/qwaylandwindow_p.h>
5+
#include <qlogging.h>
6+
#include <qobject.h>
7+
#include <qqmlinfo.h>
8+
#include <qtmetamacros.h>
9+
#include <qtypes.h>
10+
#include <qwindow.h>
11+
12+
#include "../../../window/proxywindow.hpp"
13+
#include "../../../window/windowinterface.hpp"
14+
#include "../../util.hpp"
15+
#include "manager.hpp"
16+
#include "surface.hpp"
17+
18+
using QtWaylandClient::QWaylandWindow;
19+
20+
namespace qs::hyprland::surface {
21+
22+
HyprlandWindow* HyprlandWindow::qmlAttachedProperties(QObject* object) {
23+
auto* proxyWindow = qobject_cast<ProxyWindowBase*>(object);
24+
25+
if (!proxyWindow) {
26+
if (auto* iface = qobject_cast<WindowInterface*>(object)) {
27+
proxyWindow = iface->proxyWindow();
28+
}
29+
}
30+
31+
qDebug() << "hlwindow for" << proxyWindow;
32+
if (!proxyWindow) return nullptr;
33+
return new HyprlandWindow(proxyWindow);
34+
}
35+
36+
HyprlandWindow::HyprlandWindow(ProxyWindowBase* window): QObject(nullptr), proxyWindow(window) {
37+
QObject::connect(
38+
window,
39+
&ProxyWindowBase::windowConnected,
40+
this,
41+
&HyprlandWindow::onWindowConnected
42+
);
43+
44+
QObject::connect(window, &QObject::destroyed, this, &HyprlandWindow::onProxyWindowDestroyed);
45+
46+
if (window->backingWindow()) {
47+
this->onWindowConnected();
48+
}
49+
}
50+
51+
qreal HyprlandWindow::opacity() const { return this->mOpacity; }
52+
53+
void HyprlandWindow::setOpacity(qreal opacity) {
54+
if (opacity == this->mOpacity) return;
55+
56+
if (opacity < 0.0 || opacity > 1.0) {
57+
qmlWarning(this
58+
) << "Cannot set HyprlandWindow.opacity to a value larger than 1.0 or smaller than 0.0";
59+
return;
60+
}
61+
62+
this->mOpacity = opacity;
63+
64+
if (this->surface) {
65+
this->surface->setOpacity(opacity);
66+
qs::wayland::util::scheduleCommit(this->mWaylandWindow);
67+
}
68+
69+
emit this->opacityChanged();
70+
}
71+
72+
void HyprlandWindow::onWindowConnected() {
73+
this->mWindow = this->proxyWindow->backingWindow();
74+
// disconnected by destructor
75+
QObject::connect(
76+
this->mWindow,
77+
&QWindow::visibleChanged,
78+
this,
79+
&HyprlandWindow::onWindowVisibleChanged
80+
);
81+
82+
this->onWindowVisibleChanged();
83+
}
84+
85+
void HyprlandWindow::onWindowVisibleChanged() {
86+
if (this->mWindow->isVisible()) {
87+
if (!this->mWindow->handle()) {
88+
this->mWindow->create();
89+
}
90+
91+
this->mWaylandWindow = dynamic_cast<QWaylandWindow*>(this->mWindow->handle());
92+
93+
if (this->mWaylandWindow) {
94+
// disconnected by destructor
95+
96+
QObject::connect(
97+
this->mWaylandWindow,
98+
&QWaylandWindow::surfaceCreated,
99+
this,
100+
&HyprlandWindow::onWaylandSurfaceCreated
101+
);
102+
103+
QObject::connect(
104+
this->mWaylandWindow,
105+
&QWaylandWindow::surfaceDestroyed,
106+
this,
107+
&HyprlandWindow::onWaylandSurfaceDestroyed
108+
);
109+
110+
if (this->mWaylandWindow->surface()) {
111+
this->onWaylandSurfaceCreated();
112+
}
113+
}
114+
}
115+
}
116+
117+
void HyprlandWindow::onWaylandSurfaceCreated() {
118+
auto* manager = impl::HyprlandSurfaceManager::instance();
119+
120+
if (!manager->isActive()) {
121+
qWarning() << "The active compositor does not support the hyprland_surface_v1 protocol. "
122+
"HyprlandWindow will not work.";
123+
return;
124+
}
125+
126+
auto* ext = manager->createHyprlandExtension(this->mWaylandWindow);
127+
this->surface = std::unique_ptr<impl::HyprlandSurface>(ext);
128+
129+
if (this->mOpacity != 1.0) {
130+
this->surface->setOpacity(this->mOpacity);
131+
qs::wayland::util::scheduleCommit(this->mWaylandWindow);
132+
}
133+
}
134+
135+
void HyprlandWindow::onWaylandSurfaceDestroyed() {
136+
this->surface = nullptr;
137+
138+
if (!this->proxyWindow) {
139+
this->deleteLater();
140+
}
141+
}
142+
143+
void HyprlandWindow::onProxyWindowDestroyed() {
144+
// Don't delete the HyprlandWindow, and therefore the impl::HyprlandSurface until the wl_surface is destroyed.
145+
// Deleting it when the proxy window is deleted will cause a full opacity frame between the destruction of the
146+
// hyprland_surface_v1 and wl_surface objects.
147+
148+
if (this->surface == nullptr) {
149+
this->proxyWindow = nullptr;
150+
this->deleteLater();
151+
}
152+
}
153+
154+
} // namespace qs::hyprland::surface

0 commit comments

Comments
 (0)