Add i3 focus tracking and harden X11 handoff

This commit is contained in:
Storm Dragon committed 2026-07-30 18:53:56 -04:00
1 parent f4ee5e4468
commit c39f3bda47
17 files changed
+1379 -76

No files matched your search

@@ -11,6 +11,7 @@ import cthulhu as cthulhu_package
from cthulhu import cthulhu_state
from cthulhu import compositor_state_adapter
from cthulhu import compositor_state_i3
from cthulhu import compositor_state_types
from cthulhu import compositor_state_wayland
@@ -74,7 +75,7 @@ class CompositorStateAdapterRegressionTests(unittest.TestCase):
self.assertTrue(callable(selectedBackend.activate_calls[0]))
self.assertEqual(adapter.get_snapshot().backend_name, "selected")
def test_default_workspace_backends_include_wayland_then_null_backend(self) -> None:
def test_default_workspace_backends_include_i3_wayland_then_null_backend(self) -> None:
adapter = compositor_state_adapter.CompositorStateAdapter()
backend_types = [type(backend) for backend in adapter._workspaceBackends]
@@ -82,11 +83,68 @@ class CompositorStateAdapterRegressionTests(unittest.TestCase):
self.assertEqual(
backend_types,
[
compositor_state_i3.I3WorkspaceBackend,
compositor_state_wayland.WaylandSharedProtocolsBackend,
compositor_state_wayland.NullWorkspaceBackend,
],
)
def test_workspace_payload_records_i3_foreground_context(self) -> None:
backend = FakeWorkspaceBackend(True, "i3-ipc")
adapter = compositor_state_adapter.CompositorStateAdapter(workspace_backends=[backend])
adapter.activate()
backend.activate_calls[-1](
compositor_state_types.WORKSPACE_STATE_CHANGED,
{"8"},
"i3-window-focus",
{
"focused_window_id": 0x200000C,
"focused_window_title": "9 devel",
"focused_workspace_empty": False,
},
)
snapshot = adapter.get_snapshot()
self.assertEqual(snapshot.active_workspace_ids, frozenset({"8"}))
self.assertEqual(snapshot.focused_window_id, 0x200000C)
self.assertEqual(snapshot.focused_window_title, "9 devel")
self.assertFalse(snapshot.focused_workspace_empty)
def test_confirmed_empty_workspace_clears_stale_accessible_context(self) -> None:
backend = FakeWorkspaceBackend(True, "i3-ipc")
adapter = compositor_state_adapter.CompositorStateAdapter(workspace_backends=[backend])
adapter.activate()
adapter._snapshot = compositor_state_types.DesktopContextSnapshot(
session_type="x11",
backend_name="i3-ipc",
active_window_token="42:Browser",
locus_of_focus_token="42:Web content",
active_window_pid=42,
locus_of_focus_pid=42,
active_window_name="Browser",
locus_of_focus_name="Web content",
)
backend.activate_calls[-1](
compositor_state_types.WORKSPACE_STATE_CHANGED,
{"9"},
"i3-workspace-focus",
{
"focused_window_id": None,
"focused_window_title": "",
"focused_workspace_empty": True,
},
)
snapshot = adapter.get_snapshot()
self.assertEqual(snapshot.active_window_token, "")
self.assertEqual(snapshot.locus_of_focus_token, "")
self.assertEqual(snapshot.active_window_pid, -1)
self.assertEqual(snapshot.locus_of_focus_pid, -1)
self.assertEqual(snapshot.active_window_name, "")
self.assertEqual(snapshot.locus_of_focus_name, "")
def test_wayland_backend_is_unavailable_without_wayland_session(self) -> None:
backend = compositor_state_wayland.WaylandSharedProtocolsBackend()