Unify per-monitor workspace numbering and lifecycle policy

This commit is contained in:
Samuel Onoja committed 2026-10-01 18:24:12 +01:00
1 parent 07bda5a21c
commit 1441875096
20 files changed
+707 -316

No files matched your search

@@ -1,7 +1,7 @@
"""Workspace history against a temporary nested compositor.
FERESE_TEST_WORKSPACE_HISTORY=1 python3 scripts/tests/test_workspace_history_isolated.py
Requires target/debug/ferese, feresectl, dbus-daemon, and a Wayland host.
Requires target/debug/ferese, feresectl, foot, dbus-daemon, and a Wayland host.
"""
import json
import os
@@ -15,7 +15,7 @@ import unittest
@unittest.skipUnless(os.environ.get("FERESE_TEST_WORKSPACE_HISTORY") == "1", "nested workspace test is opt-in")
class WorkspaceHistoryTest(unittest.TestCase):
def test_empty_workspace_history_and_explicit_ipc_selection(self):
def test_dynamic_positions_spare_cleanup_and_history(self):
repo = Path(__file__).resolve().parents[2]
with tempfile.TemporaryDirectory(prefix="ferese-workspace-test-") as temporary:
root = Path(temporary)
@@ -59,28 +59,73 @@ class WorkspaceHistoryTest(unittest.TestCase):
if compositor.poll() is not None or time.monotonic() >= deadline:
self.fail((root / "compositor.log").read_text())
time.sleep(.025)
self.assertEqual(active(), "1")
command("workspace-back-and-forth")
self.assertEqual(active(), "1", "no history is a no-op")
def wait_for(predicate):
deadline = time.monotonic() + 10
while time.monotonic() < deadline:
value = predicate()
if value:
return value
time.sleep(.025)
self.fail("Timed out: " + (root / "compositor.log").read_text())
def workspaces():
return command("get-workspaces")
initial = workspaces()[0]["id"]
self.assertEqual(len(workspaces()), 1)
command("workspace", "9")
self.assertEqual(workspaces()[0]["id"], initial)
self.assertEqual(len(workspaces()), 1, "numeric lookup must not create gaps")
env["WAYLAND_DISPLAY"] = str(next(
path for path in runtime.glob("wayland-*") if not path.name.endswith(".lock")
))
first = launch(["foot", "--app-id", "ferese.test.workspace.first", "sleep", "120"],
stdout=log, stderr=log)
wait_for(lambda: len(workspaces()) == 2 and workspaces()[0]["window_count"] == 1)
command("workspace", "2")
self.assertEqual(active(), "2")
self.assertEqual({w["name"] for w in command("get-workspaces")}, {"1", "2"})
second_id = next(w["id"] for w in workspaces() if w["focused"])
second = launch(["foot", "--app-id", "ferese.test.workspace.second", "sleep", "120"],
stdout=log, stderr=log)
wait_for(lambda: len(workspaces()) == 3 and workspaces()[1]["window_count"] == 1)
for expected in ["1", "2", "1", "2"]:
command("workspace-back-and-forth")
self.assertEqual(active(), expected)
command("workspace", "3")
self.assertEqual({w["name"] for w in command("get-workspaces")}, {"2", "3"},
"old empty history is pruned")
os.killpg(first.pid, signal.SIGTERM)
first.wait(timeout=3)
wait_for(lambda: len(workspaces()) == 2)
self.assertEqual(workspaces()[0]["id"], second_id, "compaction preserves identity")
self.assertEqual([w["index"] for w in workspaces()], [1, 2])
command("workspace-back-and-forth")
self.assertEqual(active(), "1", "removed history cannot resurrect an empty workspace")
spare_id = workspaces()[1]["id"]
command("workspace", "99")
self.assertEqual(active(), "2")
self.assertEqual(next(w["id"] for w in workspaces() if w["focused"]), spare_id)
command("workspace-back-and-forth")
self.assertEqual(active(), "1")
replacement = source.with_suffix(".new")
replacement.write_text("animations { enabled #false; }\nworkspaces { auto-back-and-forth #true; }\n")
replacement.replace(source)
command("reload-config")
command("workspace", "3")
self.assertEqual(active(), "3", "literal IPC selection must not auto-toggle")
command("workspace", "1")
self.assertEqual(active(), "1", "explicit IPC selection must not auto-toggle")
command("workspace-back-and-forth")
self.assertEqual(active(), "2", "reload must preserve history")
self.assertEqual(active(), "2")
command("workspace-back-and-forth")
self.assertEqual(active(), "3")
self.assertEqual(active(), "1")
os.killpg(second.pid, signal.SIGTERM)
second.wait(timeout=3)
wait_for(lambda: all(w["window_count"] == 0 for w in workspaces()))
command("workspace", "2")
wait_for(lambda: len(workspaces()) == 1)
self.assertEqual(workspaces()[0]["id"], spare_id)
self.assertEqual(workspaces()[0]["name"], "1")
self.assertTrue(workspaces()[0]["visible"])
self.assertTrue(workspaces()[0]["focused"])
finally:
for child in reversed(children):
if child.poll() is None: