Skip to content

Commit 87a884c

Browse files
committed
hyprland/focus_grab: add HyprlandFocusGrab
1 parent e7cfb5c commit 87a884c

File tree

15 files changed

+607
-0
lines changed

15 files changed

+607
-0
lines changed

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ option(SOCKETS "Enable unix socket support" ON)
1414
option(WAYLAND "Enable wayland support" ON)
1515
option(WAYLAND_WLR_LAYERSHELL "Support the zwlr_layer_shell_v1 wayland protocol" ON)
1616
option(WAYLAND_SESSION_LOCK "Support the ext_session_lock_v1 wayland protocol" ON)
17+
option(HYPRLAND "Support hyprland specific features" ON)
1718
option(SERVICE_STATUS_NOTIFIER "StatusNotifierItem service" ON)
1819

1920
message(STATUS "Quickshell configuration")
@@ -27,6 +28,7 @@ if (WAYLAND)
2728
endif ()
2829
message(STATUS " Services")
2930
message(STATUS " StatusNotifier: ${SERVICE_STATUS_NOTIFIER}")
31+
message(STATUS " Hyprland: ${HYPRLAND}")
3032

3133
if (NOT DEFINED GIT_REVISION)
3234
execute_process(

src/core/proxywindow.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ void ProxyWindowBase::createWindow() {
8888
}
8989

9090
void ProxyWindowBase::deleteWindow() {
91+
if (this->window != nullptr) emit this->windowDestroyed();
9192
if (auto* window = this->disownWindow()) {
9293
if (auto* generation = EngineGeneration::findObjectGeneration(this)) {
9394
generation->deregisterIncubationController(window->incubationController());

src/core/proxywindow.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ class ProxyWindowBase: public Reloadable {
102102

103103
signals:
104104
void windowConnected();
105+
void windowDestroyed();
105106
void visibleChanged();
106107
void backerVisibilityChanged();
107108
void xChanged();

src/wayland/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,10 @@ if (WAYLAND_SESSION_LOCK)
6868
add_subdirectory(session_lock)
6969
endif()
7070

71+
if (HYPRLAND)
72+
add_subdirectory(hyprland)
73+
endif()
74+
7175
target_link_libraries(quickshell-wayland PRIVATE ${QT_DEPS})
7276
target_link_libraries(quickshell-wayland-init PRIVATE ${QT_DEPS})
7377

src/wayland/hyprland/CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
qt_add_library(quickshell-hyprland STATIC)
2+
qt_add_qml_module(quickshell-hyprland URI Quickshell.Hyprland VERSION 0.1)
3+
4+
add_subdirectory(focus_grab)
5+
6+
target_link_libraries(quickshell-hyprland PRIVATE ${QT_DEPS})
7+
8+
qs_pch(quickshell-hyprland)
9+
qs_pch(quickshell-hyprlandplugin)
10+
11+
target_link_libraries(quickshell PRIVATE quickshell-hyprlandplugin)
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
qt_add_library(quickshell-hyprland-focus-grab STATIC
2+
manager.cpp
3+
grab.cpp
4+
qml.cpp
5+
)
6+
7+
qt_add_qml_module(quickshell-hyprland-focus-grab URI Quickshell.Hyprland._FocusGrab VERSION 0.1)
8+
9+
add_library(quickshell-hyprland-focus-grab-init OBJECT init.cpp)
10+
11+
wl_proto(quickshell-hyprland-focus-grab hyprland-focus-grab-v1 "${CMAKE_CURRENT_SOURCE_DIR}/hyprland-focus-grab-v1.xml")
12+
target_link_libraries(quickshell-hyprland-focus-grab PRIVATE ${QT_DEPS} wayland-client)
13+
target_link_libraries(quickshell-hyprland-focus-grab-init PRIVATE ${QT_DEPS})
14+
15+
qs_pch(quickshell-hyprland-focus-grab)
16+
qs_pch(quickshell-hyprland-focus-grabplugin)
17+
qs_pch(quickshell-hyprland-focus-grab-init)
18+
19+
target_link_libraries(quickshell PRIVATE quickshell-hyprland-focus-grabplugin quickshell-hyprland-focus-grab-init)
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
#include "grab.hpp"
2+
3+
#include <private/qwaylandwindow_p.h>
4+
#include <qobject.h>
5+
#include <qtmetamacros.h>
6+
#include <qwindow.h>
7+
#include <wayland-hyprland-focus-grab-v1-client-protocol.h>
8+
9+
namespace qs::hyprland::focus_grab {
10+
11+
FocusGrab::FocusGrab(::hyprland_focus_grab_v1* grab) { this->init(grab); }
12+
13+
FocusGrab::~FocusGrab() {
14+
if (this->isInitialized()) {
15+
this->destroy();
16+
}
17+
}
18+
19+
bool FocusGrab::isActive() const { return this->active; }
20+
21+
void FocusGrab::addWindow(QWindow* window) {
22+
if (auto* waylandWindow = dynamic_cast<QWaylandWindow*>(window->handle())) {
23+
this->addWaylandWindow(waylandWindow);
24+
} else {
25+
QObject::connect(window, &QWindow::visibleChanged, this, [this, window]() {
26+
if (window->isVisible()) {
27+
if (window->handle() == nullptr) {
28+
window->create();
29+
}
30+
31+
auto* waylandWindow = dynamic_cast<QWaylandWindow*>(window->handle());
32+
this->addWaylandWindow(waylandWindow);
33+
this->sync();
34+
}
35+
});
36+
}
37+
}
38+
39+
void FocusGrab::removeWindow(QWindow* window) {
40+
QObject::disconnect(window, nullptr, this, nullptr);
41+
42+
if (auto* waylandWindow = dynamic_cast<QWaylandWindow*>(window->handle())) {
43+
this->pendingAdditions.removeAll(waylandWindow);
44+
this->remove_surface(waylandWindow->surface());
45+
this->commitRequired = true;
46+
}
47+
}
48+
49+
void FocusGrab::addWaylandWindow(QWaylandWindow* window) {
50+
this->add_surface(window->surface());
51+
this->pendingAdditions.append(window);
52+
this->commitRequired = true;
53+
}
54+
55+
void FocusGrab::sync() {
56+
if (this->commitRequired) {
57+
this->commit();
58+
this->commitRequired = false;
59+
60+
// the protocol will always send cleared() when the grab is deactivated,
61+
// even if it was due to window destruction, so we don't need to track it.
62+
if (!this->pendingAdditions.isEmpty()) {
63+
this->pendingAdditions.clear();
64+
65+
if (!this->active) {
66+
this->active = true;
67+
emit this->activated();
68+
}
69+
}
70+
}
71+
}
72+
73+
void FocusGrab::hyprland_focus_grab_v1_cleared() {
74+
this->active = false;
75+
emit this->cleared();
76+
}
77+
78+
} // namespace qs::hyprland::focus_grab
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
#pragma once
2+
3+
#include <private/qwaylandwindow_p.h>
4+
#include <qlist.h>
5+
#include <qobject.h>
6+
#include <qtclasshelpermacros.h>
7+
#include <qtmetamacros.h>
8+
#include <qwayland-hyprland-focus-grab-v1.h>
9+
#include <qwindow.h>
10+
#include <wayland-hyprland-focus-grab-v1-client-protocol.h>
11+
12+
namespace qs::hyprland::focus_grab {
13+
using HyprlandFocusGrab = QtWayland::hyprland_focus_grab_v1;
14+
15+
class FocusGrab
16+
: public QObject
17+
, public HyprlandFocusGrab {
18+
using QWaylandWindow = QtWaylandClient::QWaylandWindow;
19+
20+
Q_OBJECT;
21+
22+
public:
23+
explicit FocusGrab(::hyprland_focus_grab_v1* grab);
24+
~FocusGrab() override;
25+
Q_DISABLE_COPY_MOVE(FocusGrab);
26+
27+
[[nodiscard]] bool isActive() const;
28+
void addWindow(QWindow* window);
29+
void removeWindow(QWindow* window);
30+
void sync();
31+
32+
signals:
33+
void activated();
34+
void cleared();
35+
36+
private:
37+
void hyprland_focus_grab_v1_cleared() override;
38+
39+
void addWaylandWindow(QWaylandWindow* window);
40+
41+
QList<QWaylandWindow*> pendingAdditions;
42+
bool commitRequired = false;
43+
bool active = false;
44+
};
45+
46+
} // namespace qs::hyprland::focus_grab
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<protocol name="hyprland_focus_grab_v1">
3+
<copyright>
4+
Copyright © 2024 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="limit input focus to a set of surfaces">
34+
This protocol allows clients to limit input focus to a specific set
35+
of surfaces and receive a notification when the limiter is removed as
36+
detailed below.
37+
</description>
38+
39+
<interface name="hyprland_focus_grab_manager_v1" version="1">
40+
<description summary="manager for focus grab objects">
41+
This interface allows a client to create surface grab objects.
42+
</description>
43+
44+
<request name="create_grab">
45+
<description summary="create a focus grab object">
46+
Create a surface grab object.
47+
</description>
48+
49+
<arg name="grab" type="new_id" interface="hyprland_focus_grab_v1"/>
50+
</request>
51+
52+
<request name="destroy" type="destructor">
53+
<description summary="destroy the focus grab manager">
54+
Destroy the focus grab manager.
55+
This doesn't destroy existing focus grab objects.
56+
</description>
57+
</request>
58+
</interface>
59+
60+
<interface name="hyprland_focus_grab_v1" version="1">
61+
<description summary="input focus limiter">
62+
This interface restricts input focus to a specified whitelist of
63+
surfaces as long as the focus grab object exists and has at least
64+
one comitted surface.
65+
66+
Mouse and touch events inside a whitelisted surface will be passed
67+
to the surface normally, while events outside of a whitelisted surface
68+
will clear the grab object. Keyboard events will be passed to the client
69+
and a compositor-picked surface in the whitelist will receive a
70+
wl_keyboard::enter event if a whitelisted surface is not already entered.
71+
72+
Upon meeting implementation-defined criteria usually meaning a mouse or
73+
touch input outside of any whitelisted surfaces, the compositor will
74+
clear the whitelist, rendering the grab inert and sending the cleared
75+
event. The same will happen if another focus grab or similar action
76+
is started at the compositor's discretion.
77+
</description>
78+
79+
<request name="add_surface">
80+
<description summary="add a surface to the focus whitelist">
81+
Add a surface to the whitelist. Destroying the surface is treated the
82+
same as an explicit call to remove_surface and duplicate additions are
83+
ignored.
84+
85+
Does not take effect until commit is called.
86+
</description>
87+
88+
<arg name="surface" type="object" interface="wl_surface"/>
89+
</request>
90+
91+
<request name="remove_surface">
92+
<description summary="remove a surface from the focus whitelist">
93+
Remove a surface from the whitelist. Destroying the surface is treated
94+
the same as an explicit call to this function.
95+
96+
If the grab was active and the removed surface was entered by the
97+
keyboard, another surface will be entered on commit.
98+
99+
Does not take effect until commit is called.
100+
</description>
101+
102+
<arg name="surface" type="object" interface="wl_surface"/>
103+
</request>
104+
105+
<request name="commit">
106+
<description summary="commit the focus whitelist">
107+
Commit pending changes to the surface whitelist.
108+
109+
If the list previously had no entries and now has at least one, the grab
110+
will start. If it previously had entries and now has none, the grab will
111+
become inert.
112+
</description>
113+
</request>
114+
115+
<request name="destroy" type="destructor">
116+
<description summary="destroy the focus grab">
117+
Destroy the grab object and remove the grab if active.
118+
</description>
119+
</request>
120+
121+
<event name="cleared">
122+
<description summary="the focus grab was cleared">
123+
Sent when an active grab is cancelled by the compositor,
124+
regardless of cause.
125+
</description>
126+
</event>
127+
</interface>
128+
</protocol>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#include <qqml.h>
2+
3+
#include "../../../core/plugin.hpp"
4+
5+
namespace {
6+
7+
class HyprlandFocusGrabPlugin: public QuickshellPlugin {
8+
void registerTypes() override {
9+
qmlRegisterModuleImport(
10+
"Quickshell.Hyprland",
11+
QQmlModuleImportModuleAny,
12+
"Quickshell.Hyprland._FocusGrab",
13+
QQmlModuleImportLatest
14+
);
15+
}
16+
};
17+
18+
QS_REGISTER_PLUGIN(HyprlandFocusGrabPlugin);
19+
20+
} // namespace
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
#include "manager.hpp"
2+
3+
#include <qwaylandclientextension.h>
4+
5+
#include "grab.hpp"
6+
7+
namespace qs::hyprland::focus_grab {
8+
9+
FocusGrabManager::FocusGrabManager(): QWaylandClientExtensionTemplate<FocusGrabManager>(1) {
10+
this->initialize();
11+
}
12+
13+
bool FocusGrabManager::available() const { return this->isActive(); }
14+
15+
FocusGrab* FocusGrabManager::createGrab() { return new FocusGrab(this->create_grab()); }
16+
17+
FocusGrabManager* FocusGrabManager::instance() {
18+
static FocusGrabManager* instance = nullptr; // NOLINT
19+
20+
if (instance == nullptr) {
21+
instance = new FocusGrabManager();
22+
}
23+
24+
return instance;
25+
}
26+
27+
} // namespace qs::hyprland::focus_grab

0 commit comments

Comments
 (0)