diff --git a/src/cthulhu/event_manager.py b/src/cthulhu/event_manager.py index 321690a..7d177c4 100644 --- a/src/cthulhu/event_manager.py +++ b/src/cthulhu/event_manager.py @@ -251,7 +251,7 @@ class EventManager: liveAttr = AXObject.get_attribute(obj, 'live') containerLive = AXObject.get_attribute(obj, 'container-live') except Exception: - return False + return isinstance(obj, Atspi.Accessible) if liveAttr in ('assertive', 'polite') or containerLive in ('assertive', 'polite'): return True @@ -333,7 +333,11 @@ class EventManager: return self.RELEVANCE_KEEP if event.type.startswith("object:state-changed:focused"): - return self.RELEVANCE_KEEP + if event.detail1 or cthulhu_state.locusOfFocus is None: + return self.RELEVANCE_KEEP + if self._eventTouchesCurrentFocusContext(event): + return self.RELEVANCE_KEEP + return self.RELEVANCE_DROP role = self._eventRoleForRelevance(event) eventGroup = self._relevanceEventGroup(event, role) diff --git a/tests/test_event_manager_relevance_gate_regressions.py b/tests/test_event_manager_relevance_gate_regressions.py index 650c6eb..dabf965 100644 --- a/tests/test_event_manager_relevance_gate_regressions.py +++ b/tests/test_event_manager_relevance_gate_regressions.py @@ -140,6 +140,29 @@ class EventManagerRelevanceGateRegressionTests(unittest.TestCase): ): self.assertFalse(self.manager._ignore(event)) + def test_unfocused_focus_loss_is_dropped_when_focus_is_stable_elsewhere(self) -> None: + app = object() + focus = object() + source = object() + event = FakeEvent("object:state-changed:focused", source=source, detail1=0) + cthulhu_state.activeScript = mock.Mock(app=app) + cthulhu_state.locusOfFocus = focus + + with ( + mock.patch.object(event_manager.debug, "printMessage"), + mock.patch.object(event_manager.debug, "printTokens"), + mock.patch.object(event_manager.debug, "print_log"), + mock.patch.object(event_manager.AXObject, "get_application", return_value=app), + mock.patch.object(event_manager.AXObject, "get_name", return_value="Chromium"), + mock.patch.object(event_manager.AXObject, "get_role", return_value=event_manager.Atspi.Role.LINK), + mock.patch.object(event_manager.AXObject, "is_dead", return_value=False), + mock.patch.object(event_manager.AXUtilities, "has_no_state", return_value=False), + mock.patch.object(event_manager.AXUtilities, "is_defunct", return_value=False), + mock.patch.object(event_manager.AXUtilities, "is_notification", return_value=False), + mock.patch.object(event_manager.AXUtilities, "is_alert", return_value=False), + ): + self.assertTrue(self.manager._ignore(event)) + def test_repeated_web_children_changed_burst_is_collapsed(self) -> None: app = object() focus = object()