import sys import types import unittest from pathlib import Path from unittest import mock sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) from cthulhu import input_event_manager from cthulhu import mouse_review class FakeDevice: def __init__(self): self.add_key_watcher_calls = [] self.connect_calls = [] self.disconnect_calls = [] self.set_capabilities_calls = [] self.capabilities = 0 self.next_handler_id = 17 def add_key_watcher(self, callback, user_data=None): self.add_key_watcher_calls.append((callback, user_data)) def connect(self, signalName, callback): self.connect_calls.append((signalName, callback)) return self.next_handler_id def disconnect(self, handlerId): self.disconnect_calls.append(handlerId) def get_capabilities(self): return self.capabilities def set_capabilities(self, capabilities): self.set_capabilities_calls.append(capabilities) self.capabilities = capabilities return capabilities class FakeDeviceFactory: def __init__(self, device): self.device = device self.new_calls = 0 self.new_full_calls = 0 self.app_ids = [] def new(self): self.new_calls += 1 return self.device def new_full(self, appId): self.new_full_calls += 1 self.app_ids.append(appId) return self.device class InputEventManagerPointerMonitorTests(unittest.TestCase): def setUp(self): self.manager = input_event_manager.InputEventManager() def test_activate_device_creates_the_device_only_once(self): device = FakeDevice() deviceFactory = FakeDeviceFactory(device) fakeAtspi = types.SimpleNamespace( get_version=lambda: (2, 58, 4), Device=deviceFactory, ) with mock.patch.object(input_event_manager, "Atspi", fakeAtspi): firstDevice = self.manager.activate_device() secondDevice = self.manager.activate_device() self.assertIs(firstDevice, device) self.assertIs(secondDevice, device) self.assertEqual(deviceFactory.new_full_calls, 1) self.assertEqual(deviceFactory.new_calls, 0) self.assertEqual(deviceFactory.app_ids, ["org.stormux.Cthulhu"]) def test_enable_pointer_monitoring_returns_false_without_a_device(self): fakeAtspi = types.SimpleNamespace() with mock.patch.object(input_event_manager, "Atspi", fakeAtspi): self.assertFalse(self.manager.enable_pointer_monitoring()) def test_enable_pointer_monitoring_returns_false_when_device_capability_is_missing(self): device = FakeDevice() deviceFactory = FakeDeviceFactory(device) fakeAtspi = types.SimpleNamespace( get_version=lambda: (2, 58, 4), Device=deviceFactory, ) with mock.patch.object(input_event_manager, "Atspi", fakeAtspi): self.manager.activate_device() self.assertFalse(self.manager.enable_pointer_monitoring()) self.assertEqual(device.set_capabilities_calls, []) def test_enable_pointer_monitoring_returns_true_when_pointer_monitor_is_granted(self): device = FakeDevice() deviceFactory = FakeDeviceFactory(device) fakeDeviceCapability = types.SimpleNamespace(POINTER_MONITOR=8) fakeAtspi = types.SimpleNamespace( get_version=lambda: (2, 58, 4), Device=deviceFactory, DeviceCapability=fakeDeviceCapability, ) with mock.patch.object(input_event_manager, "Atspi", fakeAtspi): self.manager.activate_device() self.assertTrue(self.manager.enable_pointer_monitoring()) self.assertEqual(device.set_capabilities_calls, [8]) self.assertEqual(device.capabilities, 8) def test_start_and_stop_pointer_watcher_connect_and_disconnect_pointer_moved(self): device = FakeDevice() deviceFactory = FakeDeviceFactory(device) fakeAtspi = types.SimpleNamespace( get_version=lambda: (2, 58, 4), Device=deviceFactory, ) callback = mock.Mock() with mock.patch.object(input_event_manager, "Atspi", fakeAtspi): self.manager.activate_device() self.manager.start_pointer_watcher(callback) self.manager.stop_pointer_watcher() self.assertEqual(device.connect_calls, [("pointer-moved", callback)]) self.assertEqual(device.disconnect_calls, [17]) class MouseReviewBackendSelectionTests(unittest.TestCase): @staticmethod def _make_app(enabled=False): app = mock.Mock() settingsManager = mock.Mock() settingsManager.getSetting.return_value = enabled app.getSettingsManager.return_value = settingsManager app.getScriptManager.return_value = mock.Mock() return app def test_prefers_atspi_backend_when_version_and_capability_are_available(self): listener = mock.Mock() deviceManager = mock.Mock() deviceManager.enable_pointer_monitoring.return_value = True with ( mock.patch.object(mouse_review.Atspi.EventListener, "new", return_value=listener), mock.patch.object(mouse_review.Atspi, "get_version", return_value=(2, 60, 0)), mock.patch.object( mouse_review.Atspi, "DeviceCapability", new=types.SimpleNamespace(POINTER_MONITOR=8), create=True, ), mock.patch.object(mouse_review.input_event_manager, "get_manager", return_value=deviceManager), mock.patch.object(mouse_review, "Wnck", None), ): reviewer = mouse_review.MouseReviewer(self._make_app()) self.assertTrue(reviewer._useAtspi) self.assertTrue(reviewer._mouseReviewCapable) deviceManager.activate_device.assert_called_once_with() deviceManager.enable_pointer_monitoring.assert_called_once_with() def test_activate_uses_pointer_watcher_for_atspi_backend(self): listener = mock.Mock() deviceManager = mock.Mock() deviceManager.enable_pointer_monitoring.return_value = True with ( mock.patch.object(mouse_review.Atspi.EventListener, "new", return_value=listener), mock.patch.object(mouse_review.Atspi, "get_version", return_value=(2, 60, 0)), mock.patch.object( mouse_review.Atspi, "DeviceCapability", new=types.SimpleNamespace(POINTER_MONITOR=8), create=True, ), mock.patch.object(mouse_review.input_event_manager, "get_manager", return_value=deviceManager), mock.patch.object(mouse_review.cthulhu_state, "locusOfFocus", None), mock.patch.object(mouse_review, "Wnck", None), ): reviewer = mouse_review.MouseReviewer(self._make_app(enabled=False)) reviewer.activate() reviewer.deactivate() deviceManager.start_pointer_watcher.assert_called_once_with(reviewer._on_pointer_moved) deviceManager.stop_pointer_watcher.assert_called_once_with() listener.register.assert_not_called() def test_keeps_legacy_backend_when_atspi_pointer_monitor_is_unavailable(self): listener = mock.Mock() pointer = mock.Mock() seat = mock.Mock() seat.get_pointer.return_value = pointer screen = mock.Mock() screen.get_windows_stacked.return_value = [] screen.get_active_workspace.return_value = None fakeWnck = mock.Mock() fakeWnck.Screen.get_default.return_value = screen with ( mock.patch.object(mouse_review.Atspi.EventListener, "new", return_value=listener), mock.patch.object(mouse_review.Atspi, "get_version", return_value=(2, 58, 4)), mock.patch.object(mouse_review.input_event_manager, "get_manager"), mock.patch.object(mouse_review.cthulhu_state, "locusOfFocus", None), mock.patch.object(mouse_review.Gdk.Display, "get_default", return_value=mock.Mock()), mock.patch.object(mouse_review.Gdk.Display, "get_default_seat", return_value=seat), mock.patch.object(mouse_review, "Wnck", fakeWnck), ): reviewer = mouse_review.MouseReviewer(self._make_app(enabled=False)) reviewer.activate() self.assertFalse(reviewer._useAtspi) self.assertTrue(reviewer._mouseReviewCapable) listener.register.assert_called_once_with("mouse:abs") if __name__ == "__main__": unittest.main()