Diff
1diff --git a/CHANGELOG.md b/CHANGELOG.md
2index de6d08ba34587089e5a6d2075651ade15b421403..a0d9b58ebc45c43a59c7493af677d607e7ca3ea6 100644
3--- a/CHANGELOG.md
4+++ b/CHANGELOG.md
5@@ -8,6 +8,7 @@
6 - Tab bars now use windowsMove for all animations
7 - Fixed tab bars rendering in the wrong workspace
8 - Floating windows will now be focused when all tiled windows are closed
9+- Added `hy3:togglefocuslayer`
10
11 # hl0.43.0 and before
12
13diff --git a/README.md b/README.md
14index 69e6f67d4b96c65efb7dd916314ca279d0b3570f..68062a554e800ce8474e182262d5fb2c2bfca9d6 100644
15--- a/README.md
16+++ b/README.md
17@@ -342,6 +342,8 @@ plugin {
18 - `lower` - lower focus one level
19 - `tab` - raise focus to the nearest tab
20 - `tabnode` - raise focus to the nearest node under the tab
21+ - `hy3:togglefocuslayer, [nowarp]` - toggle focus between tiled and floating layers
22+ - `nowarp` - do not warp the mouse to the newly focused window
23 - `hy3:focustab <mouse | [l | r | left | right | index, <index>], [prioritize_hovered | require_hovered], [wrap]>`
24 - `mouse` - focus the tab under the mouse, works well with a non consuming bind, e.g.
25 ```conf
26diff --git a/src/Hy3Layout.cpp b/src/Hy3Layout.cpp
27index f27ff3a6354aec7dc59950c5ad24bff8fe917956..7e65574341d4a970a7e154caace1ea114cc179fc 100644
28--- a/src/Hy3Layout.cpp
29+++ b/src/Hy3Layout.cpp
30@@ -620,36 +620,36 @@ PHLWINDOW Hy3Layout::getNextWindowCandidate(PHLWINDOW window) {
31
32 PHLWINDOW selection;
33
34- auto findFloating = [&]() {
35- // return the first floating window on the same workspace that has not asked not to be focused
36- for (auto& w: g_pCompositor->m_vWindows | std::views::reverse) {
37- if (w->m_bIsMapped && !w->isHidden() && w->m_bIsFloating && !w->isX11OverrideRedirect()
38- && w->m_pWorkspace == window->m_pWorkspace && !w->m_bX11ShouldntFocus
39- && !w->m_sWindowData.noFocus.valueOrDefault() && w != window)
40- {
41- return w;
42- }
43- }
44-
45- return PHLWINDOW();
46- };
47+ if (window->m_bIsFloating) {
48+ if (auto floating = this->findFloatingWindowCandidate(window)) return floating;
49+ return this->findTiledWindowCandidate(window);
50+ } else {
51+ if (auto tiled = this->findTiledWindowCandidate(window)) return tiled;
52+ return this->findFloatingWindowCandidate(window);
53+ }
54+}
55
56- auto findTiled = [&]() {
57- auto* node = this->getWorkspaceFocusedNode(window->m_pWorkspace, true);
58- if (node != nullptr && node->data.is_window()) {
59- return node->data.as_window();
60- }
61+PHLWINDOW Hy3Layout::findTiledWindowCandidate(const PHLWINDOW& from) {
62+ auto* node = this->getWorkspaceFocusedNode(from->m_pWorkspace, true);
63+ if (node != nullptr && node->data.is_window()) {
64+ return node->data.as_window();
65+ }
66
67- return PHLWINDOW();
68- };
69+ return PHLWINDOW();
70+}
71
72- if (window->m_bIsFloating) {
73- if (auto floating = findFloating()) return floating;
74- return findTiled();
75- } else {
76- if (auto tiled = findTiled()) return tiled;
77- return findFloating();
78+PHLWINDOW Hy3Layout::findFloatingWindowCandidate(const PHLWINDOW& from) {
79+ // return the first floating window on the same workspace that has not asked not to be focused
80+ for (auto& w: g_pCompositor->m_vWindows | std::views::reverse) {
81+ if (w->m_bIsMapped && !w->isHidden() && w->m_bIsFloating && !w->isX11OverrideRedirect()
82+ && w->m_pWorkspace == from->m_pWorkspace && !w->m_bX11ShouldntFocus
83+ && !w->m_sWindowData.noFocus.valueOrDefault() && w != from)
84+ {
85+ return w;
86+ }
87 }
88+
89+ return nullptr;
90 }
91
92 void Hy3Layout::replaceWindowDataWith(PHLWINDOW from, PHLWINDOW to) {
93@@ -930,6 +930,26 @@ void Hy3Layout::shiftFocus(
94 }
95 }
96
97+void Hy3Layout::toggleFocusLayer(const PHLWORKSPACE& workspace, bool warp) {
98+ auto current_window = g_pCompositor->m_pLastWindow.lock();
99+ if (!current_window) return;
100+
101+ PHLWINDOW target;
102+ if (current_window->m_bIsFloating) {
103+ target = this->findTiledWindowCandidate(current_window);
104+ } else {
105+ target = this->findFloatingWindowCandidate(current_window);
106+ }
107+
108+ if (!target) return;
109+
110+ g_pCompositor->focusWindow(target);
111+
112+ if (warp) {
113+ g_pCompositor->warpCursorTo(target->middle());
114+ }
115+}
116+
117 void Hy3Layout::warpCursor() {
118 auto current_window = g_pCompositor->m_pLastWindow.lock();
119
120diff --git a/src/Hy3Layout.hpp b/src/Hy3Layout.hpp
121index 61d965d98177aca026ab0a2dd1edf8ba009121bc..af57092e1f8990b8d64cd3fb7219745fa4aea0ac 100644
122--- a/src/Hy3Layout.hpp
123+++ b/src/Hy3Layout.hpp
124@@ -113,6 +113,7 @@ public:
125 void shiftNode(Hy3Node&, ShiftDirection, bool once, bool visible);
126 void shiftWindow(const PHLWORKSPACE& workspace, ShiftDirection, bool once, bool visible);
127 void shiftFocus(const PHLWORKSPACE& workspace, ShiftDirection, bool visible, bool warp);
128+ void toggleFocusLayer(const PHLWORKSPACE& workspace, bool warp);
129 void warpCursor();
130 void moveNodeToWorkspace(const PHLWORKSPACE& origin, std::string wsname, bool follow);
131 void changeFocus(const PHLWORKSPACE& workspace, FocusShift);
132@@ -129,6 +130,8 @@ public:
133 static void warpCursorToBox(const Vector2D& pos, const Vector2D& size);
134
135 bool shouldRenderSelected(const PHLWINDOW&);
136+ PHLWINDOW findTiledWindowCandidate(const PHLWINDOW& from);
137+ PHLWINDOW findFloatingWindowCandidate(const PHLWINDOW& from);
138
139 Hy3Node* getWorkspaceRootGroup(const PHLWORKSPACE& workspace);
140 Hy3Node* getWorkspaceFocusedNode(
141diff --git a/src/dispatchers.cpp b/src/dispatchers.cpp
142index e4b59c38d94b3ca8eb63a119604feda554b44d14..ba152a7c0c51328ccec426da5ed3b3b312e9deaf 100644
143--- a/src/dispatchers.cpp
144+++ b/src/dispatchers.cpp
145@@ -130,6 +130,13 @@ void dispatch_movefocus(std::string value) {
146 g_Hy3Layout->shiftFocus(workspace, shift.value(), visible, warp_cursor);
147 }
148
149+void dispatch_togglefocuslayer(std::string value) {
150+ auto workspace = workspace_for_action();
151+ if (!valid(workspace)) return;
152+
153+ g_Hy3Layout->toggleFocusLayer(workspace, value != "nowarp");
154+}
155+
156 void dispatch_warpcursor(std::string value) { g_Hy3Layout->warpCursor(); }
157
158 void dispatch_move_to_workspace(std::string value) {
159@@ -262,6 +269,7 @@ void registerDispatchers() {
160 HyprlandAPI::addDispatcher(PHANDLE, "hy3:changegroup", dispatch_changegroup);
161 HyprlandAPI::addDispatcher(PHANDLE, "hy3:setephemeral", dispatch_setephemeral);
162 HyprlandAPI::addDispatcher(PHANDLE, "hy3:movefocus", dispatch_movefocus);
163+ HyprlandAPI::addDispatcher(PHANDLE, "hy3:togglefocuslayer", dispatch_togglefocuslayer);
164 HyprlandAPI::addDispatcher(PHANDLE, "hy3:warpcursor", dispatch_warpcursor);
165 HyprlandAPI::addDispatcher(PHANDLE, "hy3:movewindow", dispatch_movewindow);
166 HyprlandAPI::addDispatcher(PHANDLE, "hy3:movetoworkspace", dispatch_move_to_workspace);