diff --git a/hyprtester/src/tests/main/workspaces.cpp b/hyprtester/src/tests/main/workspaces.cpp index 122cd6195..f7c948f26 100644 --- a/hyprtester/src/tests/main/workspaces.cpp +++ b/hyprtester/src/tests/main/workspaces.cpp @@ -193,6 +193,35 @@ static bool testAsymmetricGaps() { return true; } +static void testWorkspaceHistoryMultiMon() { + NLog::log("{}Testing multimon workspace history tracker", Colors::YELLOW); + + // Initial state: + OK(getFromSocket("/dispatch focusmonitor HEADLESS-2")); + OK(getFromSocket("/dispatch workspace 10")); + Tests::spawnKitty(); + OK(getFromSocket("/dispatch workspace 11")); + Tests::spawnKitty(); + + OK(getFromSocket("/dispatch focusmonitor HEADLESS-3")); + OK(getFromSocket("/dispatch workspace 12")); + Tests::spawnKitty(); + + OK(getFromSocket("/dispatch focusmonitor HEADLESS-2")); + { + auto str = getFromSocket("/activeworkspace"); + EXPECT_CONTAINS(str, "workspace ID 11"); + } + OK(getFromSocket("/dispatch workspace previous_per_monitor")); + { + auto str = getFromSocket("/activeworkspace"); + EXPECT_CONTAINS(str, "workspace ID 10"); + } + + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); +} + static void testMultimonBAF() { NLog::log("{}Testing multimon back and forth", Colors::YELLOW); @@ -733,6 +762,7 @@ static bool test() { testMultimonBAF(); testMultimonFocus(); + testWorkspaceHistoryMultiMon(); // destroy the headless output OK(getFromSocket("/output remove HEADLESS-3")); diff --git a/src/desktop/history/WorkspaceHistoryTracker.cpp b/src/desktop/history/WorkspaceHistoryTracker.cpp index daa115f89..e341454d7 100644 --- a/src/desktop/history/WorkspaceHistoryTracker.cpp +++ b/src/desktop/history/WorkspaceHistoryTracker.cpp @@ -31,111 +31,92 @@ CWorkspaceHistoryTracker::CWorkspaceHistoryTracker() { }); } -CWorkspaceHistoryTracker::SWorkspacePreviousData& CWorkspaceHistoryTracker::dataFor(PHLWORKSPACE ws) { - for (auto& ref : m_datas) { - if (ref.workspace != ws) - continue; - - return ref; - } - - return m_datas.emplace_back(SWorkspacePreviousData{ - .workspace = ws, - }); -} - -void CWorkspaceHistoryTracker::track(PHLWORKSPACE w) { - if (!w || !w->m_monitor || w == m_lastWorkspaceData.workspace) +void CWorkspaceHistoryTracker::track(PHLWORKSPACE ws) { + if (!ws || !ws->m_monitor) return; - static auto PALLOWWORKSPACECYCLES = CConfigValue("binds:allow_workspace_cycles"); + static auto PALLOWWORKSPACECYCLES = CConfigValue("binds:allow_workspace_cycles"); - auto& data = dataFor(w); - - Hyprutils::Utils::CScopeGuard x([&] { setLastWorkspaceData(w); }); - - if (m_lastWorkspaceData.workspace == w && !*PALLOWWORKSPACECYCLES) + if (!m_history.empty() && m_history.front().workspace == ws && !*PALLOWWORKSPACECYCLES) return; - data.previous = m_lastWorkspaceData.workspace; - if (m_lastWorkspaceData.workspace) { - data.previousName = m_lastWorkspaceData.workspace->m_name; - data.previousID = m_lastWorkspaceData.workspace->m_id; - data.previousMon = m_lastWorkspaceData.workspace->m_monitor; - } else { - data.previousName = m_lastWorkspaceData.workspaceName; - data.previousID = m_lastWorkspaceData.workspaceID; - data.previousMon = m_lastWorkspaceData.monitor; - } + // Erase from timeline if it exists so we can move it to the very front + std::erase_if(m_history, [&](const auto& entry) { return entry.workspace == ws; }); + + // Push the newly focused workspace to the top of our MRU list + m_history.push_front(SHistoryEntry{.workspace = ws, .monitor = ws->m_monitor, .name = ws->m_name, .id = ws->m_id}); + + Hyprutils::Utils::CScopeGuard x([&] { setLastWorkspaceData(ws); }); } void CWorkspaceHistoryTracker::gc() { - std::erase_if(m_datas, [](const auto& e) { return !e.workspace; }); + std::vector monitorCounts; + std::erase_if(m_history, [&](const auto& entry) { + // Search if the monitor has been seen already + for (auto& mon : monitorCounts | std::views::drop(1)) { + // Remove entry + if (mon == entry.monitor) + return !entry.workspace; + } + // Add monitor to seen monitors + monitorCounts.emplace_back(entry.monitor); + return false; + }); } -const CWorkspaceHistoryTracker::SWorkspacePreviousData* CWorkspaceHistoryTracker::previousWorkspace(PHLWORKSPACE ws) { +const CWorkspaceHistoryTracker::SHistoryEntry CWorkspaceHistoryTracker::previousWorkspace(PHLWORKSPACE ws) { gc(); + auto it = std::ranges::find_if(m_history, [&](const auto& entry) { return entry.workspace == ws; }); - for (const auto& d : m_datas) { - if (d.workspace != ws) - continue; - return &d; - } + // If the workspace is found in history, the previous one is simply the next element down the timeline + if (it != m_history.end() && std::next(it) != m_history.end()) + return *std::next(it); - return &dataFor(ws); + // No prior history found + return SHistoryEntry{.id = WORKSPACE_INVALID}; } SWorkspaceIDName CWorkspaceHistoryTracker::previousWorkspaceIDName(PHLWORKSPACE ws) { - gc(); + const auto DATA = previousWorkspace(ws); - for (const auto& d : m_datas) { - if (d.workspace != ws) - continue; - return SWorkspaceIDName{.id = d.previousID, .name = d.previousName, .isAutoIDd = d.previousID <= 0}; - } + if (DATA.id == WORKSPACE_INVALID) + return SWorkspaceIDName{.id = WORKSPACE_INVALID}; - auto& d = dataFor(ws); - return SWorkspaceIDName{.id = d.previousID, .name = d.previousName, .isAutoIDd = d.previousID <= 0}; + return SWorkspaceIDName{.id = DATA.id, .name = DATA.name, .isAutoIDd = DATA.id <= 0}; } -const CWorkspaceHistoryTracker::SWorkspacePreviousData* CWorkspaceHistoryTracker::previousWorkspace(PHLWORKSPACE ws, PHLMONITOR restrict) { +const CWorkspaceHistoryTracker::SHistoryEntry CWorkspaceHistoryTracker::previousWorkspace(PHLWORKSPACE ws, PHLMONITOR restrict) { if (!restrict) return previousWorkspace(ws); - auto& data = dataFor(ws); - while (true) { + gc(); - // case 1: previous exists - if (data.previous) { - if (data.previous->m_monitor != restrict) { - data = dataFor(data.previous.lock()); - continue; - } + auto it = std::ranges::find_if(m_history, [&](const auto& entry) { return entry.workspace == ws; }); - break; - } + // Start looking from the element immediately following `ws` in the list + if (it != m_history.end()) + it++; + else + it = m_history.begin(); - // case 2: previous doesnt exist, but we have mon - if (data.previousMon) { - if (data.previousMon != restrict) - return nullptr; + // Scan down the timeline until we hit a workspace mapped to the restricted monitor + while (it != m_history.end()) { + if (it->monitor == restrict) + return *it; - break; - } - - // case 3: no mon and no previous - return nullptr; + it++; } - return &data; + // Entry not found + return SHistoryEntry{.id = WORKSPACE_INVALID}; } SWorkspaceIDName CWorkspaceHistoryTracker::previousWorkspaceIDName(PHLWORKSPACE ws, PHLMONITOR restrict) { const auto DATA = previousWorkspace(ws, restrict); - if (!DATA) + if (DATA.id == WORKSPACE_INVALID) return SWorkspaceIDName{.id = WORKSPACE_INVALID}; - return SWorkspaceIDName{.id = DATA->previousID, .name = DATA->previousName, .isAutoIDd = DATA->previousID <= 0}; + return SWorkspaceIDName{.id = DATA.id, .name = DATA.name, .isAutoIDd = DATA.id <= 0}; } void CWorkspaceHistoryTracker::setLastWorkspaceData(PHLWORKSPACE w) { diff --git a/src/desktop/history/WorkspaceHistoryTracker.hpp b/src/desktop/history/WorkspaceHistoryTracker.hpp index baecb3638..e80a51524 100644 --- a/src/desktop/history/WorkspaceHistoryTracker.hpp +++ b/src/desktop/history/WorkspaceHistoryTracker.hpp @@ -5,7 +5,7 @@ #include "../../macros.hpp" #include "../../helpers/MiscFunctions.hpp" -#include +#include namespace Desktop::History { class CWorkspaceHistoryTracker { @@ -17,19 +17,18 @@ namespace Desktop::History { CWorkspaceHistoryTracker(CWorkspaceHistoryTracker&) = delete; CWorkspaceHistoryTracker(CWorkspaceHistoryTracker&&) = delete; - struct SWorkspacePreviousData { + struct SHistoryEntry { PHLWORKSPACEREF workspace; - PHLWORKSPACEREF previous; - PHLMONITORREF previousMon; - std::string previousName = ""; - WORKSPACEID previousID = WORKSPACE_INVALID; + PHLMONITORREF monitor; + std::string name = ""; + WORKSPACEID id = WORKSPACE_INVALID; }; - const SWorkspacePreviousData* previousWorkspace(PHLWORKSPACE ws); - SWorkspaceIDName previousWorkspaceIDName(PHLWORKSPACE ws); + const SHistoryEntry previousWorkspace(PHLWORKSPACE ws); + SWorkspaceIDName previousWorkspaceIDName(PHLWORKSPACE ws); - const SWorkspacePreviousData* previousWorkspace(PHLWORKSPACE ws, PHLMONITOR restrict); - SWorkspaceIDName previousWorkspaceIDName(PHLWORKSPACE ws, PHLMONITOR restrict); + const SHistoryEntry previousWorkspace(PHLWORKSPACE ws, PHLMONITOR restrict); + SWorkspaceIDName previousWorkspaceIDName(PHLWORKSPACE ws, PHLMONITOR restrict); private: struct SLastWorkspaceData { @@ -39,14 +38,12 @@ namespace Desktop::History { WORKSPACEID workspaceID = WORKSPACE_INVALID; } m_lastWorkspaceData; - std::vector m_datas; + std::deque m_history; - void track(PHLWORKSPACE w); - void gc(); - void setLastWorkspaceData(PHLWORKSPACE w); - - SWorkspacePreviousData& dataFor(PHLWORKSPACE ws); + void track(PHLWORKSPACE w); + void gc(); + void setLastWorkspaceData(PHLWORKSPACE w); }; SP workspaceTracker(); -}; \ No newline at end of file +};