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

@@ -6,9 +6,20 @@ from unittest import mock
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src"))
from cthulhu import input_event_manager
from cthulhu import compositor_state_types
class InputEventManagerX11FocusRegressionTests(unittest.TestCase):
def setUp(self):
self.compositorSnapshot = input_event_manager.cthulhu_state.compositorSnapshot
input_event_manager.cthulhu_state.compositorSnapshot = None
self.addCleanup(
setattr,
input_event_manager.cthulhu_state,
"compositorSnapshot",
self.compositorSnapshot,
)
def test_active_x11_window_differs_from_cached_atspi_window_by_pid(self):
manager = input_event_manager.InputEventManager()
cachedWindow = object()
@@ -599,7 +610,6 @@ class InputEventManagerX11FocusRegressionTests(unittest.TestCase):
def test_xterm_match_reuses_cached_metadata_while_ewmh_window_id_is_unchanged(self):
manager = input_event_manager.InputEventManager()
activeWindow = object()
with (
mock.patch.object(
@@ -609,12 +619,7 @@ class InputEventManagerX11FocusRegressionTests(unittest.TestCase):
),
mock.patch.object(
manager,
"_get_active_x11_window",
return_value=activeWindow,
) as getActiveWindow,
mock.patch.object(
manager,
"_x11_window_xterm_match",
"_x11_window_id_xterm_match",
return_value=False,
) as matcher,
):
@@ -623,8 +628,165 @@ class InputEventManagerX11FocusRegressionTests(unittest.TestCase):
self.assertFalse(firstResult)
self.assertFalse(secondResult)
getActiveWindow.assert_called_once_with(ewmhWindowId=0x1400010)
matcher.assert_called_once_with(activeWindow)
matcher.assert_called_once_with(0x1400010)
def test_xterm_match_prefers_i3_focused_window_id_over_stale_ewmh_id(self):
manager = input_event_manager.InputEventManager()
snapshot = compositor_state_types.DesktopContextSnapshot(
session_type="x11",
backend_name="i3-ipc",
focused_window_id=0x2600003,
focused_workspace_empty=False,
)
with (
mock.patch.object(input_event_manager.cthulhu_state, "compositorSnapshot", snapshot),
mock.patch.object(
manager,
"_get_ewmh_active_x11_window_id",
return_value=0x200000C,
) as getEwmhWindowId,
mock.patch.object(
manager,
"_x11_window_id_xterm_match",
return_value=False,
) as matcher,
):
result = manager._active_x11_window_xterm_match()
self.assertFalse(result)
matcher.assert_called_once_with(0x2600003)
getEwmhWindowId.assert_not_called()
def test_i3_exact_xid_initializes_x11_before_matching_xterm(self):
manager = input_event_manager.InputEventManager()
snapshot = compositor_state_types.DesktopContextSnapshot(
session_type="x11",
backend_name="i3-ipc",
focused_window_id=0x200000C,
focused_workspace_empty=False,
)
window = mock.Mock()
window.get_wm_class.return_value = ("xterm", "XTerm")
window.get_wm_name.return_value = "devel"
window.get_full_property.return_value = None
display = mock.Mock()
display.create_resource_object.return_value = window
def initializeX11():
manager._x11Display = display
manager._x11AnyPropertyType = object()
return True
with (
mock.patch.object(input_event_manager.cthulhu_state, "compositorSnapshot", snapshot),
mock.patch.object(
manager,
"_ensure_x11_display",
side_effect=initializeX11,
) as ensureX11,
mock.patch.object(manager, "_get_ewmh_active_x11_window_id") as getEwmhWindowId,
):
result = manager._active_x11_window_xterm_match()
self.assertTrue(result)
ensureX11.assert_called_once_with()
display.create_resource_object.assert_called_once_with("window", 0x200000C)
getEwmhWindowId.assert_not_called()
def test_confirmed_empty_i3_workspace_is_definitely_not_xterm(self):
manager = input_event_manager.InputEventManager()
snapshot = compositor_state_types.DesktopContextSnapshot(
session_type="x11",
backend_name="i3-ipc",
focused_workspace_empty=True,
)
with (
mock.patch.object(input_event_manager.cthulhu_state, "compositorSnapshot", snapshot),
mock.patch.object(manager, "_get_ewmh_active_x11_window_id") as getEwmhWindowId,
mock.patch.object(manager, "_x11_window_id_xterm_match") as matcher,
):
result = manager._active_x11_window_xterm_match()
self.assertFalse(result)
getEwmhWindowId.assert_not_called()
matcher.assert_not_called()
def test_unknown_i3_context_falls_back_to_generic_ewmh_window_id(self):
manager = input_event_manager.InputEventManager()
snapshot = compositor_state_types.DesktopContextSnapshot(
session_type="x11",
backend_name="i3-ipc",
focused_window_id=None,
focused_workspace_empty=None,
)
with (
mock.patch.object(input_event_manager.cthulhu_state, "compositorSnapshot", snapshot),
mock.patch.object(
manager,
"_get_ewmh_active_x11_window_id",
return_value=0x2600003,
),
mock.patch.object(
manager,
"_x11_window_id_xterm_match",
return_value=False,
) as matcher,
):
result = manager._active_x11_window_xterm_match()
self.assertFalse(result)
matcher.assert_called_once_with(0x2600003)
def test_ewmh_brave_window_does_not_use_stale_wnck_xterm_metadata(self):
manager = input_event_manager.InputEventManager()
with (
mock.patch.object(
manager,
"_get_ewmh_active_x11_window_id",
return_value=0x2600003,
),
mock.patch.object(
manager,
"_x11_window_id_xterm_match",
return_value=False,
) as directMatcher,
mock.patch.object(manager, "_get_active_x11_window") as getWnckWindow,
mock.patch.object(manager, "_x11_window_xterm_match") as wnckMatcher,
):
result = manager._active_x11_window_xterm_match()
self.assertFalse(result)
directMatcher.assert_called_once_with(0x2600003)
getWnckWindow.assert_not_called()
wnckMatcher.assert_not_called()
def test_direct_x11_metadata_identifies_brave_as_non_xterm(self):
manager = input_event_manager.InputEventManager()
with mock.patch.object(
manager,
"_get_x11_window_metadata",
return_value=(["brave-origin", "Brave-origin", "New Tab - Brave Origin"], 1234),
):
result = manager._x11_window_id_xterm_match(0x2600003)
self.assertFalse(result)
def test_direct_x11_metadata_identifies_xterm(self):
manager = input_event_manager.InputEventManager()
with mock.patch.object(
manager,
"_get_x11_window_metadata",
return_value=(["xterm", "XTerm", "devel"], 1234),
):
result = manager._x11_window_id_xterm_match(0x200000C)
self.assertTrue(result)
def test_xterm_focus_change_suspends_grabs_without_keyboard_event(self):
manager = input_event_manager.InputEventManager()
@@ -687,9 +849,30 @@ class InputEventManagerX11FocusRegressionTests(unittest.TestCase):
result = manager._poll_xterm_grab_recovery()
self.assertTrue(result)
self.assertEqual(manager._xtermRecoveryPollCount, 1)
restore.assert_called_once_with()
suspend.assert_not_called()
def test_xterm_match_records_last_raw_ewmh_window_id(self):
manager = input_event_manager.InputEventManager()
with (
mock.patch.object(
manager,
"_get_ewmh_active_x11_window_id",
return_value=0x1400010,
),
mock.patch.object(
manager,
"_x11_window_id_xterm_match",
return_value=False,
),
):
result = manager._active_x11_window_xterm_match()
self.assertFalse(result)
self.assertEqual(manager._lastEwmhReadWindowId, 0x1400010)
def test_xterm_focus_poll_survives_transient_matcher_failure(self):
manager = input_event_manager.InputEventManager()
manager._device = object()