desktop/workspaceHistory: small refactor to work better with multi monitor setups (#13632)

This commit is contained in:
Mr-Stoneman 2026-03-26 22:38:39 +01:00 committed by GitHub
parent 4c29b9de4e
commit b0f6ac23b2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 95 additions and 87 deletions

View file

@ -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"));

View file

@ -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<Hyprlang::INT>("binds:allow_workspace_cycles");
static auto PALLOWWORKSPACECYCLES = CConfigValue<Hyprlang::INT>("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<PHLMONITORREF> 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) {

View file

@ -5,7 +5,7 @@
#include "../../macros.hpp"
#include "../../helpers/MiscFunctions.hpp"
#include <vector>
#include <deque>
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<SWorkspacePreviousData> m_datas;
std::deque<SHistoryEntry> 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<CWorkspaceHistoryTracker> workspaceTracker();
};
};