3
0
mirror of https://github.com/hyprwm/Hyprland.git synced 2026-08-18 11:02:10 +00:00

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
This commit is contained in:
Lucas Ritzdorf
2026-08-05 06:10:45 -06:00
committed by GitHub
parent 5aacb62147
commit db95de4f5b
9 changed files with 276 additions and 56 deletions

View File

@ -994,8 +994,8 @@ TEST_CASE(luaGetWorkspace) {
} }
Tests::spawnKitty(); Tests::spawnKitty();
ASSERT(getFromSocket("r/repl hl.get_workspace('name:test')"), "HL.Workspace(-1337:test)"); ASSERT(getFromSocket("/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.get_active_workspace()"), "true");
OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 1})")); OK(getFromSocket("/dispatch hl.dsp.focus({ workspace = 1})"));
{ {
@ -1003,9 +1003,197 @@ TEST_CASE(luaGetWorkspace) {
ASSERT_CONTAINS(str, "workspace ID 1 (1)"); ASSERT_CONTAINS(str, "workspace ID 1 (1)");
} }
ASSERT(getFromSocket("r/repl hl.get_workspace('e-1')"), "HL.Workspace(-1337:test)"); ASSERT(getFromSocket("/repl hl.get_workspace('e-1')"), "HL.Workspace(-1337:test)");
ASSERT(getFromSocket("r/repl hl.get_workspace('r+1')"), "nil"); ASSERT(getFromSocket("/repl hl.get_workspace('r+1')"), "nil");
ASSERT(getFromSocket("r/repl hl.get_workspace(42)"), "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) { TEST_CASE(workspacesDistinctTiledAndFloatGaps) {

View File

@ -198,6 +198,8 @@ std::optional<std::string> Internal::workspaceSelectorFromLuaSelectorOrObject(lu
return std::nullopt; return std::nullopt;
} }
if (ws->m_isSpecialWorkspace)
return ws->m_name;
return std::to_string(ws->m_id); return std::to_string(ws->m_id);
} }

View File

@ -6,6 +6,7 @@
#include "../bindings/LuaBindingsInternal.hpp" #include "../bindings/LuaBindingsInternal.hpp"
#include "../../../output/Monitor.hpp" #include "../../../output/Monitor.hpp"
#include "../../../desktop/state/FocusState.hpp" #include "../../../desktop/state/FocusState.hpp"
#include "../../../state/WorkspacePlacementController.hpp"
#include <string_view> #include <string_view>
@ -36,35 +37,48 @@ static int monitorToString(lua_State* L) {
} }
static int monitorSetWorkspace(lua_State* L) { static int monitorSetWorkspace(lua_State* L) {
auto* ref = sc<PHLMONITORREF*>(luaL_checkudata(L, 1, MT)); auto* ref = sc<PHLMONITORREF*>(luaL_checkudata(L, 1, MT));
const auto id = Internal::requireTableFieldWorkspaceSelector(L, 2, "workspace", "HLMonitor.set_workspace"); const auto selector = Internal::workspaceSelectorFromLuaSelectorOrObject(L, 2, "HLMonitor.set_workspace");
if (id.empty()) if (!selector)
return 0; 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) 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; return 0;
} }
static int monitorSetSpecialWorkspace(lua_State* L) { static int monitorSetSpecialWorkspace(lua_State* L) {
auto* ref = sc<PHLMONITORREF*>(luaL_checkudata(L, 1, MT)); auto* ref = sc<PHLMONITORREF*>(luaL_checkudata(L, 1, MT));
const auto id = Internal::tableOptWorkspaceSelector(L, 2, "workspace", "HLMonitor.set_workspace"); std::optional<std::string> 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) { if (!selector) {
(*ref)->setSpecialWorkspace(WORKSPACE_INVALID); (*ref)->setSpecialWorkspace(WORKSPACE_INVALID, true);
return 0; return 0;
} }
auto ws = State::workspaceState()->query().name(*id).run(); const auto& [id, name, _] = getWorkspaceIDNameFromString(*selector, ref->lock());
if (!ws) if (id == WORKSPACE_INVALID || !State::workspaceState()->isSpecial(id))
return 0; 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; return 0;
} }

View File

@ -122,8 +122,9 @@ static bool isAutoIDdWorkspace(WORKSPACEID id) {
return id < WORKSPACE_INVALID; return id < WORKSPACE_INVALID;
} }
SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) { SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in, std::optional<PHLMONITOR> baseMon) {
SWorkspaceIDName result = {WORKSPACE_INVALID, ""}; const auto BASEMONITOR = baseMon.value_or(Desktop::focusState()->monitor());
SWorkspaceIDName result = {WORKSPACE_INVALID, ""};
if (in.starts_with("special")) { if (in.starts_with("special")) {
result.name = "special:special"; result.name = "special:special";
@ -149,7 +150,7 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) {
} else if (in.starts_with("empty")) { } else if (in.starts_with("empty")) {
const bool same_mon = in.substr(5).contains("m"); const bool same_mon = in.substr(5).contains("m");
const bool next = in.substr(5).contains("n"); 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!"); Log::logger->log(Log::ERR, "Empty monitor workspace on monitor null!");
return {WORKSPACE_INVALID}; return {WORKSPACE_INVALID};
} }
@ -160,13 +161,13 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) {
if (!rule->isEnabled()) if (!rule->isEnabled())
continue; continue;
const auto PMONITOR = State::monitorState()->query().relativeTo(Desktop::focusState()->monitor()).configString(rule->m_monitor).run(); const auto PMONITOR = State::monitorState()->query().relativeTo(BASEMONITOR).configString(rule->m_monitor).run();
if (PMONITOR && (PMONITOR->m_id != Desktop::focusState()->monitor()->m_id)) if (PMONITOR && (PMONITOR->m_id != BASEMONITOR->m_id))
invalidWSes.insert(rule->m_workspaceId); invalidWSes.insert(rule->m_workspaceId);
} }
} }
WORKSPACEID id = next ? Desktop::focusState()->monitor()->activeWorkspaceID() : 0; WORKSPACEID id = next ? BASEMONITOR->activeWorkspaceID() : 0;
while (++id < LONG_MAX) { while (++id < LONG_MAX) {
const auto PWORKSPACE = State::workspaceState()->query().id(id).run(); const auto PWORKSPACE = State::workspaceState()->query().id(id).run();
if (!invalidWSes.contains(id) && (!PWORKSPACE || PWORKSPACE->getWindowCount() == 0)) { if (!invalidWSes.contains(id) && (!PWORKSPACE || PWORKSPACE->getWindowCount() == 0)) {
@ -175,11 +176,10 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) {
} }
} }
} else if (in.starts_with("prev")) { } else if (in.starts_with("prev")) {
auto monitor = Desktop::focusState()->monitor(); if (!BASEMONITOR)
if (!monitor)
return {WORKSPACE_INVALID}; return {WORKSPACE_INVALID};
const auto PWORKSPACE = monitor->m_activeWorkspace; const auto PWORKSPACE = BASEMONITOR->m_activeWorkspace;
if (!valid(PWORKSPACE)) if (!valid(PWORKSPACE))
return {WORKSPACE_INVALID}; return {WORKSPACE_INVALID};
@ -198,12 +198,12 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) {
return {PLASTWORKSPACE->m_id, PLASTWORKSPACE->m_name}; return {PLASTWORKSPACE->m_id, PLASTWORKSPACE->m_name};
} else if (in == "next") { } 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'"); Log::logger->log(Log::ERR, "no active monitor or workspace for 'next'");
return {WORKSPACE_INVALID}; return {WORKSPACE_INVALID};
} }
auto PCURRENTWORKSPACE = Desktop::focusState()->monitor()->m_activeWorkspace; auto PCURRENTWORKSPACE = BASEMONITOR->m_activeWorkspace;
WORKSPACEID nextId = PCURRENTWORKSPACE->m_id + 1; WORKSPACEID nextId = PCURRENTWORKSPACE->m_id + 1;
@ -216,7 +216,7 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) {
} else { } else {
if (in[0] == 'r' && (in[1] == '-' || in[1] == '+' || in[1] == '~') && isNumber(in.substr(2))) { if (in[0] == 'r' && (in[1] == '-' || in[1] == '+' || in[1] == '~') && isNumber(in.substr(2))) {
bool absolute = in[1] == '~'; bool absolute = in[1] == '~';
if (!Desktop::focusState()->monitor()) { if (!BASEMONITOR) {
Log::logger->log(Log::ERR, "Relative monitor workspace on monitor null!"); Log::logger->log(Log::ERR, "Relative monitor workspace on monitor null!");
return {WORKSPACE_INVALID}; return {WORKSPACE_INVALID};
} }
@ -234,7 +234,7 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) {
// Collect all the workspaces we can't jump to. // Collect all the workspaces we can't jump to.
for (auto const& ws : State::workspaceState()->workspaces()) { 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 // Can't jump to this workspace
invalidWSes.insert(ws->m_id); invalidWSes.insert(ws->m_id);
} }
@ -243,8 +243,8 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) {
if (!rule->isEnabled()) if (!rule->isEnabled())
continue; continue;
const auto PMONITOR = State::monitorState()->query().relativeTo(Desktop::focusState()->monitor()).configString(rule->m_monitor).run(); const auto PMONITOR = State::monitorState()->query().relativeTo(BASEMONITOR).configString(rule->m_monitor).run();
if (!PMONITOR || PMONITOR->m_id == Desktop::focusState()->monitor()->m_id) { if (!PMONITOR || PMONITOR->m_id == BASEMONITOR->m_id) {
// Can't be invalid // Can't be invalid
continue; continue;
} }
@ -255,7 +255,7 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) {
// Prepare all named workspaces in case when we need them // Prepare all named workspaces in case when we need them
std::vector<WORKSPACEID> namedWSes; std::vector<WORKSPACEID> namedWSes;
for (auto const& ws : State::workspaceState()->workspaces()) { 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; continue;
namedWSes.push_back(ws->m_id); namedWSes.push_back(ws->m_id);
@ -282,7 +282,7 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) {
} else { } else {
// Just take a blind guess at where we'll probably end up // 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; WORKSPACEID predictedWSID = activeWSID + remains;
int remainingWSes = 0; int remainingWSes = 0;
char walkDir = in[1]; char walkDir = in[1];
@ -381,7 +381,7 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) {
bool onAllMonitors = in[0] == 'e'; bool onAllMonitors = in[0] == 'e';
bool absolute = in[1] == '~'; bool absolute = in[1] == '~';
if (!Desktop::focusState()->monitor()) { if (!BASEMONITOR) {
Log::logger->log(Log::ERR, "Relative monitor workspace on monitor null!"); Log::logger->log(Log::ERR, "Relative monitor workspace on monitor null!");
return {WORKSPACE_INVALID}; return {WORKSPACE_INVALID};
} }
@ -399,7 +399,7 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) {
std::vector<WORKSPACEID> validWSes; std::vector<WORKSPACEID> validWSes;
for (auto const& ws : State::workspaceState()->workspaces()) { 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; continue;
validWSes.push_back(ws->m_id); 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(); remains = remains < 0 ? -((-remains) % validWSes.size()) : remains % validWSes.size();
// get the current item // 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<ssize_t>(validWSes.size()); i++) { for (ssize_t i = 0; i < sc<ssize_t>(validWSes.size()); i++) {
if (validWSes[i] == activeWSID) { if (validWSes[i] == activeWSID) {
currentItem = i; currentItem = i;
@ -447,8 +447,8 @@ SWorkspaceIDName getWorkspaceIDNameFromString(const std::string& in) {
result.name = State::workspaceState()->query().id(validWSes[currentItem]).run()->m_name; result.name = State::workspaceState()->query().id(validWSes[currentItem]).run()->m_name;
} else { } else {
if (in[0] == '+' || in[0] == '-') { if (in[0] == '+' || in[0] == '-') {
if (Desktop::focusState()->monitor()) { if (BASEMONITOR) {
const auto PLUSMINUSRESULT = getPlusMinusKeywordResult(in, Desktop::focusState()->monitor()->activeWorkspaceID()); const auto PLUSMINUSRESULT = getPlusMinusKeywordResult(in, BASEMONITOR->activeWorkspaceID());
if (!PLUSMINUSRESULT.has_value()) if (!PLUSMINUSRESULT.has_value())
return {WORKSPACE_INVALID}; return {WORKSPACE_INVALID};

View File

@ -9,6 +9,7 @@
#include <hyprutils/os/FileDescriptor.hpp> #include <hyprutils/os/FileDescriptor.hpp>
#include "../SharedDefs.hpp" #include "../SharedDefs.hpp"
#include "../macros.hpp" #include "../macros.hpp"
#include "../desktop/DesktopTypes.hpp"
struct SCallstackFrameInfo { struct SCallstackFrameInfo {
void* adr = nullptr; void* adr = nullptr;
@ -24,7 +25,7 @@ struct SWorkspaceIDName {
std::string absolutePath(const std::string&, const std::string&); std::string absolutePath(const std::string&, const std::string&);
std::string escapeJSONStrings(const std::string& str); std::string escapeJSONStrings(const std::string& str);
bool isDirection(std::string_view); bool isDirection(std::string_view);
SWorkspaceIDName getWorkspaceIDNameFromString(const std::string&); SWorkspaceIDName getWorkspaceIDNameFromString(const std::string&, std::optional<PHLMONITOR> = std::nullopt);
std::optional<std::string> cleanCmdForWorkspace(const std::string&, std::string); std::optional<std::string> cleanCmdForWorkspace(const std::string&, std::string);
float vecToRectDistanceSquared(const Vector2D& vec, const Vector2D& p1, const Vector2D& p2); float vecToRectDistanceSquared(const Vector2D& vec, const Vector2D& p1, const Vector2D& p2);
std::string execAndGet(const char*); std::string execAndGet(const char*);

View File

@ -1446,7 +1446,7 @@ void CMonitor::changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal, bo
if (pWorkspace->m_isSpecialWorkspace) { if (pWorkspace->m_isSpecialWorkspace) {
if (m_activeSpecialWorkspace != pWorkspace) { if (m_activeSpecialWorkspace != pWorkspace) {
Log::logger->log(Log::DEBUG, "changeworkspace on special, togglespecialworkspace to id {}", pWorkspace->m_id); Log::logger->log(Log::DEBUG, "changeworkspace on special, togglespecialworkspace to id {}", pWorkspace->m_id);
setSpecialWorkspace(pWorkspace); setSpecialWorkspace(pWorkspace, noFocus);
} }
return; 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); g_layoutManager->recalculateMonitor(m_self.lock(), Layout::CLayoutManager::RECALCULATE_MONITOR_REASON_WORKSPACE_CHANGE);
IPC::Socket2::sock()->postEvent({"workspace", pWorkspace->m_name}); if (!noFocus) {
IPC::Socket2::sock()->postEvent({"workspacev2", std::format("{},{}", pWorkspace->m_id, pWorkspace->m_name)}); IPC::Socket2::sock()->postEvent({"workspace", pWorkspace->m_name});
Event::bus()->m_events.workspace.active.emit(pWorkspace); 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 // 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; *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) if (m_activeSpecialWorkspace == pWorkspace)
return; 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); 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) if (const auto PLAST = m_activeWorkspace->getLastFocusedWindow(); PLAST)
Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_TOGGLE_SPECIAL_WORKSPACE); Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_TOGGLE_SPECIAL_WORKSPACE);
else else
@ -1616,6 +1619,15 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) {
PMONITOR->m_activeSpecialWorkspace.reset(); PMONITOR->m_activeSpecialWorkspace.reset();
g_layoutManager->recalculateMonitor(PMONITOR, Layout::CLayoutManager::RECALCULATE_MONITOR_REASON_TOGGLE_SPECIAL_WORKSPACE); g_layoutManager->recalculateMonitor(PMONITOR, Layout::CLayoutManager::RECALCULATE_MONITOR_REASON_TOGGLE_SPECIAL_WORKSPACE);
g_pHyprRenderer->damageMonitor(PMONITOR); 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({"activespecial", std::format(",{}", PMONITOR->m_name)});
IPC::Socket2::sock()->postEvent({"activespecialv2", 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); 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) if (const auto PLAST = pWorkspace->getLastFocusedWindow(); PLAST)
Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_TOGGLE_SPECIAL_WORKSPACE); Desktop::focusState()->fullWindowFocus(PLAST, Desktop::FOCUS_REASON_TOGGLE_SPECIAL_WORKSPACE);
else else
@ -1703,8 +1718,8 @@ void CMonitor::setSpecialWorkspace(const PHLWORKSPACE& pWorkspace) {
Event::bus()->m_events.workspace.specialActive.emit(pWorkspace, m_self.lock()); Event::bus()->m_events.workspace.specialActive.emit(pWorkspace, m_self.lock());
} }
void CMonitor::setSpecialWorkspace(const WORKSPACEID& id) { void CMonitor::setSpecialWorkspace(const WORKSPACEID& id, bool noFocus) {
setSpecialWorkspace(State::workspaceState()->query().id(id).run()); setSpecialWorkspace(State::workspaceState()->query().id(id).run(), noFocus);
} }
PHLWORKSPACE CMonitor::getCurrentWorkspace() { PHLWORKSPACE CMonitor::getCurrentWorkspace() {

View File

@ -271,8 +271,8 @@ namespace Monitor {
float getDefaultScale(); float getDefaultScale();
void changeWorkspace(const PHLWORKSPACE& pWorkspace, bool internal = false, bool noMouseMove = false, bool noFocus = false); 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 changeWorkspace(const WORKSPACEID& id, bool internal = false, bool noMouseMove = false, bool noFocus = false);
void setSpecialWorkspace(const PHLWORKSPACE& pWorkspace); void setSpecialWorkspace(const PHLWORKSPACE& pWorkspace, bool noFocus = false);
void setSpecialWorkspace(const WORKSPACEID& id); void setSpecialWorkspace(const WORKSPACEID& id, bool noFocus = false);
PHLWORKSPACE getCurrentWorkspace(); PHLWORKSPACE getCurrentWorkspace();
WORKSPACEID activeWorkspaceID(); WORKSPACEID activeWorkspaceID();
WORKSPACEID activeSpecialWorkspaceID(); WORKSPACEID activeSpecialWorkspaceID();

View File

@ -236,7 +236,7 @@ void CWorkspacePlacementController::swapActiveWorkspaces(PHLMONITOR pMonitorA, P
Event::bus()->m_events.workspace.moveToMonitor.emit(PWORKSPACEB, pMonitorA); 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<Config::INTEGER>("binds:hide_special_on_workspace_change"); static auto PHIDESPECIALONWORKSPACECHANGE = CConfigValue<Config::INTEGER>("binds:hide_special_on_workspace_change");
if (!pWorkspace || !pMonitor) if (!pWorkspace || !pMonitor)
@ -287,7 +287,7 @@ void CWorkspacePlacementController::moveWorkspaceToMonitor(PHLWORKSPACE pWorkspa
Log::logger->log(Log::DEBUG, "moveWorkspaceToMonitor: Plugging gap with existing {}", nextWorkspaceOnMonitorID); Log::logger->log(Log::DEBUG, "moveWorkspaceToMonitor: Plugging gap with existing {}", nextWorkspaceOnMonitorID);
if (POLDMON) if (POLDMON)
POLDMON->changeWorkspace(nextWorkspaceOnMonitorID, false, true, true); POLDMON->changeWorkspace(nextWorkspaceOnMonitorID, false, true, carryFocus || POLDMON != Desktop::focusState()->monitor());
} }
// move the workspace // 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); Log::logger->log(Log::DEBUG, "moveWorkspaceToMonitor: SWITCHINGISACTIVE, active {} -> {}", pMonitor->activeWorkspaceID(), pWorkspace->m_id);
if (valid(pMonitor->m_activeWorkspace)) { if (valid(pMonitor->m_activeWorkspace)) {

View File

@ -16,7 +16,7 @@ namespace State {
void ensurePersistentWorkspacesPresent(PHLWORKSPACE pWorkspace, const FMoveWorkspace& moveWorkspace) const; void ensurePersistentWorkspacesPresent(PHLWORKSPACE pWorkspace, const FMoveWorkspace& moveWorkspace) const;
void ensurePersistentWorkspacesPresent(const std::vector<SP<Config::CWorkspaceRule>>& rules, PHLWORKSPACE pWorkspace, const FMoveWorkspace& moveWorkspace) const; void ensurePersistentWorkspacesPresent(const std::vector<SP<Config::CWorkspaceRule>>& rules, PHLWORKSPACE pWorkspace, const FMoveWorkspace& moveWorkspace) const;
void ensureWorkspacesOnAssignedMonitors(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; void swapActiveWorkspaces(PHLMONITOR, PHLMONITOR) const;
}; };