Tighten up relase and reclaim clode for terminals.
This commit is contained in:
@@ -63,6 +63,15 @@ class CompositorStateAdapter:
|
|||||||
def get_snapshot(self) -> DesktopContextSnapshot:
|
def get_snapshot(self) -> DesktopContextSnapshot:
|
||||||
return self._snapshot
|
return self._snapshot
|
||||||
|
|
||||||
|
def refresh_workspace_context(self, reason: str) -> DesktopContextSnapshot:
|
||||||
|
"""Refreshes and returns the selected backend's current workspace context."""
|
||||||
|
|
||||||
|
backend = self._workspaceBackend
|
||||||
|
refreshContext = getattr(backend, "refresh_context", None)
|
||||||
|
if callable(refreshContext):
|
||||||
|
refreshContext(reason)
|
||||||
|
return self._snapshot
|
||||||
|
|
||||||
def get_session_type(self) -> str:
|
def get_session_type(self) -> str:
|
||||||
return get_session_type()
|
return get_session_type()
|
||||||
|
|
||||||
|
|||||||
@@ -92,6 +92,11 @@ class I3WorkspaceBackend:
|
|||||||
self._emitSignal = None
|
self._emitSignal = None
|
||||||
self._lastContext = None
|
self._lastContext = None
|
||||||
|
|
||||||
|
def refresh_context(self, reason: str) -> None:
|
||||||
|
"""Refreshes the cached i3 focus context without emitting a transition."""
|
||||||
|
|
||||||
|
self._refresh_context(reason, transition=False)
|
||||||
|
|
||||||
def _ensure_connection(self) -> bool:
|
def _ensure_connection(self) -> bool:
|
||||||
if self._connection is not None:
|
if self._connection is not None:
|
||||||
return True
|
return True
|
||||||
|
|||||||
@@ -174,6 +174,7 @@ class EventManager:
|
|||||||
|
|
||||||
debug.printMessage(debug.LEVEL_INFO, 'EVENT MANAGER: Activating keyboard handling', True)
|
debug.printMessage(debug.LEVEL_INFO, 'EVENT MANAGER: Activating keyboard handling', True)
|
||||||
self._inputEventManager = input_event_manager.get_manager()
|
self._inputEventManager = input_event_manager.get_manager()
|
||||||
|
self._inputEventManager.set_compositor_state_adapter(self._compositorStateAdapter)
|
||||||
self._inputEventManager.start_key_watcher()
|
self._inputEventManager.start_key_watcher()
|
||||||
cthulhu_state.device = self._inputEventManager._device
|
cthulhu_state.device = self._inputEventManager._device
|
||||||
self._keyHandlingActive = True
|
self._keyHandlingActive = True
|
||||||
@@ -277,6 +278,8 @@ class EventManager:
|
|||||||
self._compositorStateAdapter.remove_listener(self._handle_compositor_signal)
|
self._compositorStateAdapter.remove_listener(self._handle_compositor_signal)
|
||||||
|
|
||||||
self._compositorStateAdapter = adapter
|
self._compositorStateAdapter = adapter
|
||||||
|
if self._inputEventManager is not None:
|
||||||
|
self._inputEventManager.set_compositor_state_adapter(adapter)
|
||||||
if adapter is not None and hasattr(adapter, "add_listener"):
|
if adapter is not None and hasattr(adapter, "add_listener"):
|
||||||
adapter.add_listener(self._handle_compositor_signal)
|
adapter.add_listener(self._handle_compositor_signal)
|
||||||
|
|
||||||
|
|||||||
@@ -134,6 +134,12 @@ class InputEventManager:
|
|||||||
self._xtermRecoverySourceId: int = 0
|
self._xtermRecoverySourceId: int = 0
|
||||||
self._xtermRecoveryPollCount: int = 0
|
self._xtermRecoveryPollCount: int = 0
|
||||||
self._xtermHandoffHistory: List[str] = []
|
self._xtermHandoffHistory: List[str] = []
|
||||||
|
self._compositorStateAdapter: Optional[Any] = None
|
||||||
|
|
||||||
|
def set_compositor_state_adapter(self, adapter: Optional[Any]) -> None:
|
||||||
|
"""Stores the compositor adapter used to refresh XTerm handoff state."""
|
||||||
|
|
||||||
|
self._compositorStateAdapter = adapter
|
||||||
|
|
||||||
def activate_device(self) -> Atspi.Device:
|
def activate_device(self) -> Atspi.Device:
|
||||||
"""Creates and returns the AT-SPI device used by this manager."""
|
"""Creates and returns the AT-SPI device used by this manager."""
|
||||||
@@ -847,9 +853,17 @@ class InputEventManager:
|
|||||||
|
|
||||||
snapshot = cthulhu_state.compositorSnapshot
|
snapshot = cthulhu_state.compositorSnapshot
|
||||||
usingI3Context = snapshot is not None and snapshot.backend_name == "i3-ipc"
|
usingI3Context = snapshot is not None and snapshot.backend_name == "i3-ipc"
|
||||||
|
if usingI3Context and self._lastEwmhXtermMatch is True:
|
||||||
|
adapter = self._compositorStateAdapter
|
||||||
|
refreshContext = getattr(adapter, "refresh_workspace_context", None)
|
||||||
|
if callable(refreshContext):
|
||||||
|
snapshot = refreshContext("xterm-handoff-check")
|
||||||
|
usingI3Context = snapshot is not None and snapshot.backend_name == "i3-ipc"
|
||||||
if usingI3Context and snapshot.focused_workspace_empty is True:
|
if usingI3Context and snapshot.focused_workspace_empty is True:
|
||||||
self._record_xterm_handoff_action("i3:confirmed-empty-workspace")
|
self._record_xterm_handoff_action("i3:confirmed-empty-workspace")
|
||||||
self._lastEwmhReadWindowId = None
|
self._lastEwmhReadWindowId = None
|
||||||
|
self._lastEwmhActiveWindowId = None
|
||||||
|
self._lastEwmhXtermMatch = False
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if usingI3Context and snapshot.focused_window_id is not None:
|
if usingI3Context and snapshot.focused_window_id is not None:
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ class FakeWorkspaceBackend:
|
|||||||
self.name = name
|
self.name = name
|
||||||
self.activate_calls = []
|
self.activate_calls = []
|
||||||
self.deactivate_calls = []
|
self.deactivate_calls = []
|
||||||
|
self.refresh_calls = []
|
||||||
|
|
||||||
def is_available(self, session_type: str | None = None) -> bool:
|
def is_available(self, session_type: str | None = None) -> bool:
|
||||||
return self.available
|
return self.available
|
||||||
@@ -52,6 +53,9 @@ class FakeWorkspaceBackend:
|
|||||||
def deactivate(self, emit_signal=None) -> None:
|
def deactivate(self, emit_signal=None) -> None:
|
||||||
self.deactivate_calls.append(emit_signal)
|
self.deactivate_calls.append(emit_signal)
|
||||||
|
|
||||||
|
def refresh_context(self, reason: str) -> None:
|
||||||
|
self.refresh_calls.append(reason)
|
||||||
|
|
||||||
|
|
||||||
class CompositorStateAdapterRegressionTests(unittest.TestCase):
|
class CompositorStateAdapterRegressionTests(unittest.TestCase):
|
||||||
def setUp(self) -> None:
|
def setUp(self) -> None:
|
||||||
@@ -111,6 +115,16 @@ class CompositorStateAdapterRegressionTests(unittest.TestCase):
|
|||||||
self.assertEqual(snapshot.focused_window_title, "9 devel")
|
self.assertEqual(snapshot.focused_window_title, "9 devel")
|
||||||
self.assertFalse(snapshot.focused_workspace_empty)
|
self.assertFalse(snapshot.focused_workspace_empty)
|
||||||
|
|
||||||
|
def test_refresh_workspace_context_queries_selected_backend(self) -> None:
|
||||||
|
backend = FakeWorkspaceBackend(True, "i3-ipc")
|
||||||
|
adapter = compositor_state_adapter.CompositorStateAdapter(workspace_backends=[backend])
|
||||||
|
adapter.activate()
|
||||||
|
|
||||||
|
snapshot = adapter.refresh_workspace_context("xterm-handoff-check")
|
||||||
|
|
||||||
|
self.assertEqual(backend.refresh_calls, ["xterm-handoff-check"])
|
||||||
|
self.assertIs(snapshot, adapter.get_snapshot())
|
||||||
|
|
||||||
def test_confirmed_empty_workspace_clears_stale_accessible_context(self) -> None:
|
def test_confirmed_empty_workspace_clears_stale_accessible_context(self) -> None:
|
||||||
backend = FakeWorkspaceBackend(True, "i3-ipc")
|
backend = FakeWorkspaceBackend(True, "i3-ipc")
|
||||||
adapter = compositor_state_adapter.CompositorStateAdapter(workspace_backends=[backend])
|
adapter = compositor_state_adapter.CompositorStateAdapter(workspace_backends=[backend])
|
||||||
|
|||||||
@@ -153,6 +153,26 @@ class I3WorkspaceBackendRegressionTests(unittest.TestCase):
|
|||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def test_public_refresh_emits_updated_context_without_transition(self) -> None:
|
||||||
|
backend = compositor_state_i3.I3WorkspaceBackend(i3ipc_module=types.SimpleNamespace())
|
||||||
|
backend._active = True
|
||||||
|
backend._emitSignal = mock.Mock()
|
||||||
|
payload = {
|
||||||
|
"focused_window_id": 0x2600003,
|
||||||
|
"focused_window_title": "Browser",
|
||||||
|
"focused_workspace_empty": False,
|
||||||
|
}
|
||||||
|
|
||||||
|
with mock.patch.object(backend, "_query_context", return_value=({"8"}, payload)):
|
||||||
|
backend.refresh_context("xterm-handoff-check")
|
||||||
|
|
||||||
|
backend._emitSignal.assert_called_once_with(
|
||||||
|
compositor_state_types.WORKSPACE_STATE_CHANGED,
|
||||||
|
{"8"},
|
||||||
|
"xterm-handoff-check",
|
||||||
|
payload,
|
||||||
|
)
|
||||||
|
|
||||||
def test_query_failure_emits_unknown_instead_of_preserving_stale_i3_context(self):
|
def test_query_failure_emits_unknown_instead_of_preserving_stale_i3_context(self):
|
||||||
backend = compositor_state_i3.I3WorkspaceBackend(i3ipc_module=types.SimpleNamespace())
|
backend = compositor_state_i3.I3WorkspaceBackend(i3ipc_module=types.SimpleNamespace())
|
||||||
backend._active = True
|
backend._active = True
|
||||||
|
|||||||
@@ -64,6 +64,16 @@ class EventManagerCompositorContextRegressionTests(unittest.TestCase):
|
|||||||
|
|
||||||
adapter.add_listener.assert_called_once_with(self.manager._handle_compositor_signal)
|
adapter.add_listener.assert_called_once_with(self.manager._handle_compositor_signal)
|
||||||
|
|
||||||
|
def test_set_compositor_state_adapter_updates_active_input_manager(self) -> None:
|
||||||
|
adapter = mock.Mock()
|
||||||
|
self.manager._inputEventManager = mock.Mock()
|
||||||
|
|
||||||
|
self.manager.set_compositor_state_adapter(adapter)
|
||||||
|
|
||||||
|
self.manager._inputEventManager.set_compositor_state_adapter.assert_called_once_with(
|
||||||
|
adapter
|
||||||
|
)
|
||||||
|
|
||||||
def test_pause_signal_updates_churn_state_and_resume_clears_it(self) -> None:
|
def test_pause_signal_updates_churn_state_and_resume_clears_it(self) -> None:
|
||||||
snapshot = compositor_state_types.DesktopContextSnapshot(session_type="wayland")
|
snapshot = compositor_state_types.DesktopContextSnapshot(session_type="wayland")
|
||||||
|
|
||||||
|
|||||||
@@ -658,6 +658,43 @@ class InputEventManagerX11FocusRegressionTests(unittest.TestCase):
|
|||||||
matcher.assert_called_once_with(0x2600003)
|
matcher.assert_called_once_with(0x2600003)
|
||||||
getEwmhWindowId.assert_not_called()
|
getEwmhWindowId.assert_not_called()
|
||||||
|
|
||||||
|
def test_xterm_match_refreshes_i3_context_before_reusing_cached_match(self) -> None:
|
||||||
|
manager = input_event_manager.InputEventManager()
|
||||||
|
staleSnapshot = compositor_state_types.DesktopContextSnapshot(
|
||||||
|
session_type="x11",
|
||||||
|
backend_name="i3-ipc",
|
||||||
|
focused_window_id=0x200000C,
|
||||||
|
focused_workspace_empty=False,
|
||||||
|
)
|
||||||
|
freshSnapshot = compositor_state_types.DesktopContextSnapshot(
|
||||||
|
session_type="x11",
|
||||||
|
backend_name="i3-ipc",
|
||||||
|
focused_window_id=0x2600003,
|
||||||
|
focused_workspace_empty=False,
|
||||||
|
)
|
||||||
|
adapter = mock.Mock()
|
||||||
|
|
||||||
|
def refresh_context(_reason: str) -> compositor_state_types.DesktopContextSnapshot:
|
||||||
|
input_event_manager.cthulhu_state.compositorSnapshot = freshSnapshot
|
||||||
|
return freshSnapshot
|
||||||
|
|
||||||
|
adapter.refresh_workspace_context.side_effect = refresh_context
|
||||||
|
input_event_manager.cthulhu_state.compositorSnapshot = staleSnapshot
|
||||||
|
manager._lastEwmhActiveWindowId = 0x200000C
|
||||||
|
manager._lastEwmhXtermMatch = True
|
||||||
|
|
||||||
|
manager.set_compositor_state_adapter(adapter)
|
||||||
|
with mock.patch.object(
|
||||||
|
manager,
|
||||||
|
"_x11_window_id_xterm_match",
|
||||||
|
return_value=False,
|
||||||
|
) as matcher:
|
||||||
|
result = manager._active_x11_window_xterm_match()
|
||||||
|
|
||||||
|
self.assertFalse(result)
|
||||||
|
adapter.refresh_workspace_context.assert_called_once_with("xterm-handoff-check")
|
||||||
|
matcher.assert_called_once_with(0x2600003)
|
||||||
|
|
||||||
def test_i3_exact_xid_initializes_x11_before_matching_xterm(self):
|
def test_i3_exact_xid_initializes_x11_before_matching_xterm(self):
|
||||||
manager = input_event_manager.InputEventManager()
|
manager = input_event_manager.InputEventManager()
|
||||||
snapshot = compositor_state_types.DesktopContextSnapshot(
|
snapshot = compositor_state_types.DesktopContextSnapshot(
|
||||||
@@ -696,6 +733,8 @@ class InputEventManagerX11FocusRegressionTests(unittest.TestCase):
|
|||||||
|
|
||||||
def test_confirmed_empty_i3_workspace_is_definitely_not_xterm(self):
|
def test_confirmed_empty_i3_workspace_is_definitely_not_xterm(self):
|
||||||
manager = input_event_manager.InputEventManager()
|
manager = input_event_manager.InputEventManager()
|
||||||
|
manager._lastEwmhActiveWindowId = 0x200000C
|
||||||
|
manager._lastEwmhXtermMatch = True
|
||||||
snapshot = compositor_state_types.DesktopContextSnapshot(
|
snapshot = compositor_state_types.DesktopContextSnapshot(
|
||||||
session_type="x11",
|
session_type="x11",
|
||||||
backend_name="i3-ipc",
|
backend_name="i3-ipc",
|
||||||
@@ -710,6 +749,8 @@ class InputEventManagerX11FocusRegressionTests(unittest.TestCase):
|
|||||||
result = manager._active_x11_window_xterm_match()
|
result = manager._active_x11_window_xterm_match()
|
||||||
|
|
||||||
self.assertFalse(result)
|
self.assertFalse(result)
|
||||||
|
self.assertIsNone(manager._lastEwmhActiveWindowId)
|
||||||
|
self.assertFalse(manager._lastEwmhXtermMatch)
|
||||||
getEwmhWindowId.assert_not_called()
|
getEwmhWindowId.assert_not_called()
|
||||||
matcher.assert_not_called()
|
matcher.assert_not_called()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user