From db95de4f5b4ce446984d873e5b51ebdc380dc76c Mon Sep 17 00:00:00 2001 From: Lucas Ritzdorf <42657792+LRitzdorf@users.noreply.github.com> Date: Wed, 5 Aug 2026 06:10:45 -0600 Subject: [PATCH] config/lua: clean up `HLMonitor:set_workspace()` and `set_special_workspace()` (#15534) * lua: make HLMonitor:set_workspace "silent" (no focus or mouse move) * lua: report correct function name when HLMonitor:set_special_workspace errors * monitor: allow setting special workspace on monitor without focusing that monitor * lua: make HLMonitor:set_special_workspace also "silent" * workspace: support not carrying focus with us in moveWorkspaceToMonitor This leaves focus on the same monitor, rather than carrying it along with the moved workspace as usual. * lua: fix HLMonitor:set_workspace placing the same WS on different monitors I thought this was already handled for me; apparently not. * lua: support creating new workspaces via HLMonitor:set_workspace(), set_special_workspace() * monitor: don't send workspace events on non-focusing workspace change * lua: better handle (non-)?special selectors in HLMonitor:set_workspace(), set_special_workspace() Both now check that they're given a workspace of the appropriate special-ness. * lua: don't require a table arg for HLMonitor:set_workspace(), set_special_workspace() Also, names given to `set_special_workspace()` no longer require a `special:` prefix, matching the toggle_special dispatcher's behavior. * misc: allow getting workspaces relative to a given monitor Not just the currently focused one. * lua: reference targeted monitor for relative WS selectors in set_workspace() Now, e.g. `m+1` selects the next workspace on the *targeted* monitor, rather than on the focused monitor. * tests: HLMonitor:set_workspace() behavior * tests: HLMonitor:set_special_workspace() behavior * tests: don't force unnecessary refreshes in luaGetWorkspace test * lua: return workspace name as selector for special workspaces Previously this would be the ID, which was negative and would then be interpreted later as a *relative* ID. Normal workspaces can still return their (positive) IDs, that's fine. * monitor: properly leave focus behind when stealing a special workspace --- hyprtester/src/tests/main/workspaces.cpp | 198 +++++++++++++++++- .../lua/bindings/LuaBindingsInternal.cpp | 2 + src/config/lua/objects/LuaMonitor.cpp | 40 ++-- src/helpers/MiscFunctions.cpp | 44 ++-- src/helpers/MiscFunctions.hpp | 3 +- src/output/Monitor.cpp | 33 ++- src/output/Monitor.hpp | 4 +- src/state/WorkspacePlacementController.cpp | 6 +- src/state/WorkspacePlacementController.hpp | 2 +- 9 files changed, 276 insertions(+), 56 deletions(-) diff --git a/hyprtester/src/tests/main/workspaces.cpp b/hyprtester/src/tests/main/workspaces.cpp index 8e5145689..9fa2a5453 100644 --- a/hyprtester/src/tests/main/workspaces.cpp +++ b/hyprtester/src/tests/main/workspaces.cpp @@ -994,8 +994,8 @@ TEST_CASE(luaGetWorkspace) { } Tests::spawnKitty(); - ASSERT(getFromSocket("r/repl hl.get_workspace('name:test')"), "HL.Workspace(-1337:test)"); - ASSERT(getFromSocket("r/repl hl.get_workspace('name:test') == hl.get_active_workspace()"), "true"); + ASSERT(getFromSocket("/repl hl.get_workspace('name:test')"), "HL.Workspace(-1337:test)"); + ASSERT(getFromSocket("/repl hl.get_workspace('name:test') == hl.get_active_workspace()"), "true"); OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 1})")); { @@ -1003,9 +1003,197 @@ TEST_CASE(luaGetWorkspace) { ASSERT_CONTAINS(str, "workspace ID 1 (1)"); } - ASSERT(getFromSocket("r/repl hl.get_workspace('e-1')"), "HL.Workspace(-1337:test)"); - ASSERT(getFromSocket("r/repl hl.get_workspace('r+1')"), "nil"); - ASSERT(getFromSocket("r/repl hl.get_workspace(42)"), "nil"); + ASSERT(getFromSocket("/repl hl.get_workspace('e-1')"), "HL.Workspace(-1337:test)"); + ASSERT(getFromSocket("/repl hl.get_workspace('r+1')"), "nil"); + ASSERT(getFromSocket("/repl hl.get_workspace(42)"), "nil"); +} + +SUBTEST(luaSetWorkspaceCreate) { + // set up monitor 2, the workspace donor for future tests + NLog::log("{}Creating four new workspaces on monitor 2", Colors::YELLOW); + + OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = M2 })")); + ASSERT_CONTAINS(getFromSocket("/activeworkspace"), "on monitor HEADLESS-3:"); + + OK(getFromSocket("/eval M2:set_workspace(100)")); + Tests::spawnKitty("ws100"); + OK(getFromSocket("/eval M2:set_workspace(101)")); + Tests::spawnKitty("ws101"); + + const auto workspaces = getFromSocket("/workspaces"); + ASSERT_CONTAINS(workspaces, "workspace ID 100 (100) on monitor HEADLESS-3:"); + ASSERT_CONTAINS(workspaces, "workspace ID 101 (101) on monitor HEADLESS-3:"); + + OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'HEADLESS-2' })")); + ASSERT_CONTAINS(getFromSocket("/activeworkspace"), "on monitor HEADLESS-2:"); +} + +SUBTEST(luaSetWorkspaceInactiveToFocused) { + NLog::log("{}Setting focused monitor 1 to a workspace currently inactive on monitor 2", Colors::YELLOW); + + // steal workspace 100 from monitor 2 + OK(getFromSocket("/eval M1:set_workspace(100)")); + ASSERT_CONTAINS(getFromSocket("/activewindow"), "class: ws100"); + ASSERT_CONTAINS(getFromSocket("/activeworkspace"), "workspace ID 100 (100) on monitor HEADLESS-2:"); + + // should leave workspace 101 active on monitor 2 + OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'HEADLESS-3' })")); + ASSERT_CONTAINS(getFromSocket("/activeworkspace"), "workspace ID 101 (101) on monitor HEADLESS-3:"); + + OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'HEADLESS-2' })")); +} + +SUBTEST(luaSetWorkspaceActiveToFocused) { + NLog::log("{}Setting focused monitor 1 to a workspace currently active on monitor 2", Colors::YELLOW); + + // steal workspace 101 from monitor 2 + OK(getFromSocket("/eval M1:set_workspace(101)")); + ASSERT_CONTAINS(getFromSocket("/activewindow"), "class: ws101"); + ASSERT_CONTAINS(getFromSocket("/activeworkspace"), "workspace ID 101 (101) on monitor HEADLESS-2:"); + + // should create workspace 2 on monitor 2 + OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'HEADLESS-3' })")); + ASSERT_CONTAINS(getFromSocket("/activeworkspace"), "workspace ID 2 (2) on monitor HEADLESS-3:"); + + OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'HEADLESS-2' })")); +} + +SUBTEST(luaSetWorkspaceInactiveToUnfocused) { + NLog::log("{}Setting unfocused monitor 2 to a workspace currently inactive on monitor 1", Colors::YELLOW); + + // steal workspace 100 from monitor 1 + OK(getFromSocket("/eval M2:set_workspace(100)")); + ASSERT_CONTAINS(getFromSocket("/activewindow"), "class: ws101"); + ASSERT_CONTAINS(getFromSocket("/activeworkspace"), "workspace ID 101 (101) on monitor HEADLESS-2:"); + + // should make workspace 100 active on monitor 2 + OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'HEADLESS-3' })")); + ASSERT_CONTAINS(getFromSocket("/activeworkspace"), "workspace ID 100 (100) on monitor HEADLESS-3:"); + + OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'HEADLESS-2' })")); +} + +SUBTEST(luaSetWorkspaceActiveToUnfocused) { + NLog::log("{}Setting unfocused monitor 2 to a workspace currently active on monitor 1", Colors::YELLOW); + + // steal workspace 101 from monitor 1 + OK(getFromSocket("/eval M2:set_workspace(101)")); + ASSERT_CONTAINS(getFromSocket("/activewindow"), "class: ws1"); + ASSERT_CONTAINS(getFromSocket("/activeworkspace"), "workspace ID 1 (1) on monitor HEADLESS-2:"); + + // should make workspace 101 active on monitor 2 + OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'HEADLESS-3' })")); + ASSERT_CONTAINS(getFromSocket("/activeworkspace"), "workspace ID 101 (101) on monitor HEADLESS-3:"); + + OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'HEADLESS-2' })")); +} + +SUBTEST(luaSetWorkspaceUnfocusedRelative) { + NLog::log("{}Setting unfocused monitor 2 via a relative workspace selector", Colors::YELLOW); + + // relative workspace selector should be relative to the targeted monitor + OK(getFromSocket("/eval M2:set_workspace('m-1')")); + + OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'HEADLESS-3' })")); + ASSERT_CONTAINS(getFromSocket("/activewindow"), "class: ws100"); + ASSERT_CONTAINS(getFromSocket("/activeworkspace"), "workspace ID 100 (100) on monitor HEADLESS-3:"); + + OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'HEADLESS-2' })")); +} + +TEST_CASE(luaSetWorkspace) { + // add a new monitor + NLog::log("{}Adding a new monitor", Colors::YELLOW); + ASSERT(getFromSocket("/output create headless HEADLESS-3"), "ok"); + + // should take workspace 2 + { + auto str = getFromSocket("/monitors"); + ASSERT_CONTAINS(str, "active workspace: 2 (2)"); + ASSERT_CONTAINS(str, "active workspace: 1 (1)"); + ASSERT_CONTAINS(str, "HEADLESS-3"); + } + + // plonk a recognizable window on the first monitor + OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'HEADLESS-2' })")); + Tests::spawnKitty("ws1"); + { + auto str = getFromSocket("/activewindow"); + ASSERT_CONTAINS(str, "monitor: 1"); + ASSERT_CONTAINS(str, "workspace: 1 (1)"); + } + + // for use in subtests + OK(getFromSocket("/eval M1 = hl.get_monitors()[1]")); + OK(getFromSocket("/eval M2 = hl.get_monitors()[2]")); + + // order very much matters for these + CALL_SUBTEST(luaSetWorkspaceCreate); + CALL_SUBTEST(luaSetWorkspaceInactiveToFocused); + CALL_SUBTEST(luaSetWorkspaceActiveToFocused); + CALL_SUBTEST(luaSetWorkspaceInactiveToUnfocused); + CALL_SUBTEST(luaSetWorkspaceActiveToUnfocused); + CALL_SUBTEST(luaSetWorkspaceUnfocusedRelative); + + // clean up + Tests::killAllWindows(); + OK(getFromSocket("/output remove HEADLESS-3")); +} + +TEST_CASE(luaSetSpecialWorkspace) { + // add a new monitor + NLog::log("{}Adding a new monitor", Colors::YELLOW); + ASSERT(getFromSocket("/output create headless HEADLESS-3"), "ok"); + + // should take workspace 2 + { + auto str = getFromSocket("/monitors"); + ASSERT_CONTAINS(str, "active workspace: 2 (2)"); + ASSERT_CONTAINS(str, "active workspace: 1 (1)"); + ASSERT_CONTAINS(str, "HEADLESS-3"); + } + + // for ease of access + OK(getFromSocket("/eval M1 = hl.get_monitors()[1]")); + OK(getFromSocket("/eval M2 = hl.get_monitors()[2]")); + + // special workspace with a window + NLog::log("{}Setting special workspace on active monitor", Colors::YELLOW); + OK(getFromSocket("/eval M1:set_special_workspace(1)")); + Tests::spawnKitty(); + { + auto str = getFromSocket("/activewindow"); + ASSERT_CONTAINS(str, "monitor: 1"); + ASSERT_CONTAINS(str, "workspace: -98 (special:1)"); + } + + // new special workspace on unfocused monitor + NLog::log("{}Setting special workspace on inactive monitor", Colors::YELLOW); + OK(getFromSocket("/eval M2:set_special_workspace(2)")); + ASSERT_CONTAINS(getFromSocket("/workspaces"), "workspace ID -97 (special:2) on monitor HEADLESS-3:"); + + // move focused special to unfocused monitor + NLog::log("{}Moving active special workspace to inactive monitor", Colors::YELLOW); + OK(getFromSocket("/eval M2:set_special_workspace(1)")); + ASSERT_CONTAINS(getFromSocket("/activeworkspace"), "workspace ID 1 (1) on monitor HEADLESS-2:"); + OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'HEADLESS-3' })")); + { + auto str = getFromSocket("/activewindow"); + ASSERT_CONTAINS(str, "monitor: 2"); + ASSERT_CONTAINS(str, "workspace: -98 (special:1)"); + } + OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'HEADLESS-2' })")); + + // unset special workspace on unfocused monitor + NLog::log("{}Clearing special workspace on inactive monitor", Colors::YELLOW); + OK(getFromSocket("/eval M2:set_special_workspace(nil)")); + OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'HEADLESS-3' })")); + ASSERT_CONTAINS(getFromSocket("/activeworkspace"), "workspace ID 2 (2) on monitor HEADLESS-3:"); + OK(getFromSocket("/dispatch hl.dsp.focus({ monitor = 'HEADLESS-2' })")); + + // clean up + Tests::killAllWindows(); + OK(getFromSocket("/output remove HEADLESS-3")); } TEST_CASE(workspacesDistinctTiledAndFloatGaps) { diff --git a/src/config/lua/bindings/LuaBindingsInternal.cpp b/src/config/lua/bindings/LuaBindingsInternal.cpp index a3a8d2cf3..8414ed6f6 100644 --- a/src/config/lua/bindings/LuaBindingsInternal.cpp +++ b/src/config/lua/bindings/LuaBindingsInternal.cpp @@ -198,6 +198,8 @@ std::optional Internal::workspaceSelectorFromLuaSelectorOrObject(lu return std::nullopt; } + if (ws->m_isSpecialWorkspace) + return ws->m_name; return std::to_string(ws->m_id); } diff --git a/src/config/lua/objects/LuaMonitor.cpp b/src/config/lua/objects/LuaMonitor.cpp index 68d4d777e..d9d6b4e19 100644 --- a/src/config/lua/objects/LuaMonitor.cpp +++ b/src/config/lua/objects/LuaMonitor.cpp @@ -6,6 +6,7 @@ #include "../bindings/LuaBindingsInternal.hpp" #include "../../../output/Monitor.hpp" #include "../../../desktop/state/FocusState.hpp" +#include "../../../state/WorkspacePlacementController.hpp" #include @@ -36,35 +37,48 @@ static int monitorToString(lua_State* L) { } static int monitorSetWorkspace(lua_State* L) { - auto* ref = sc(luaL_checkudata(L, 1, MT)); - const auto id = Internal::requireTableFieldWorkspaceSelector(L, 2, "workspace", "HLMonitor.set_workspace"); + auto* ref = sc(luaL_checkudata(L, 1, MT)); + const auto selector = Internal::workspaceSelectorFromLuaSelectorOrObject(L, 2, "HLMonitor.set_workspace"); - if (id.empty()) + if (!selector) return 0; - auto ws = State::workspaceState()->query().name(id).run(); + const auto& [id, name, _] = getWorkspaceIDNameFromString(*selector, ref->lock()); + if (id == WORKSPACE_INVALID || State::workspaceState()->isSpecial(id)) + return 0; + + auto ws = State::workspaceState()->query().id(id).run(); if (!ws) - return 0; + ws = State::workspaceState()->create(id, (*ref)->m_id, name); - (*ref)->changeWorkspace(ws->m_id); + State::workspacePlacementController()->moveWorkspaceToMonitor(ws, ref->lock(), true, false); + (*ref)->changeWorkspace(ws, false, true, Desktop::focusState()->monitor() != *ref); return 0; } static int monitorSetSpecialWorkspace(lua_State* L) { - auto* ref = sc(luaL_checkudata(L, 1, MT)); - const auto id = Internal::tableOptWorkspaceSelector(L, 2, "workspace", "HLMonitor.set_workspace"); + auto* ref = sc(luaL_checkudata(L, 1, MT)); + std::optional selector; + if (lua_isstring(L, 2) || lua_isnumber(L, 2)) + selector = std::format("special:{}", Internal::argStr(L, 2)); + else + selector = Internal::workspaceSelectorFromLuaSelectorOrObject(L, 2, "HLMonitor.set_special_workspace"); - if (!id) { - (*ref)->setSpecialWorkspace(WORKSPACE_INVALID); + if (!selector) { + (*ref)->setSpecialWorkspace(WORKSPACE_INVALID, true); return 0; } - auto ws = State::workspaceState()->query().name(*id).run(); - if (!ws) + const auto& [id, name, _] = getWorkspaceIDNameFromString(*selector, ref->lock()); + if (id == WORKSPACE_INVALID || !State::workspaceState()->isSpecial(id)) return 0; - (*ref)->setSpecialWorkspace(ws->m_id); + auto ws = State::workspaceState()->query().id(id).run(); + if (!ws) + ws = State::workspaceState()->create(id, (*ref)->m_id, name); + + (*ref)->setSpecialWorkspace(ws->m_id, true); return 0; } diff --git a/src/helpers/MiscFunctions.cpp b/src/helpers/MiscFunctions.cpp index 449e90a88..b1acabae9 100644 --- a/src/helpers/MiscFunctions.cpp +++ b/src/helpers/MiscFunctions.cpp @@ -122,8 +122,9 @@ static bool isAutoIDdWorkspace(WORKSPACEID id) { return id < WORKSPACE_INVALID; } -SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { - SWorkspaceIDName result = {WORKSPACE_INVALID, ""}; +SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in, std::optional baseMon) { + const auto BASEMONITOR = baseMon.value_or(Desktop::focusState()->monitor()); + SWorkspaceIDName result = {WORKSPACE_INVALID, ""}; if (in.starts_with("special")) { result.name = "special:special"; @@ -149,7 +150,7 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { } else if (in.starts_with("empty")) { const bool same_mon = in.substr(5).contains("m"); const bool next = in.substr(5).contains("n"); - if ((same_mon || next) && !Desktop::focusState()->monitor()) { + if ((same_mon || next) && !BASEMONITOR) { Log::logger->log(Log::ERR, "Empty monitor workspace on monitor null!"); return {WORKSPACE_INVALID}; } @@ -160,13 +161,13 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { if (!rule->isEnabled()) continue; - const auto PMONITOR = State::monitorState()->query().relativeTo(Desktop::focusState()->monitor()).configString(rule->m_monitor).run(); - if (PMONITOR && (PMONITOR->m_id != Desktop::focusState()->monitor()->m_id)) + const auto PMONITOR = State::monitorState()->query().relativeTo(BASEMONITOR).configString(rule->m_monitor).run(); + if (PMONITOR && (PMONITOR->m_id != BASEMONITOR->m_id)) invalidWSes.insert(rule->m_workspaceId); } } - WORKSPACEID id = next ? Desktop::focusState()->monitor()->activeWorkspaceID() : 0; + WORKSPACEID id = next ? BASEMONITOR->activeWorkspaceID() : 0; while (++id < LONG_MAX) { const auto PWORKSPACE = State::workspaceState()->query().id(id).run(); if (!invalidWSes.contains(id) && (!PWORKSPACE || PWORKSPACE->getWindowCount() == 0)) { @@ -175,11 +176,10 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { } } } else if (in.starts_with("prev")) { - auto monitor = Desktop::focusState()->monitor(); - if (!monitor) + if (!BASEMONITOR) return {WORKSPACE_INVALID}; - const auto PWORKSPACE = monitor->m_activeWorkspace; + const auto PWORKSPACE = BASEMONITOR->m_activeWorkspace; if (!valid(PWORKSPACE)) return {WORKSPACE_INVALID}; @@ -198,12 +198,12 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { return {PLASTWORKSPACE->m_id, PLASTWORKSPACE->m_name}; } else if (in == "next") { - if (!Desktop::focusState()->monitor() || !Desktop::focusState()->monitor()->m_activeWorkspace) { + if (!BASEMONITOR || !BASEMONITOR->m_activeWorkspace) { Log::logger->log(Log::ERR, "no active monitor or workspace for 'next'"); return {WORKSPACE_INVALID}; } - auto PCURRENTWORKSPACE = Desktop::focusState()->monitor()->m_activeWorkspace; + auto PCURRENTWORKSPACE = BASEMONITOR->m_activeWorkspace; WORKSPACEID nextId = PCURRENTWORKSPACE->m_id + 1; @@ -216,7 +216,7 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { } else { if (in[0] == 'r' && (in[1] == '-' || in[1] == '+' || in[1] == '~') && isNumber(in.substr(2))) { bool absolute = in[1] == '~'; - if (!Desktop::focusState()->monitor()) { + if (!BASEMONITOR) { Log::logger->log(Log::ERR, "Relative monitor workspace on monitor null!"); return {WORKSPACE_INVALID}; } @@ -234,7 +234,7 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { // Collect all the workspaces we can't jump to. for (auto const& ws : State::workspaceState()->workspaces()) { - if (ws->m_isSpecialWorkspace || (ws->m_monitor != Desktop::focusState()->monitor())) { + if (ws->m_isSpecialWorkspace || (ws->m_monitor != BASEMONITOR)) { // Can't jump to this workspace invalidWSes.insert(ws->m_id); } @@ -243,8 +243,8 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { if (!rule->isEnabled()) continue; - const auto PMONITOR = State::monitorState()->query().relativeTo(Desktop::focusState()->monitor()).configString(rule->m_monitor).run(); - if (!PMONITOR || PMONITOR->m_id == Desktop::focusState()->monitor()->m_id) { + const auto PMONITOR = State::monitorState()->query().relativeTo(BASEMONITOR).configString(rule->m_monitor).run(); + if (!PMONITOR || PMONITOR->m_id == BASEMONITOR->m_id) { // Can't be invalid continue; } @@ -255,7 +255,7 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { // Prepare all named workspaces in case when we need them std::vector namedWSes; for (auto const& ws : State::workspaceState()->workspaces()) { - if (ws->m_isSpecialWorkspace || (ws->m_monitor != Desktop::focusState()->monitor()) || ws->m_id >= 0) + if (ws->m_isSpecialWorkspace || (ws->m_monitor != BASEMONITOR) || ws->m_id >= 0) continue; namedWSes.push_back(ws->m_id); @@ -282,7 +282,7 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { } else { // Just take a blind guess at where we'll probably end up - WORKSPACEID activeWSID = Desktop::focusState()->monitor()->m_activeWorkspace ? Desktop::focusState()->monitor()->m_activeWorkspace->m_id : 1; + WORKSPACEID activeWSID = BASEMONITOR->m_activeWorkspace ? BASEMONITOR->m_activeWorkspace->m_id : 1; WORKSPACEID predictedWSID = activeWSID + remains; int remainingWSes = 0; char walkDir = in[1]; @@ -381,7 +381,7 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { bool onAllMonitors = in[0] == 'e'; bool absolute = in[1] == '~'; - if (!Desktop::focusState()->monitor()) { + if (!BASEMONITOR) { Log::logger->log(Log::ERR, "Relative monitor workspace on monitor null!"); return {WORKSPACE_INVALID}; } @@ -399,7 +399,7 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { std::vector validWSes; for (auto const& ws : State::workspaceState()->workspaces()) { - if (ws->m_isSpecialWorkspace || (ws->m_monitor != Desktop::focusState()->monitor() && !onAllMonitors)) + if (ws->m_isSpecialWorkspace || (ws->m_monitor != BASEMONITOR && !onAllMonitors)) continue; validWSes.push_back(ws->m_id); @@ -424,7 +424,7 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { remains = remains < 0 ? -((-remains) % validWSes.size()) : remains % validWSes.size(); // get the current item - WORKSPACEID activeWSID = Desktop::focusState()->monitor()->m_activeWorkspace ? Desktop::focusState()->monitor()->m_activeWorkspace->m_id : 1; + WORKSPACEID activeWSID = BASEMONITOR->m_activeWorkspace ? BASEMONITOR->m_activeWorkspace->m_id : 1; for (ssize_t i = 0; i < sc(validWSes.size()); i++) { if (validWSes[i] == activeWSID) { currentItem = i; @@ -447,8 +447,8 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { result.name = State::workspaceState()->query().id(validWSes[currentItem]).run()->m_name; } else { if (in[0] == '+' || in[0] == '-') { - if (Desktop::focusState()->monitor()) { - const auto PLUSMINUSRESULT = getPlusMinusKeywordResult(in, Desktop::focusState()->monitor()->activeWorkspaceID()); + if (BASEMONITOR) { + const auto PLUSMINUSRESULT = getPlusMinusKeywordResult(in, BASEMONITOR->activeWorkspaceID()); if (!PLUSMINUSRESULT.has_value()) return {WORKSPACE_INVALID}; diff --git a/src/helpers/MiscFunctions.hpp b/src/helpers/MiscFunctions.hpp index cca62a65a..712f31471 100644 --- a/src/helpers/MiscFunctions.hpp +++ b/src/helpers/MiscFunctions.hpp @@ -9,6 +9,7 @@ #include #include "../SharedDefs.hpp" #include "../macros.hpp" +#include "../desktop/DesktopTypes.hpp" struct SCallstackFrameInfo { void* adr = nullptr; @@ -24,7 +25,7 @@ struct SWorkspaceIDName { std::string absolutePath(const std::string&, const std::string&); std::string escapeJSONStrings(const std::string& str); bool isDirection(std::string_view); -SWorkspaceIDName getWorkspaceIDNameFromString(const std::string&); +SWorkspaceIDName getWorkspaceIDNameFromString(const std::string&, std::optional = std::nullopt); std::optional cleanCmdForWorkspace(const std::string&, std::string); float vecToRectDistanceSquared(const Vector2D& vec, const Vector2D& p1, const Vector2D& p2); std::string execAndGet(const char*); diff --git a/src/output/Monitor.cpp b/src/output/Monitor.cpp index 4cbe8a0a0..fc397a63b 100644 --- a/src/output/Monitor.cpp +++ b/src/output/Monitor.cpp @@ -1446,7 +1446,7 @@ void CMonitor::changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal, bo if (pWorkspace->m_isSpecialWorkspace) { if (m_activeSpecialWorkspace != pWorkspace) { Log::logger->log(Log::DEBUG, "changeworkspace on special, togglespecialworkspace to id {}", pWorkspace->m_id); - setSpecialWorkspace(pWorkspace); + setSpecialWorkspace(pWorkspace, noFocus); } return; } @@ -1505,9 +1505,11 @@ void CMonitor::changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal, bo g_layoutManager->recalculateMonitor(m_self.lock(), Layout::CLayoutManager::RECALCULATE_MONITOR_REASON_WORKSPACE_CHANGE); - IPC::Socket2::sock()->postEvent({"workspace", pWorkspace->m_name}); - IPC::Socket2::sock()->postEvent({"workspacev2", std::format("{},{}", pWorkspace->m_id, pWorkspace->m_name)}); - Event::bus()->m_events.workspace.active.emit(pWorkspace); + if (!noFocus) { + IPC::Socket2::sock()->postEvent({"workspace", pWorkspace->m_name}); + IPC::Socket2::sock()->postEvent({"workspacev2", std::format("{},{}", pWorkspace->m_id, pWorkspace->m_name)}); + Event::bus()->m_events.workspace.active.emit(pWorkspace); + } } // set all LSes as not above fullscreen on workspace changes @@ -1552,7 +1554,7 @@ void CMonitor::setSpecialWorkspaceVisualState(bool active) { *m_specialBlur = active && *PBLURSPECIAL && *PBLUR ? 1.F : 0.F; } -void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) { +void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace, bool noFocus) { if (m_activeSpecialWorkspace == pWorkspace) return; @@ -1583,7 +1585,8 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) { g_layoutManager->recalculateMonitor(m_self.lock(), Layout::CLayoutManager::RECALCULATE_MONITOR_REASON_TOGGLE_SPECIAL_WORKSPACE); - if (!(Desktop::focusState()->window() && Desktop::focusState()->window()->m_pinned && Desktop::focusState()->window()->m_monitor == m_self)) { + if (!(noFocus && Desktop::focusState()->monitor() != m_self) && + !(Desktop::focusState()->window() && Desktop::focusState()->window()->m_pinned && Desktop::focusState()->window()->m_monitor == m_self)) { if (const auto PLAST = m_activeWorkspace->getLastFocusedWindow(); PLAST) Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_TOGGLE_SPECIAL_WORKSPACE); else @@ -1616,6 +1619,15 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) { PMONITOR->m_activeSpecialWorkspace.reset(); g_layoutManager->recalculateMonitor(PMONITOR, Layout::CLayoutManager::RECALCULATE_MONITOR_REASON_TOGGLE_SPECIAL_WORKSPACE); g_pHyprRenderer->damageMonitor(PMONITOR); + if (noFocus && + (Desktop::focusState()->monitor() == PMONITOR && + !(Desktop::focusState()->window() && Desktop::focusState()->window()->m_pinned && Desktop::focusState()->window()->m_monitor == PMONITOR))) { + // leave focus behind + if (const auto PLAST = PMONITOR->m_activeWorkspace->getLastFocusedWindow(); PLAST) + Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_TOGGLE_SPECIAL_WORKSPACE); + else + g_pInputManager->refocus(); + } IPC::Socket2::sock()->postEvent({"activespecial", std::format(",{}", PMONITOR->m_name)}); IPC::Socket2::sock()->postEvent({"activespecialv2", std::format(",,{}", PMONITOR->m_name)}); @@ -1681,7 +1693,10 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) { g_layoutManager->recalculateMonitor(m_self.lock(), Layout::CLayoutManager::RECALCULATE_MONITOR_REASON_TOGGLE_SPECIAL_WORKSPACE); - if (!(Desktop::focusState()->window() && Desktop::focusState()->window()->m_pinned && Desktop::focusState()->window()->m_monitor == m_self)) { + if (!noFocus || + (Desktop::focusState()->monitor() == m_self && + !(Desktop::focusState()->window() && Desktop::focusState()->window()->m_pinned && Desktop::focusState()->window()->m_monitor == m_self))) { + // focus the workspace we just moved if (const auto PLAST = pWorkspace->getLastFocusedWindow(); PLAST) Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_TOGGLE_SPECIAL_WORKSPACE); else @@ -1703,8 +1718,8 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) { Event::bus()->m_events.workspace.specialActive.emit(pWorkspace, m_self.lock()); } -void CMonitor::setSpecialWorkspace(const WORKSPACEID& id) { - setSpecialWorkspace(State::workspaceState()->query().id(id).run()); +void CMonitor::setSpecialWorkspace(const WORKSPACEID& id, bool noFocus) { + setSpecialWorkspace(State::workspaceState()->query().id(id).run(), noFocus); } PHLWORKSPACE CMonitor::getCurrentWorkspace() { diff --git a/src/output/Monitor.hpp b/src/output/Monitor.hpp index c10f38c69..6c9521a61 100644 --- a/src/output/Monitor.hpp +++ b/src/output/Monitor.hpp @@ -271,8 +271,8 @@ namespace Monitor { float getDefaultScale(); void changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal = false, bool noMouseMove = false, bool noFocus = false); void changeWorkspace(const WORKSPACEID& id, bool internal = false, bool noMouseMove = false, bool noFocus = false); - void setSpecialWorkspace(const PHLWORKSPACE& pWorkspace); - void setSpecialWorkspace(const WORKSPACEID& id); + void setSpecialWorkspace(const PHLWORKSPACE& pWorkspace, bool noFocus = false); + void setSpecialWorkspace(const WORKSPACEID& id, bool noFocus = false); PHLWORKSPACE getCurrentWorkspace(); WORKSPACEID activeWorkspaceID(); WORKSPACEID activeSpecialWorkspaceID(); diff --git a/src/state/WorkspacePlacementController.cpp b/src/state/WorkspacePlacementController.cpp index 06912e6ca..dc6040cbb 100644 --- a/src/state/WorkspacePlacementController.cpp +++ b/src/state/WorkspacePlacementController.cpp @@ -236,7 +236,7 @@ void CWorkspacePlacementController::swapActiveWorkspaces(PHLMONITOR pMonitorA, P Event::bus()->m_events.workspace.moveToMonitor.emit(PWORKSPACEB, pMonitorA); } -void CWorkspacePlacementController::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMonitor, bool noWarpCursor) const { +void CWorkspacePlacementController::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspace, PHLMONITOR pMonitor, bool noWarpCursor, bool carryFocus) const { static auto PHIDESPECIALONWORKSPACECHANGE = CConfigValue("binds:hide_special_on_workspace_change"); if (!pWorkspace || !pMonitor) @@ -287,7 +287,7 @@ void CWorkspacePlacementController::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspa Log::logger->log(Log::DEBUG, "moveWorkspaceToMonitor: Plugging gap with existing {}", nextWorkspaceOnMonitorID); if (POLDMON) - POLDMON->changeWorkspace(nextWorkspaceOnMonitorID, false, true, true); + POLDMON->changeWorkspace(nextWorkspaceOnMonitorID, false, true, carryFocus || POLDMON != Desktop::focusState()->monitor()); } // move the workspace @@ -325,7 +325,7 @@ void CWorkspacePlacementController::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspa } } - if (SWITCHINGISACTIVE && POLDMON == Desktop::focusState()->monitor()) { // if it was active, preserve its' status. If it wasn't, don't. + if (carryFocus && SWITCHINGISACTIVE && POLDMON == Desktop::focusState()->monitor()) { // if it was active, preserve its' status. If it wasn't, don't. Log::logger->log(Log::DEBUG, "moveWorkspaceToMonitor: SWITCHINGISACTIVE, active {} -> {}", pMonitor->activeWorkspaceID(), pWorkspace->m_id); if (valid(pMonitor->m_activeWorkspace)) { diff --git a/src/state/WorkspacePlacementController.hpp b/src/state/WorkspacePlacementController.hpp index 9351a692e..5f83d2ca3 100644 --- a/src/state/WorkspacePlacementController.hpp +++ b/src/state/WorkspacePlacementController.hpp @@ -16,7 +16,7 @@ namespace State { void ensurePersistentWorkspacesPresent(PHLWORKSPACE pWorkspace, const FMoveWorkspace& moveWorkspace) const; void ensurePersistentWorkspacesPresent(const std::vector>& rules, PHLWORKSPACE pWorkspace, const FMoveWorkspace& moveWorkspace) const; void ensureWorkspacesOnAssignedMonitors(const FMoveWorkspace& moveWorkspace) const; - void moveWorkspaceToMonitor(PHLWORKSPACE, PHLMONITOR, bool noWarpCursor = false) const; + void moveWorkspaceToMonitor(PHLWORKSPACE, PHLMONITOR, bool noWarpCursor = false, bool carryFocus = true) const; void swapActiveWorkspaces(PHLMONITOR, PHLMONITOR) const; };