From d51ef2ad69aad365944e1dcb7bc0fc6edc374cbc Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Tue, 7 Apr 2026 15:39:49 -0400 Subject: [PATCH] Attempt to prevent key pass through in wayland. Hopefully also improve web browsing speed. --- src/cthulhu/scripts/web/script.py | 12 +++- tests/test_web_input_regressions.py | 102 ++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 tests/test_web_input_regressions.py diff --git a/src/cthulhu/scripts/web/script.py b/src/cthulhu/scripts/web/script.py index 9916b99..bda631e 100644 --- a/src/cthulhu/scripts/web/script.py +++ b/src/cthulhu/scripts/web/script.py @@ -1632,6 +1632,17 @@ class Script(default.Script): self._inFocusMode = False self._setNavigationSuspended(True, "focus left document content") debug.printMessage(debug.LEVEL_INFO, msg, True) + oldDocument = self.utilities.getTopLevelDocumentForObject(oldFocus) + if not oldDocument and self.utilities.isDocument(oldFocus): + oldDocument = oldFocus + + if oldFocus and not oldDocument: + msg = "WEB: Not refreshing grabs because we weren't in a document before" + debug.printMessage(debug.LEVEL_INFO, msg, True) + return False + + tokens = ["WEB: Refreshing grabs because we left document", oldDocument] + debug.printTokens(debug.LEVEL_INFO, tokens, True) self.refreshKeyGrabs() return False @@ -2923,7 +2934,6 @@ class Script(default.Script): self._lastCommandWasStructNav = False self._lastCommandWasMouseButton = False self._lastMouseButtonContext = None, -1 - self.removeKeyGrabs() return False def getTransferableAttributes(self): diff --git a/tests/test_web_input_regressions.py b/tests/test_web_input_regressions.py new file mode 100644 index 0000000..0a44b2c --- /dev/null +++ b/tests/test_web_input_regressions.py @@ -0,0 +1,102 @@ +import os +import sys +import unittest +from pathlib import Path +from unittest import mock + +import gi + +os.environ.setdefault("GSETTINGS_BACKEND", "memory") + +gi.require_version("Gdk", "3.0") +gi.require_version("Gtk", "3.0") + +sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "src")) + +soundGeneratorModule = sys.modules.get("cthulhu.sound_generator") +if soundGeneratorModule is not None and not hasattr(soundGeneratorModule, "SoundGenerator"): + class _StubSoundGenerator: + pass + + soundGeneratorModule.SoundGenerator = _StubSoundGenerator + +from cthulhu.scripts.web import script as web_script + + +class WebKeyGrabRegressionTests(unittest.TestCase): + def _make_partial_script(self): + testScript = web_script.Script.__new__(web_script.Script) + testScript._lastCommandWasCaretNav = True + testScript._lastCommandWasStructNav = True + testScript._lastCommandWasMouseButton = True + testScript._lastMouseButtonContext = ("old", 7) + testScript._madeFindAnnouncement = True + testScript._inFocusMode = True + testScript._navSuspended = False + testScript.removeKeyGrabs = mock.Mock() + testScript.refreshKeyGrabs = mock.Mock() + testScript._setNavigationSuspended = mock.Mock() + testScript.utilities = mock.Mock() + testScript.utilities.isZombie.return_value = False + testScript.utilities.isDocument.return_value = False + testScript.utilities.getTopLevelDocumentForObject.return_value = None + return testScript + + def test_window_deactivate_does_not_drop_key_grabs(self): + testScript = self._make_partial_script() + + result = web_script.Script.onWindowDeactivated(testScript, mock.Mock()) + + self.assertFalse(result) + self.assertFalse(testScript._lastCommandWasCaretNav) + self.assertFalse(testScript._lastCommandWasStructNav) + self.assertFalse(testScript._lastCommandWasMouseButton) + self.assertEqual(testScript._lastMouseButtonContext, (None, -1)) + testScript.removeKeyGrabs.assert_not_called() + + def test_non_document_focus_change_without_prior_document_does_not_refresh_grabs(self): + testScript = self._make_partial_script() + oldFocus = object() + newFocus = object() + + with mock.patch("cthulhu.scripts.web.script.AXObject.is_dead", return_value=False): + result = web_script.Script.locus_of_focus_changed(testScript, None, oldFocus, newFocus) + + self.assertFalse(result) + self.assertFalse(testScript._madeFindAnnouncement) + self.assertFalse(testScript._inFocusMode) + testScript._setNavigationSuspended.assert_called_once_with( + True, + "focus left document content", + ) + testScript.refreshKeyGrabs.assert_not_called() + + def test_non_document_focus_change_refreshes_grabs_after_leaving_document(self): + testScript = self._make_partial_script() + oldFocus = object() + newFocus = object() + oldDocument = object() + + testScript.utilities.getTopLevelDocumentForObject.side_effect = ( + lambda obj: oldDocument if obj is oldFocus else None + ) + + with mock.patch("cthulhu.scripts.web.script.AXObject.is_dead", return_value=False): + result = web_script.Script.locus_of_focus_changed(testScript, None, oldFocus, newFocus) + + self.assertFalse(result) + testScript.refreshKeyGrabs.assert_called_once_with() + + def test_non_document_focus_change_refreshes_grabs_when_old_focus_missing(self): + testScript = self._make_partial_script() + newFocus = object() + + with mock.patch("cthulhu.scripts.web.script.AXObject.is_dead", return_value=False): + result = web_script.Script.locus_of_focus_changed(testScript, None, None, newFocus) + + self.assertFalse(result) + testScript.refreshKeyGrabs.assert_called_once_with() + + +if __name__ == "__main__": + unittest.main()