From 61e40b81f67bc91cfd4e8461993085e5990222a9 Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Wed, 17 Dec 2025 06:25:02 -0500 Subject: [PATCH] Fixed browser crashing on some button activations, I think. --- src/cthulhu/mouse_review.py | 18 ++++++- src/cthulhu/scripts/web/script.py | 52 +++++++++++++++++++++ src/cthulhu/scripts/web/script_utilities.py | 17 +++++-- 3 files changed, 80 insertions(+), 7 deletions(-) diff --git a/src/cthulhu/mouse_review.py b/src/cthulhu/mouse_review.py index de9ed6b..b758a81 100644 --- a/src/cthulhu/mouse_review.py +++ b/src/cthulhu/mouse_review.py @@ -509,8 +509,22 @@ class MouseReviewer: cthulhu_state.activeScript.presentMessage(msg) def _update_workspace_windows(self): - self._windows = [w for w in self._all_windows - if w.is_on_workspace(self._workspace)] + """Refresh cached windows for the current workspace.""" + + if not self._workspace: + self._windows = [] + return + + windows = [] + for w in self._all_windows: + try: + if w and w.is_on_workspace(self._workspace): + windows.append(w) + except TypeError: + # Seen when Wnck hands us None/invalid workspace references mid-switch. + continue + + self._windows = windows def _on_stacking_changed(self, screen): """Callback for Wnck's window-stacking-changed signal.""" diff --git a/src/cthulhu/scripts/web/script.py b/src/cthulhu/scripts/web/script.py index e7bb127..3222635 100644 --- a/src/cthulhu/scripts/web/script.py +++ b/src/cthulhu/scripts/web/script.py @@ -84,6 +84,8 @@ class Script(default.Script): self._inFocusMode = False self._focusModeIsSticky = False self._browseModeIsSticky = False + self._navSuspended = False + self._structNavWasEnabled = None if _settingsManager.getSetting('caretNavigationEnabled') is None: _settingsManager.setSetting('caretNavigationEnabled', True) @@ -114,6 +116,22 @@ class Script(default.Script): self.attributeNamesDict["text-align"] = "justification" self.attributeNamesDict["text-indent"] = "indent" + def activate(self): + """Called when this script is activated.""" + + tokens = ["WEB: Activating script for", self.app] + debug.printTokens(debug.LEVEL_INFO, tokens, True) + + focus = cthulhu_state.locusOfFocus + inApp = AXUtilities.get_application(focus) == self.app if focus else False + inDoc = self.utilities.inDocumentContent(focus) + suspend = not (inDoc and inApp) + reason = f"script activation, not in document content in this app: {suspend}" + + self._setNavigationSuspended(suspend, reason) + + super().activate() + def deactivate(self): """Called when this script is deactivated.""" @@ -130,8 +148,30 @@ class Script(default.Script): self._preMouseOverContext = None, -1 self._inMouseOverObject = False self.utilities.clearCachedObjects() + # Ensure navigation commands are re-enabled for the next activation. + self._setNavigationSuspended(False, "script deactivation") self.removeKeyGrabs() + def _setNavigationSuspended(self, suspend, reason=""): + """Suspend or resume navigation command handling (caret & structural).""" + + if suspend == self._navSuspended: + return + + self._navSuspended = suspend + + # Structural navigation has an enabled flag we can toggle. + if suspend: + self._structNavWasEnabled = self.structuralNavigation.enabled + self.structuralNavigation.enabled = False + else: + if self._structNavWasEnabled is not None: + self.structuralNavigation.enabled = self._structNavWasEnabled + self._structNavWasEnabled = None + + tokens = ["WEB: Navigation suspended:", suspend, reason] + debug.printTokens(debug.LEVEL_INFO, tokens, True) + def getAppKeyBindings(self): """Returns the application-specific keybindings for this script.""" @@ -1164,6 +1204,12 @@ class Script(default.Script): debug.printMessage(debug.LEVEL_INFO, msg, True) return False + if self._navSuspended: + if debugOutput: + msg = "WEB: Not using caret navigation: navigation suspended." + debug.printMessage(debug.LEVEL_INFO, msg, True) + return False + if not self.utilities.inDocumentContent(): if debugOutput: tokens = ["WEB: Not using caret navigation: locusOfFocus", @@ -1198,6 +1244,12 @@ class Script(default.Script): debug.printMessage(debug.LEVEL_INFO, msg, True) return False + if self._navSuspended: + if debugOutput: + msg = "WEB: Not using structural navigation: navigation suspended." + debug.printMessage(debug.LEVEL_INFO, msg, True) + return False + if not self.utilities.inDocumentContent(): if debugOutput: tokens = ["WEB: Not using structural navigation: locusOfFocus", diff --git a/src/cthulhu/scripts/web/script_utilities.py b/src/cthulhu/scripts/web/script_utilities.py index 795bf77..e6ab008 100644 --- a/src/cthulhu/scripts/web/script_utilities.py +++ b/src/cthulhu/scripts/web/script_utilities.py @@ -4277,7 +4277,9 @@ class Utilities(script_utilities.Utilities): Atspi.Role.LIST_BOX, Atspi.Role.PASSWORD_TEXT, Atspi.Role.RADIO_BUTTON] - rv = role in roles and not self.displayedLabel(obj) + get_disp = getattr(AXUtilities, "get_displayed_label", lambda _o: None) + displayed = get_disp(obj) or self.displayedLabel(obj) + rv = role in roles and not displayed self._shouldInferLabelFor[hash(obj)] = rv @@ -4289,6 +4291,8 @@ class Utilities(script_utilities.Utilities): return rv def displayedLabel(self, obj): + """Return the author-visible label, preferring toolkit-displayed text when available.""" + if not (obj and self.inDocumentContent(obj)): return super().displayedLabel(obj) @@ -4296,10 +4300,13 @@ class Utilities(script_utilities.Utilities): if rv is not None: return rv - labels = self.labelsForObject(obj) - strings = [AXObject.get_name(label) - or self.displayedText(label) for label in labels if label is not None] - rv = " ".join(strings) + # Prefer toolkit-provided displayed label (covers aria-labelledby/displayed text cases). + rv = getattr(AXUtilities, "get_displayed_label", lambda _o: None)(obj) or "" + if not rv: + labels = self.labelsForObject(obj) + strings = [AXObject.get_name(label) or self.displayedText(label) + for label in labels if label is not None] + rv = " ".join(strings) self._displayedLabelText[hash(obj)] = rv return rv