Add i3 focus tracking and harden X11 handoff
This commit is contained in:
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()
|
||||
|
||||
Reference in new issue
Block a user