From c63d0003a1e5155248695f19778f815a8ad34c67 Mon Sep 17 00:00:00 2001 From: Vaxry <43317083+vaxerski@users.noreply.github.com> Date: Sun, 27 Jul 2025 18:46:23 +0200 Subject: [PATCH] core: fix workspace persistence tracking (#11239) --- CMakeLists.txt | 1 + hyprtester/src/tests/main/persistent.cpp | 83 ++++++++++++++++++++++++ hyprtester/test.conf | 1 + src/Compositor.cpp | 31 +++++++-- src/debug/HyprCtl.cpp | 7 +- src/desktop/Workspace.cpp | 20 +++++- src/desktop/Workspace.hpp | 7 +- 7 files changed, 140 insertions(+), 10 deletions(-) create mode 100644 hyprtester/src/tests/main/persistent.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 0da6d7ec..5c5c5ddd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -399,6 +399,7 @@ if(NO_TESTS) message(STATUS "building tests is disabled") else() message(STATUS "building tests is enabled (NO_TESTS not defined)") + add_subdirectory(hyprtester) endif() # binary and symlink diff --git a/hyprtester/src/tests/main/persistent.cpp b/hyprtester/src/tests/main/persistent.cpp new file mode 100644 index 00000000..8a2324a7 --- /dev/null +++ b/hyprtester/src/tests/main/persistent.cpp @@ -0,0 +1,83 @@ +#include "tests.hpp" +#include "../../shared.hpp" +#include "../../hyprctlCompat.hpp" +#include +#include +#include +#include +#include +#include +#include +#include "../shared.hpp" + +static int ret = 0; + +using namespace Hyprutils::OS; +using namespace Hyprutils::Memory; + +#define UP CUniquePointer +#define SP CSharedPointer + +static bool test() { + NLog::log("{}Testing persistent workspaces", Colors::GREEN); + + EXPECT(Tests::windowCount(), 0); + + // test on workspace "window" + NLog::log("{}Switching to workspace 1", Colors::YELLOW); + OK(getFromSocket("/dispatch workspace 1")); + + OK(getFromSocket("/keyword workspace 5, monitor:HEADLESS-2, persistent:1")); + OK(getFromSocket("/keyword workspace 6, monitor:HEADLESS-PERSISTENT-TEST, persistent:1")); + OK(getFromSocket("/keyword workspace name:PERSIST, monitor:HEADLESS-PERSISTENT-TEST, persistent:1")); + + { + auto str = getFromSocket("/workspaces"); + EXPECT_CONTAINS(str, "ID 5 (5)"); + EXPECT_COUNT_STRING(str, "workspace ID ", 2); + } + + OK(getFromSocket("/output create headless HEADLESS-PERSISTENT-TEST")); + + { + auto str = getFromSocket("/monitors"); + EXPECT_CONTAINS(str, "HEADLESS-PERSISTENT-TEST"); + } + + OK(getFromSocket("/dispatch focusmonitor HEADLESS-PERSISTENT-TEST")); + + { + auto str = getFromSocket("/workspaces"); + EXPECT_CONTAINS(str, "ID 2 (2)"); // this should be automatically generated by hl + EXPECT_CONTAINS(str, "ID 5 (5)"); + EXPECT_CONTAINS(str, "ID 6 (6)"); + EXPECT_CONTAINS(str, "(PERSIST) on monitor"); + EXPECT_COUNT_STRING(str, "workspace ID ", 5); + } + + OK(getFromSocket("/reload")); + + { + auto str = getFromSocket("/workspaces"); + EXPECT_NOT_CONTAINS(str, "ID 5 (5)"); + EXPECT_NOT_CONTAINS(str, "ID 6 (6)"); + EXPECT_NOT_CONTAINS(str, "(PERSIST) on monitor"); + EXPECT_COUNT_STRING(str, "workspace ID ", 2); + } + + OK(getFromSocket("/output remove HEADLESS-PERSISTENT-TEST")); + + // kill all + NLog::log("{}Killing all windows", Colors::YELLOW); + Tests::killAllWindows(); + + NLog::log("{}Expecting 0 windows", Colors::YELLOW); + EXPECT(Tests::windowCount(), 0); + + // reload cfg + OK(getFromSocket("/reload")); + + return !ret; +} + +REGISTER_TEST_FN(test) diff --git a/hyprtester/test.conf b/hyprtester/test.conf index 65f44e5c..928be397 100644 --- a/hyprtester/test.conf +++ b/hyprtester/test.conf @@ -22,6 +22,7 @@ monitor=HEADLESS-3,1920x1080@60,auto-right,1 monitor=HEADLESS-4,1920x1080@60,auto-right,1 monitor=HEADLESS-5,1920x1080@60,auto-right,1 monitor=HEADLESS-6,1920x1080@60,auto-right,1 +monitor=HEADLESS-PERSISTENT-TEST,1920x1080@60,auto-right,1 monitor=,disabled diff --git a/src/Compositor.cpp b/src/Compositor.cpp index cf1a7cb4..14f9ee63 100644 --- a/src/Compositor.cpp +++ b/src/Compositor.cpp @@ -3109,6 +3109,8 @@ void CCompositor::ensurePersistentWorkspacesPresent(const std::vector persistentFound; + for (const auto& rule : rules) { if (!rule.isPersistent) continue; @@ -3142,17 +3144,20 @@ void CCompositor::ensurePersistentWorkspacesPresent(const std::vectorm_id : m_lastMonitor->m_id, wsname, false); + PWORKSPACE = createNewWorkspace(id, PMONITOR ? PMONITOR->m_id : m_lastMonitor->m_id, wsname, false); } - if (PWORKSPACE) - PWORKSPACE->m_persistent = true; - if (!PMONITOR) { Debug::log(ERR, "ensurePersistentWorkspacesPresent: couldn't resolve monitor for {}, skipping", rule.monitor); continue; } + if (PWORKSPACE) + PWORKSPACE->setPersistent(true); + + if (!pWorkspace) + persistentFound.emplace_back(PWORKSPACE); + if (PWORKSPACE) { if (PWORKSPACE->m_monitor == PMONITOR) { Debug::log(LOG, "ensurePersistentWorkspacesPresent: workspace persistent {} already on {}", rule.workspaceString, PMONITOR->m_name); @@ -3165,4 +3170,22 @@ void CCompositor::ensurePersistentWorkspacesPresent(const std::vector toDowngrade; + for (auto& w : getWorkspaces()) { + if (!w->isPersistent()) + continue; + + if (std::ranges::contains(persistentFound, w.lock())) + continue; + + toDowngrade.emplace_back(w); + } + + for (auto& ws : toDowngrade) { + ws->setPersistent(false); + } + } } diff --git a/src/debug/HyprCtl.cpp b/src/debug/HyprCtl.cpp index c8751d7e..7d0eb114 100644 --- a/src/debug/HyprCtl.cpp +++ b/src/debug/HyprCtl.cpp @@ -355,12 +355,12 @@ std::string CHyprCtl::getWorkspaceData(PHLWORKSPACE w, eHyprCtlOutputFormat form }})#", w->m_id, escapeJSONStrings(w->m_name), escapeJSONStrings(PMONITOR ? PMONITOR->m_name : "?"), escapeJSONStrings(PMONITOR ? std::to_string(PMONITOR->m_id) : "null"), w->getWindows(), w->m_hasFullscreenWindow ? "true" : "false", - (uintptr_t)PLASTW.get(), PLASTW ? escapeJSONStrings(PLASTW->m_title) : "", w->m_persistent ? "true" : "false"); + (uintptr_t)PLASTW.get(), PLASTW ? escapeJSONStrings(PLASTW->m_title) : "", w->isPersistent() ? "true" : "false"); } else { return std::format( "workspace ID {} ({}) on monitor {}:\n\tmonitorID: {}\n\twindows: {}\n\thasfullscreen: {}\n\tlastwindow: 0x{:x}\n\tlastwindowtitle: {}\n\tispersistent: {}\n\n", w->m_id, w->m_name, PMONITOR ? PMONITOR->m_name : "?", PMONITOR ? std::to_string(PMONITOR->m_id) : "null", w->getWindows(), (int)w->m_hasFullscreenWindow, - (uintptr_t)PLASTW.get(), PLASTW ? PLASTW->m_title : "", (int)w->m_persistent); + (uintptr_t)PLASTW.get(), PLASTW ? PLASTW->m_title : "", (int)w->isPersistent()); } } @@ -1183,6 +1183,9 @@ static std::string dispatchKeyword(eHyprCtlOutputFormat format, std::string in) } } + if (COMMAND.contains("workspace")) + g_pConfigManager->ensurePersistentWorkspacesPresent(); + Debug::log(LOG, "Hyprctl: keyword {} : {}", COMMAND, VALUE); if (retval.empty()) diff --git a/src/desktop/Workspace.cpp b/src/desktop/Workspace.cpp index 49882534..c14d6e38 100644 --- a/src/desktop/Workspace.cpp +++ b/src/desktop/Workspace.cpp @@ -44,7 +44,7 @@ void CWorkspace::init(PHLWORKSPACE self) { m_inert = false; const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(self); - m_persistent = WORKSPACERULE.isPersistent; + setPersistent(WORKSPACERULE.isPersistent); if (self->m_wasCreatedEmpty) if (auto cmd = WORKSPACERULE.onCreatedEmptyRunCmd) @@ -639,7 +639,7 @@ void CWorkspace::rename(const std::string& name) { m_name = name; const auto WORKSPACERULE = g_pConfigManager->getWorkspaceRuleFor(m_self.lock()); - m_persistent = WORKSPACERULE.isPersistent; + setPersistent(WORKSPACERULE.isPersistent); if (WORKSPACERULE.isPersistent) g_pCompositor->ensurePersistentWorkspacesPresent(std::vector{WORKSPACERULE}, m_self.lock()); @@ -658,3 +658,19 @@ void CWorkspace::updateWindows() { w->updateDynamicRules(); } } + +void CWorkspace::setPersistent(bool persistent) { + if (m_persistent == persistent) + return; + + m_persistent = persistent; + + if (persistent) + m_selfPersistent = m_self.lock(); + else + m_selfPersistent.reset(); +} + +bool CWorkspace::isPersistent() { + return m_persistent; +} diff --git a/src/desktop/Workspace.hpp b/src/desktop/Workspace.hpp index fa23966b..a6074843 100644 --- a/src/desktop/Workspace.hpp +++ b/src/desktop/Workspace.hpp @@ -58,8 +58,6 @@ class CWorkspace { bool m_wasCreatedEmpty = true; - bool m_persistent = false; - // Inert: destroyed and invalid. If this is true, release the ptr you have. bool inert(); void startAnim(bool in, bool left, bool instant = false); @@ -83,6 +81,8 @@ class CWorkspace { void rename(const std::string& name = ""); void forceReportSizesToWindows(); void updateWindows(); + void setPersistent(bool persistent); + bool isPersistent(); struct { CSignalT<> destroy; @@ -99,6 +99,9 @@ class CWorkspace { SP m_focusedWindowHook; bool m_inert = true; + + SP m_selfPersistent; // for persistent workspaces. + bool m_persistent = false; }; inline bool valid(const PHLWORKSPACE& ref) {