Refactor complete. We should be in very good shape moving forward.

This commit is contained in:
Storm Dragon
2026-01-16 11:41:49 -05:00
parent 1862de64ee
commit 73ddc18114
7 changed files with 164 additions and 175 deletions
+1
View File
@@ -20,6 +20,7 @@ This repository is a screen reader. Prioritize accessibility, correctness, and s
- variables: `camelCase` - variables: `camelCase`
- functions/methods: `snake_case` - functions/methods: `snake_case`
- classes: `PascalCase` - classes: `PascalCase`
- Keep changes clean and well-structured; avoid layering short-term workarounds when a focused fix is possible.
- Add debug logs where helpful for troubleshooting. When adding timestamps to logs, use: `"Message [timestamp]"` (message first). - Add debug logs where helpful for troubleshooting. When adding timestamps to logs, use: `"Message [timestamp]"` (message first).
## Accessibility requirements (high priority) ## Accessibility requirements (high priority)
+21 -4
View File
@@ -255,14 +255,31 @@ def _asString(obj):
return str(obj) return str(obj)
def _format_tokens(tokens):
text = " ".join(map(_asString, tokens))
text = re.sub(r"[ \u00A0]+", " ", text)
text = re.sub(r" (?=[,.:)])(?![\n])", "", text)
return text
def format_log_message(prefix, message, reason=None):
text = f"{prefix}: {message}"
if reason:
text = f"{text} (reason={reason})"
return text
def print_log(level, prefix, message, reason=None, timestamp=False, stack=False):
text = format_log_message(prefix, message, reason)
printMessage(level, text, timestamp, stack)
def print_log_tokens(level, prefix, tokens, reason=None, timestamp=False, stack=False):
text = format_log_message(prefix, _format_tokens(tokens), reason)
printMessage(level, text, timestamp, stack)
def printTokens(level, tokens, timestamp=False, stack=False): def printTokens(level, tokens, timestamp=False, stack=False):
if level < debugLevel: if level < debugLevel:
return return
text = " ".join(map(_asString, tokens)) println(level, _format_tokens(tokens), timestamp, stack)
text = re.sub(r"[ \u00A0]+", " ", text)
text = re.sub(r" (?=[,.:)])(?![\n])", "", text)
println(level, text, timestamp, stack)
def print_tokens(level, tokens, timestamp=False, stack=False): def print_tokens(level, tokens, timestamp=False, stack=False):
return printTokens(level, tokens, timestamp, stack) return printTokens(level, tokens, timestamp, stack)
+6 -6
View File
@@ -238,12 +238,12 @@ class EventManager:
debug.printTokens(debug.LEVEL_INFO, tokens, True) debug.printTokens(debug.LEVEL_INFO, tokens, True)
def _log_ignore(reason, message): def _log_ignore(reason, message):
msg = f"EVENT MANAGER: Ignoring ({reason}) - {message}" debug.print_log(debug.LEVEL_INFO, "EVENT MANAGER",
debug.printMessage(debug.LEVEL_INFO, msg, True) f"Ignoring - {message}", reason, True)
def _log_allow(reason, message): def _log_allow(reason, message):
msg = f"EVENT MANAGER: Not ignoring ({reason}) - {message}" debug.print_log(debug.LEVEL_INFO, "EVENT MANAGER",
debug.printMessage(debug.LEVEL_INFO, msg, True) f"Not ignoring - {message}", reason, True)
def _ignore_with_reason(reason, message): def _ignore_with_reason(reason, message):
_log_ignore(reason, message) _log_ignore(reason, message)
@@ -1103,8 +1103,8 @@ class EventManager:
return return
setNewActiveScript, reason = self._isActivatableEvent(event, script) setNewActiveScript, reason = self._isActivatableEvent(event, script)
msg = f'EVENT MANAGER: Change active script: {setNewActiveScript} ({reason})' debug.print_log(debug.LEVEL_INFO, "EVENT MANAGER",
debug.printMessage(debug.LEVEL_INFO, msg, True) f"Change active script: {setNewActiveScript}", reason, True)
if setNewActiveScript: if setNewActiveScript:
try: try:
+34 -59
View File
@@ -59,6 +59,12 @@ def _get_ax_utilities():
from .ax_utilities import AXUtilities from .ax_utilities import AXUtilities
return AXUtilities return AXUtilities
def _log(message, reason=None, timestamp=True, stack=False):
debug.print_log(debug.LEVEL_INFO, "FOCUS MANAGER", message, reason, timestamp, stack)
def _log_tokens(tokens, reason=None, timestamp=True, stack=False):
debug.print_log_tokens(debug.LEVEL_INFO, "FOCUS MANAGER", tokens, reason, timestamp, stack)
if TYPE_CHECKING: if TYPE_CHECKING:
from .input_event import InputEvent from .input_event import InputEvent
from .scripts import default from .scripts import default
@@ -84,18 +90,14 @@ class FocusManager:
self._last_cursor_position: tuple[Optional[Atspi.Accessible], int] = (None, -1) self._last_cursor_position: tuple[Optional[Atspi.Accessible], int] = (None, -1)
self._penultimate_cursor_position: tuple[Optional[Atspi.Accessible], int] = (None, -1) self._penultimate_cursor_position: tuple[Optional[Atspi.Accessible], int] = (None, -1)
msg = "FOCUS MANAGER: Registering D-Bus commands." _log("Registering D-Bus commands.")
debug.print_message(debug.LEVEL_INFO, msg, True)
controller = dbus_service.get_remote_controller() controller = dbus_service.get_remote_controller()
controller.register_decorated_module("FocusManager", self) controller.register_decorated_module("FocusManager", self)
def clear_state(self, reason: str = "") -> None: def clear_state(self, reason: str = "") -> None:
"""Clears everything we're tracking.""" """Clears everything we're tracking."""
msg = "FOCUS MANAGER: Clearing all state" _log("Clearing all state", reason)
if reason:
msg += f": {reason}"
debug.print_message(debug.LEVEL_INFO, msg, True)
self._focus = None self._focus = None
self._window = None self._window = None
self._object_of_interest = None self._object_of_interest = None
@@ -109,8 +111,7 @@ class FocusManager:
"""Returns the focused object in the active window.""" """Returns the focused object in the active window."""
result = _get_ax_utilities().get_focused_object(self._window) result = _get_ax_utilities().get_focused_object(self._window)
tokens = ["FOCUS MANAGER: Focused object in", self._window, "is", result] _log_tokens(["Focused object in", self._window, "is", result])
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
return result return result
def focus_and_window_are_unknown(self) -> bool: def focus_and_window_are_unknown(self) -> bool:
@@ -118,8 +119,7 @@ class FocusManager:
result = self._focus is None and self._window is None result = self._focus is None and self._window is None
if result: if result:
msg = "FOCUS MANAGER: Focus and window are unknown" _log("Focus and window are unknown")
debug.print_message(debug.LEVEL_INFO, msg, True)
return result return result
@@ -129,8 +129,7 @@ class FocusManager:
if not AXObject.is_dead(self._focus): if not AXObject.is_dead(self._focus):
return False return False
msg = "FOCUS MANAGER: Focus is dead" _log("Focus is dead", "dead-focus")
debug.print_message(debug.LEVEL_INFO, msg, True)
return True return True
def focus_is_active_window(self) -> bool: def focus_is_active_window(self) -> bool:
@@ -165,8 +164,7 @@ class FocusManager:
obj.emit("mode-changed::" + mode, 1, "") obj.emit("mode-changed::" + mode, 1, "")
if mode != self._active_mode: if mode != self._active_mode:
tokens = ["FOCUS MANAGER: Switching mode from", self._active_mode, "to", mode] _log_tokens(["Switching mode from", self._active_mode, "to", mode])
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
self._active_mode = mode self._active_mode = mode
cthulhu_state.activeMode = mode cthulhu_state.activeMode = mode
if mode == FLAT_REVIEW: if mode == FLAT_REVIEW:
@@ -174,15 +172,12 @@ class FocusManager:
else: else:
braille.setBrlapiPriority() braille.setBrlapiPriority()
tokens = ["FOCUS MANAGER: Region of interest:", obj, f"({start_offset}, {end_offset})"] _log_tokens(["Region of interest:", obj, f"({start_offset}, {end_offset})"])
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
if obj is not None: if obj is not None:
obj.emit("region-changed", start_offset, end_offset) obj.emit("region-changed", start_offset, end_offset)
if obj != self._object_of_interest: if obj != self._object_of_interest:
tokens = ["FOCUS MANAGER: Switching object of interest from", _log_tokens(["Switching object of interest from", self._object_of_interest, "to", obj])
self._object_of_interest, "to", obj]
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
self._object_of_interest = obj self._object_of_interest = obj
cthulhu_state.objOfInterest = obj cthulhu_state.objOfInterest = obj
@@ -196,32 +191,27 @@ class FocusManager:
) -> tuple[Optional[str], Optional[Atspi.Accessible]]: ) -> tuple[Optional[str], Optional[Atspi.Accessible]]:
"""Returns the current mode and associated object of interest""" """Returns the current mode and associated object of interest"""
tokens = ["FOCUS MANAGER: Active mode:", self._active_mode, _log_tokens(["Active mode:", self._active_mode, "Object of interest:", self._object_of_interest])
"Object of interest:", self._object_of_interest]
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
return self._active_mode, self._object_of_interest return self._active_mode, self._object_of_interest
def get_penultimate_cursor_position(self) -> tuple[Optional[Atspi.Accessible], int]: def get_penultimate_cursor_position(self) -> tuple[Optional[Atspi.Accessible], int]:
"""Returns the penultimate cursor position as a tuple of (object, offset).""" """Returns the penultimate cursor position as a tuple of (object, offset)."""
obj, offset = self._penultimate_cursor_position obj, offset = self._penultimate_cursor_position
tokens = ["FOCUS MANAGER: Penultimate cursor position:", obj, offset] _log_tokens(["Penultimate cursor position:", obj, offset])
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
return obj, offset return obj, offset
def get_last_cursor_position(self) -> tuple[Optional[Atspi.Accessible], int]: def get_last_cursor_position(self) -> tuple[Optional[Atspi.Accessible], int]:
"""Returns the last cursor position as a tuple of (object, offset).""" """Returns the last cursor position as a tuple of (object, offset)."""
obj, offset = self._last_cursor_position obj, offset = self._last_cursor_position
tokens = ["FOCUS MANAGER: Last cursor position:", obj, offset] _log_tokens(["Last cursor position:", obj, offset])
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
return obj, offset return obj, offset
def set_last_cursor_position(self, obj: Optional[Atspi.Accessible], offset: int) -> None: def set_last_cursor_position(self, obj: Optional[Atspi.Accessible], offset: int) -> None:
"""Sets the last cursor position as a tuple of (object, offset).""" """Sets the last cursor position as a tuple of (object, offset)."""
tokens = ["FOCUS MANAGER: Setting last cursor position to", obj, offset] _log_tokens(["Setting last cursor position to", obj, offset])
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
self._penultimate_cursor_position = self._last_cursor_position self._penultimate_cursor_position = self._last_cursor_position
self._last_cursor_position = obj, offset self._last_cursor_position = obj, offset
@@ -229,22 +219,19 @@ class FocusManager:
"""Returns the last known cell coordinates as a tuple of (row, column).""" """Returns the last known cell coordinates as a tuple of (row, column)."""
row, column = self._last_cell_coordinates row, column = self._last_cell_coordinates
msg = f"FOCUS MANAGER: Last known cell coordinates: row={row}, column={column}" _log(f"Last known cell coordinates: row={row}, column={column}")
debug.print_message(debug.LEVEL_INFO, msg, True)
return row, column return row, column
def set_last_cell_coordinates(self, row: int, column: int) -> None: def set_last_cell_coordinates(self, row: int, column: int) -> None:
"""Sets the last known cell coordinates as a tuple of (row, column).""" """Sets the last known cell coordinates as a tuple of (row, column)."""
msg = f"FOCUS MANAGER: Setting last cell coordinates to row={row}, column={column}" _log(f"Setting last cell coordinates to row={row}, column={column}")
debug.print_message(debug.LEVEL_INFO, msg, True)
self._last_cell_coordinates = row, column self._last_cell_coordinates = row, column
def get_locus_of_focus(self) -> Optional[Atspi.Accessible]: def get_locus_of_focus(self) -> Optional[Atspi.Accessible]:
"""Returns the current locus of focus (i.e. the object with visual focus).""" """Returns the current locus of focus (i.e. the object with visual focus)."""
tokens = ["FOCUS MANAGER: Locus of focus is", self._focus] _log_tokens(["Locus of focus is", self._focus])
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
return self._focus return self._focus
def set_locus_of_focus( def set_locus_of_focus(
@@ -256,8 +243,7 @@ class FocusManager:
) -> None: ) -> None:
"""Sets the locus of focus (i.e., the object with visual focus).""" """Sets the locus of focus (i.e., the object with visual focus)."""
tokens = ["FOCUS MANAGER: Request to set locus of focus to", obj] _log_tokens(["Request to set locus of focus to", obj], stack=True)
debug.print_tokens(debug.LEVEL_INFO, tokens, True, True)
# We clear the cache on the locus of focus because too many apps and toolkits fail # We clear the cache on the locus of focus because too many apps and toolkits fail
# to emit the correct accessibility events. We do so recursively on table cells # to emit the correct accessibility events. We do so recursively on table cells
@@ -265,8 +251,7 @@ class FocusManager:
recursive = _get_ax_utilities().is_table_cell(obj) recursive = _get_ax_utilities().is_table_cell(obj)
AXObject.clear_cache(obj, recursive, "Setting locus of focus.") AXObject.clear_cache(obj, recursive, "Setting locus of focus.")
if not force and obj == self._focus: if not force and obj == self._focus:
msg = "FOCUS MANAGER: Setting locus of focus to existing locus of focus" _log("Setting locus of focus to existing locus of focus", "no-change")
debug.print_message(debug.LEVEL_INFO, msg, True)
return return
# We save the current row and column of a newly focused or selected table cell so that on # We save the current row and column of a newly focused or selected table cell so that on
@@ -298,26 +283,21 @@ class FocusManager:
old_focus = None old_focus = None
if obj is None: if obj is None:
msg = "FOCUS MANAGER: New locus of focus is null (being cleared)" _log("New locus of focus is null (being cleared)", "clearing")
debug.print_message(debug.LEVEL_INFO, msg, True)
self._focus = None self._focus = None
cthulhu_state.locusOfFocus = None cthulhu_state.locusOfFocus = None
return return
if AXObject.is_dead(obj): if AXObject.is_dead(obj):
tokens = ["FOCUS MANAGER: New locus of focus (", obj, ") is dead. Not updating."] _log_tokens(["New locus of focus", obj, "is dead. Not updating."], "dead")
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
return return
if script is not None: if script is not None:
if not AXObject.is_valid(obj): if not AXObject.is_valid(obj):
tokens = ["FOCUS MANAGER: New locus of focus (", obj, ") is invalid. Not updating."] _log_tokens(["New locus of focus", obj, "is invalid. Not updating."], "invalid")
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
return return
tokens = ["FOCUS MANAGER: Changing locus of focus from", old_focus, _log_tokens(["Changing locus of focus from", old_focus, "to", obj, ". Notify:", notify_script])
"to", obj, ". Notify:", notify_script]
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
self._focus = obj self._focus = obj
cthulhu_state.locusOfFocus = obj cthulhu_state.locusOfFocus = obj
@@ -325,8 +305,7 @@ class FocusManager:
return return
if script is None: if script is None:
msg = "FOCUS MANAGER: Cannot notify active script because there isn't one" _log("Cannot notify active script because there isn't one", "no-active-script")
debug.print_message(debug.LEVEL_INFO, msg, True)
return return
self.emit_region_changed(obj, mode=FOCUS_TRACKING) self.emit_region_changed(obj, mode=FOCUS_TRACKING)
@@ -337,15 +316,13 @@ class FocusManager:
AXObject.clear_cache(self._window, False, "Ensuring the active window is really active.") AXObject.clear_cache(self._window, False, "Ensuring the active window is really active.")
is_active = _get_ax_utilities().is_active(self._window) is_active = _get_ax_utilities().is_active(self._window)
tokens = ["FOCUS MANAGER:", self._window, "is active:", is_active] _log_tokens([self._window, "is active:", is_active])
debug.print_tokens(debug.LEVEL_INFO, tokens, True)
return is_active return is_active
def get_active_window(self) -> Optional[Atspi.Accessible]: def get_active_window(self) -> Optional[Atspi.Accessible]:
"""Returns the currently-active window (i.e. without searching or verifying).""" """Returns the currently-active window (i.e. without searching or verifying)."""
tokens = ["FOCUS MANAGER: Active window is", self._window] _log_tokens(["Active window is", self._window], stack=True)
debug.print_tokens(debug.LEVEL_INFO, tokens, True, True)
return self._window return self._window
def set_active_window( def set_active_window(
@@ -357,14 +334,13 @@ class FocusManager:
) -> None: ) -> None:
"""Sets the active window.""" """Sets the active window."""
tokens = ["FOCUS MANAGER: Request to set active window to", frame] tokens = ["Request to set active window to", frame]
if app is not None: if app is not None:
tokens.extend(["in", app]) tokens.extend(["in", app])
debug.print_tokens(debug.LEVEL_INFO, tokens, True) _log_tokens(tokens)
if frame == self._window: if frame == self._window:
msg = "FOCUS MANAGER: Setting active window to existing active window" _log("Setting active window to existing active window", "no-change")
debug.print_message(debug.LEVEL_INFO, msg, True)
elif frame is None: elif frame is None:
self._window = None self._window = None
cthulhu_state.activeWindow = None cthulhu_state.activeWindow = None
@@ -375,8 +351,7 @@ class FocusManager:
if set_window_as_focus: if set_window_as_focus:
self.set_locus_of_focus(None, self._window, notify_script) self.set_locus_of_focus(None, self._window, notify_script)
elif not (self.focus_is_active_window() or self.focus_is_in_active_window()): elif not (self.focus_is_active_window() or self.focus_is_in_active_window()):
tokens = ["FOCUS MANAGER: Focus", self._focus, "is not in", self._window] _log_tokens(["Focus", self._focus, "is not in", self._window], stack=True)
debug.print_tokens(debug.LEVEL_INFO, tokens, True, True)
# Don't update the focus to the active window if we can't get to the active window # Don't update the focus to the active window if we can't get to the active window
# from the focused object. https://bugreports.qt.io/browse/QTBUG-130116 # from the focused object. https://bugreports.qt.io/browse/QTBUG-130116
+21 -7
View File
@@ -796,8 +796,12 @@ class KeyboardEvent(InputEvent):
"""Returns True if this event should be consumed.""" """Returns True if this event should be consumed."""
# Debug logging to understand handler matching # Debug logging to understand handler matching
debugMsg = f"shouldConsume: key='{self.event_string}' hw_code={self.hw_code} modifiers={self.modifiers}" debug.print_log(
debug.printMessage(debug.LEVEL_INFO, debugMsg, True) debug.LEVEL_INFO,
"INPUT EVENT",
f"shouldConsume: key='{self.event_string}' hw_code={self.hw_code} modifiers={self.modifiers}",
timestamp=True,
)
if not self.timestamp: if not self.timestamp:
return False, 'No timestamp' return False, 'No timestamp'
@@ -810,10 +814,14 @@ class KeyboardEvent(InputEvent):
self._script = script_manager.get_manager().get_default_script() self._script = script_manager.get_manager().get_default_script()
globalHandlerUsed = True globalHandlerUsed = True
else: else:
debug.printMessage(debug.LEVEL_INFO, "shouldConsume: No active script", True) debug.print_log(debug.LEVEL_INFO, "INPUT EVENT",
"shouldConsume: No active script",
reason="no-active-script", timestamp=True)
return False, 'No active script when received' return False, 'No active script when received'
debug.printMessage(debug.LEVEL_INFO, f"shouldConsume: Active script={self._script.__class__.__name__}", True) debug.print_log(debug.LEVEL_INFO, "INPUT EVENT",
f"shouldConsume: Active script={self._script.__class__.__name__}",
timestamp=True)
if self.is_duplicate: if self.is_duplicate:
return False, 'Is duplicate' return False, 'Is duplicate'
@@ -827,15 +835,21 @@ class KeyboardEvent(InputEvent):
globalHandlerUsed = globalHandlerUsed or self._resolveHandler() globalHandlerUsed = globalHandlerUsed or self._resolveHandler()
if self._handler: if self._handler:
debug.printMessage(debug.LEVEL_INFO, f"shouldConsume: Handler found: {self._handler.description}", True) debug.print_log(debug.LEVEL_INFO, "INPUT EVENT",
f"shouldConsume: Handler found: {self._handler.description}",
timestamp=True)
else: else:
debug.printMessage(debug.LEVEL_INFO, "shouldConsume: No handler found", True) debug.print_log(debug.LEVEL_INFO, "INPUT EVENT",
"shouldConsume: No handler found",
reason="no-handler", timestamp=True)
self._script.updateKeyboardEventState(self, self._handler) self._script.updateKeyboardEventState(self, self._handler)
scriptConsumes = self._script.shouldConsumeKeyboardEvent(self, self._handler) scriptConsumes = self._script.shouldConsumeKeyboardEvent(self, self._handler)
if globalHandlerUsed: if globalHandlerUsed:
scriptConsumes = True scriptConsumes = True
debug.printMessage(debug.LEVEL_INFO, f"shouldConsume: scriptConsumes={scriptConsumes}", True) debug.print_log(debug.LEVEL_INFO, "INPUT EVENT",
f"shouldConsume: scriptConsumes={scriptConsumes}",
timestamp=True)
if self._isReleaseForLastNonModifierKeyEvent(): if self._isReleaseForLastNonModifierKeyEvent():
return scriptConsumes, 'Is release for last non-modifier keyevent' return scriptConsumes, 'Is release for last non-modifier keyevent'
+34 -46
View File
@@ -41,10 +41,16 @@ def _get_ax_utilities():
from .ax_utilities import AXUtilities from .ax_utilities import AXUtilities
return AXUtilities return AXUtilities
def _log(message, reason=None, timestamp=True, stack=False):
debug.print_log(debug.LEVEL_INFO, "SCRIPT MANAGER", message, reason, timestamp, stack)
def _log_tokens(tokens, reason=None, timestamp=True, stack=False):
debug.print_log_tokens(debug.LEVEL_INFO, "SCRIPT MANAGER", tokens, reason, timestamp, stack)
class ScriptManager: class ScriptManager:
def __init__(self, app): # Added app argument def __init__(self, app): # Added app argument
debug.printMessage(debug.LEVEL_INFO, "SCRIPT MANAGER: Initializing", True) _log("Initializing")
self.app = app # Store app instance self.app = app # Store app instance
self.appScripts = {} self.appScripts = {}
self.toolkitScripts = {} self.toolkitScripts = {}
@@ -74,22 +80,22 @@ class ScriptManager:
self.set_active_script(None, "lifecycle: init") self.set_active_script(None, "lifecycle: init")
self._active = False self._active = False
debug.printMessage(debug.LEVEL_INFO, "SCRIPT MANAGER: Initialized", True) _log("Initialized")
def activate(self): def activate(self):
"""Called when this script manager is activated.""" """Called when this script manager is activated."""
debug.printMessage(debug.LEVEL_INFO, "SCRIPT MANAGER: Activating", True) _log("Activating")
self._defaultScript = self.get_script(None) self._defaultScript = self.get_script(None)
self._defaultScript.registerEventListeners() self._defaultScript.registerEventListeners()
self.set_active_script(self._defaultScript, "lifecycle: activate") self.set_active_script(self._defaultScript, "lifecycle: activate")
self._active = True self._active = True
debug.printMessage(debug.LEVEL_INFO, "SCRIPT MANAGER: Activated", True) _log("Activated")
def deactivate(self): def deactivate(self):
"""Called when this script manager is deactivated.""" """Called when this script manager is deactivated."""
debug.printMessage(debug.LEVEL_INFO, "SCRIPT MANAGER: Deactivating", True) _log("Deactivating")
if self._defaultScript: if self._defaultScript:
self._defaultScript.deregisterEventListeners() self._defaultScript.deregisterEventListeners()
self._defaultScript = None self._defaultScript = None
@@ -98,20 +104,18 @@ class ScriptManager:
self.toolkitScripts = {} self.toolkitScripts = {}
self.customScripts = {} self.customScripts = {}
self._active = False self._active = False
debug.printMessage(debug.LEVEL_INFO, "SCRIPT MANAGER: Deactivated", True) _log("Deactivated")
def get_module_name(self, app): def get_module_name(self, app):
"""Returns the module name of the script to use for application app.""" """Returns the module name of the script to use for application app."""
if app is None: if app is None:
msg = "SCRIPT MANAGER: Cannot get module name for null app" _log("Cannot get module name for null app", "null-app")
debug.printMessage(debug.LEVEL_INFO, msg, True)
return None return None
name = AXObject.get_name(app) name = AXObject.get_name(app)
if not name: if not name:
msg = "SCRIPT MANAGER: Cannot get module name for nameless app" _log("Cannot get module name for nameless app", "nameless-app")
debug.printMessage(debug.LEVEL_INFO, msg, True)
return None return None
pid = AXObject.get_process_id(app) pid = AXObject.get_process_id(app)
@@ -136,8 +140,7 @@ class ScriptManager:
name = names[0] name = names[0]
break break
tokens = ["SCRIPT MANAGER: Mapped", app, "to", name] _log_tokens(["Mapped", app, "to", name])
debug.printTokens(debug.LEVEL_INFO, tokens, True)
return name return name
def _toolkit_for_object(self, obj): def _toolkit_for_object(self, obj):
@@ -169,8 +172,7 @@ class ScriptManager:
except OSError: except OSError:
debug.examineProcesses() debug.examineProcesses()
tokens = ["SCRIPT MANAGER: Found", moduleName] _log_tokens(["Found", moduleName])
debug.printTokens(debug.LEVEL_INFO, tokens, True)
try: try:
if hasattr(module, 'get_script'): if hasattr(module, 'get_script'):
script = module.get_script(app) script = module.get_script(app)
@@ -178,8 +180,7 @@ class ScriptManager:
script = module.Script(app) script = module.Script(app)
break break
except Exception as error: except Exception as error:
tokens = ["SCRIPT MANAGER: Could not load", moduleName, ":", error] _log_tokens(["Could not load", moduleName, ":", error], "load-failed")
debug.printTokens(debug.LEVEL_INFO, tokens, True)
return script return script
@@ -202,8 +203,7 @@ class ScriptManager:
if not script: if not script:
script = self.get_default_script(app) script = self.get_default_script(app)
tokens = ["SCRIPT MANAGER: Default script created for", app, "(obj: ", obj, ")"] _log_tokens(["Default script created for", app, "(obj:", obj, ")"])
debug.printTokens(debug.LEVEL_INFO, tokens, True)
return script return script
@@ -230,14 +230,12 @@ class ScriptManager:
if newScript: if newScript:
return newScript return newScript
tokens = ["WARNING: Failed to get a replacement script for", script.app] _log_tokens(["Failed to get a replacement script for", script.app], "replacement-missing")
debug.printTokens(debug.LEVEL_INFO, tokens, True)
return script return script
def get_script_for_mouse_button_event(self, event): def get_script_for_mouse_button_event(self, event):
isActive = _get_ax_utilities().is_active(cthulhu_state.activeWindow) isActive = _get_ax_utilities().is_active(cthulhu_state.activeWindow)
tokens = ["SCRIPT MANAGER:", cthulhu_state.activeWindow, "is active:", isActive] _log_tokens([cthulhu_state.activeWindow, "is active:", isActive])
debug.printTokens(debug.LEVEL_INFO, tokens, True)
if isActive and cthulhu_state.activeScript: if isActive and cthulhu_state.activeScript:
return cthulhu_state.activeScript return cthulhu_state.activeScript
@@ -297,8 +295,7 @@ class ScriptManager:
appScript = self._create_script(app, None) appScript = self._create_script(app, None)
self.appScripts[app] = appScript self.appScripts[app] = appScript
except Exception as error: except Exception as error:
tokens = ["SCRIPT MANAGER: Exception getting app script for", app, ":", error] _log_tokens(["Exception getting app script for", app, ":", error], "app-script-error")
debug.printTokens(debug.LEVEL_INFO, tokens, True)
appScript = self.get_default_script() appScript = self.get_default_script()
if customScript: if customScript:
@@ -351,8 +348,7 @@ class ScriptManager:
from . import cthulhu from . import cthulhu
cthulhu.cthulhuApp.getSignalManager().emitSignal('active-script-changed', newScript) cthulhu.cthulhuApp.getSignalManager().emitSignal('active-script-changed', newScript)
tokens = ["SCRIPT MANAGER: Setting active script to", newScript, "reason:", reason] _log_tokens(["Setting active script to", newScript], reason)
debug.printTokens(debug.LEVEL_INFO, tokens, True)
self._log_active_state(reason) self._log_active_state(reason)
def activate_script_for_context(self, app, obj, reason=None): def activate_script_for_context(self, app, obj, reason=None):
@@ -361,14 +357,12 @@ class ScriptManager:
return script return script
def _log_active_state(self, reason=None): def _log_active_state(self, reason=None):
tokens = [ _log_tokens(
"SCRIPT MANAGER: Active state:", ["Active state:", "window", cthulhu_state.activeWindow,
"window", cthulhu_state.activeWindow, "focus", cthulhu_state.locusOfFocus,
"focus", cthulhu_state.locusOfFocus, "script", cthulhu_state.activeScript],
"script", cthulhu_state.activeScript, reason
"reason", reason )
]
debug.printTokens(debug.LEVEL_INFO, tokens, True)
def _get_script_for_app_replicant(self, app): def _get_script_for_app_replicant(self, app):
if not self._active: if not self._active:
@@ -385,8 +379,7 @@ class ScriptManager:
if a != app and _get_ax_utilities().is_application_in_desktop(a): if a != app and _get_ax_utilities().is_application_in_desktop(a):
if script.app is None: if script.app is None:
script.app = a script.app = a
tokens = ["SCRIPT MANAGER: Script for app replicant:", script, script.app] _log_tokens(["Script for app replicant:", script, script.app])
debug.printTokens(debug.LEVEL_INFO, tokens, True)
return script return script
return None return None
@@ -396,8 +389,7 @@ class ScriptManager:
deleting any scripts as necessary. deleting any scripts as necessary.
""" """
msg = "SCRIPT MANAGER: Checking and cleaning up scripts." _log("Checking and cleaning up scripts.")
debug.printMessage(debug.LEVEL_INFO, msg, True)
appList = list(self.appScripts.keys()) appList = list(self.appScripts.keys())
for app in appList: for app in appList:
@@ -407,21 +399,17 @@ class ScriptManager:
try: try:
appScript = self.appScripts.pop(app) appScript = self.appScripts.pop(app)
except KeyError: except KeyError:
tokens = ["SCRIPT MANAGER:", app, "not found in appScripts"] _log_tokens([app, "not found in appScripts"])
debug.printTokens(debug.LEVEL_INFO, tokens, True)
continue continue
tokens = ["SCRIPT MANAGER: Old script for app found:", appScript, appScript.app] _log_tokens(["Old script for app found:", appScript, appScript.app])
debug.printTokens(debug.LEVEL_INFO, tokens, True)
newScript = self._get_script_for_app_replicant(app) newScript = self._get_script_for_app_replicant(app)
if newScript: if newScript:
tokens = ["SCRIPT MANAGER: Transferring attributes:", newScript, newScript.app] _log_tokens(["Transferring attributes:", newScript, newScript.app])
debug.printTokens(debug.LEVEL_INFO, tokens, True)
attrs = appScript.getTransferableAttributes() attrs = appScript.getTransferableAttributes()
for attr, value in attrs.items(): for attr, value in attrs.items():
tokens = ["SCRIPT MANAGER: Setting", attr, "to", value] _log_tokens(["Setting", attr, "to", value])
debug.printTokens(debug.LEVEL_INFO, tokens, True)
setattr(newScript, attr, value) setattr(newScript, attr, value)
del appScript del appScript
+47 -53
View File
@@ -70,6 +70,12 @@ from .script_utilities import Utilities
_settingsManager = None # Removed - use cthulhu.cthulhuApp.settingsManager _settingsManager = None # Removed - use cthulhu.cthulhuApp.settingsManager
def _log(message, reason=None, timestamp=True, stack=False):
debug.print_log(debug.LEVEL_INFO, "WEB", message, reason, timestamp, stack)
def _log_tokens(tokens, reason=None, timestamp=True, stack=False):
debug.print_log_tokens(debug.LEVEL_INFO, "WEB", tokens, reason, timestamp, stack)
class Script(default.Script): class Script(default.Script):
@@ -126,14 +132,13 @@ class Script(default.Script):
def activate(self): def activate(self):
"""Called when this script is activated.""" """Called when this script is activated."""
tokens = ["WEB: Activating script for", self.app] _log_tokens(["Activating script for", self.app])
debug.printTokens(debug.LEVEL_INFO, tokens, True)
focus = cthulhu_state.locusOfFocus focus = cthulhu_state.locusOfFocus
inApp = AXObject.get_application(focus) == self.app if focus else False inApp = AXObject.get_application(focus) == self.app if focus else False
inDoc = self._focusInDocumentContent() inDoc = self._focusInDocumentContent()
suspend = not (inDoc and inApp) suspend = not (inDoc and inApp)
reason = f"script activation, not in document content in this app: {suspend}" reason = "activation-outside-document" if suspend else "activation-in-document"
self._setNavigationSuspended(suspend, reason) self._setNavigationSuspended(suspend, reason)
@@ -176,8 +181,7 @@ class Script(default.Script):
self.structuralNavigation.enabled = self._structNavWasEnabled self.structuralNavigation.enabled = self._structNavWasEnabled
self._structNavWasEnabled = None self._structNavWasEnabled = None
tokens = ["WEB: Navigation suspended:", suspend, reason] _log_tokens(["Navigation suspended:", suspend], reason)
debug.printTokens(debug.LEVEL_INFO, tokens, True)
def _focusInDocumentContent(self): def _focusInDocumentContent(self):
focus = cthulhu_state.locusOfFocus focus = cthulhu_state.locusOfFocus
@@ -902,24 +906,20 @@ class Script(default.Script):
"""Returns True if we should use focus mode in obj.""" """Returns True if we should use focus mode in obj."""
if self._focusModeIsSticky: if self._focusModeIsSticky:
msg = "WEB: Using focus mode because focus mode is sticky" _log("Using focus mode", "focus-mode-sticky")
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True return True
if self._browseModeIsSticky: if self._browseModeIsSticky:
msg = "WEB: Not using focus mode because browse mode is sticky" _log("Not using focus mode", "browse-mode-sticky")
debug.printMessage(debug.LEVEL_INFO, msg, True)
return False return False
if self.inSayAll(): if self.inSayAll():
msg = "WEB: Not using focus mode because we're in SayAll." _log("Not using focus mode", "say-all")
debug.printMessage(debug.LEVEL_INFO, msg, True)
return False return False
if not cthulhu.cthulhuApp.settingsManager.getSetting('structNavTriggersFocusMode') \ if not cthulhu.cthulhuApp.settingsManager.getSetting('structNavTriggersFocusMode') \
and self._lastCommandWasStructNav: and self._lastCommandWasStructNav:
msg = "WEB: Not using focus mode due to struct nav settings" _log("Not using focus mode", "struct-nav-settings")
debug.printMessage(debug.LEVEL_INFO, msg, True)
return False return False
if prevObj and AXObject.is_dead(prevObj): if prevObj and AXObject.is_dead(prevObj):
@@ -928,39 +928,32 @@ class Script(default.Script):
if not cthulhu.cthulhuApp.settingsManager.getSetting('caretNavTriggersFocusMode') \ if not cthulhu.cthulhuApp.settingsManager.getSetting('caretNavTriggersFocusMode') \
and self._lastCommandWasCaretNav \ and self._lastCommandWasCaretNav \
and not self.utilities.isNavigableToolTipDescendant(prevObj): and not self.utilities.isNavigableToolTipDescendant(prevObj):
msg = "WEB: Not using focus mode due to caret nav settings" _log("Not using focus mode", "caret-nav-settings")
debug.printMessage(debug.LEVEL_INFO, msg, True)
return False return False
if not cthulhu.cthulhuApp.settingsManager.getSetting('nativeNavTriggersFocusMode') \ if not cthulhu.cthulhuApp.settingsManager.getSetting('nativeNavTriggersFocusMode') \
and not (self._lastCommandWasStructNav or self._lastCommandWasCaretNav): and not (self._lastCommandWasStructNav or self._lastCommandWasCaretNav):
msg = "WEB: Not changing focus/browse mode due to native nav settings" _log("Not changing focus/browse mode", "native-nav-settings")
debug.printMessage(debug.LEVEL_INFO, msg, True)
return self._inFocusMode return self._inFocusMode
if self.utilities.isFocusModeWidget(obj): if self.utilities.isFocusModeWidget(obj):
tokens = ["WEB: Using focus mode because", obj, "is a focus mode widget"] _log_tokens(["Using focus mode for", obj], "focus-mode-widget")
debug.printTokens(debug.LEVEL_INFO, tokens, True)
return True return True
doNotToggle = AXUtilities.is_link(obj) or AXUtilities.is_radio_button(obj) doNotToggle = AXUtilities.is_link(obj) or AXUtilities.is_radio_button(obj)
if self._inFocusMode and doNotToggle and self.utilities.lastInputEventWasUnmodifiedArrow(): if self._inFocusMode and doNotToggle and self.utilities.lastInputEventWasUnmodifiedArrow():
tokens = ["WEB: Staying in focus mode due to arrowing in role of", obj] _log_tokens(["Staying in focus mode due to arrowing in role of", obj], "arrowing")
debug.printTokens(debug.LEVEL_INFO, tokens, True)
return True return True
if self._inFocusMode and self.utilities.isWebAppDescendant(obj): if self._inFocusMode and self.utilities.isWebAppDescendant(obj):
if self.utilities.forceBrowseModeForWebAppDescendant(obj): if self.utilities.forceBrowseModeForWebAppDescendant(obj):
tokens = ["WEB: Forcing browse mode for web app descendant", obj] _log_tokens(["Forcing browse mode for web app descendant", obj], "web-app-forced-browse")
debug.printTokens(debug.LEVEL_INFO, tokens, True)
return False return False
msg = "WEB: Staying in focus mode because we're inside a web application" _log("Staying in focus mode because we're inside a web application", "web-app-context")
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True return True
tokens = ["WEB: Not using focus mode for", obj, "due to lack of cause"] _log_tokens(["Not using focus mode for", obj], "no-cause")
debug.printTokens(debug.LEVEL_INFO, tokens, True)
return False return False
def speakContents(self, contents, **args): def speakContents(self, contents, **args):
@@ -1209,14 +1202,12 @@ class Script(default.Script):
if not cthulhu.cthulhuApp.settingsManager.getSetting('caretNavigationEnabled'): if not cthulhu.cthulhuApp.settingsManager.getSetting('caretNavigationEnabled'):
if debugOutput: if debugOutput:
msg = "WEB: Not using caret navigation: it's not enabled." _log("Not using caret navigation", "disabled")
debug.printMessage(debug.LEVEL_INFO, msg, True)
return False return False
if self._inFocusMode: if self._inFocusMode:
if debugOutput: if debugOutput:
msg = "WEB: Not using caret navigation: focus mode is active." _log("Not using caret navigation", "focus-mode")
debug.printMessage(debug.LEVEL_INFO, msg, True)
return False return False
inDoc = self._focusInDocumentContent() inDoc = self._focusInDocumentContent()
@@ -1226,27 +1217,29 @@ class Script(default.Script):
self._setNavigationSuspended(False, "focus confirmed in document content") self._setNavigationSuspended(False, "focus confirmed in document content")
else: else:
if debugOutput: if debugOutput:
msg = "WEB: Not using caret navigation: navigation suspended." _log("Not using caret navigation", "suspended")
debug.printMessage(debug.LEVEL_INFO, msg, True)
return False return False
if not inDoc: if not inDoc:
if debugOutput: if debugOutput:
tokens = ["WEB: Not using caret navigation: locusOfFocus", _log_tokens(
cthulhu_state.locusOfFocus, "is not in document content."] ["Not using caret navigation because locusOfFocus", cthulhu_state.locusOfFocus,
debug.printTokens(debug.LEVEL_INFO, tokens, True) "is not in document content."],
"focus-not-document"
)
return False return False
if keyboardEvent and keyboardEvent.modifiers & keybindings.SHIFT_MODIFIER_MASK: if keyboardEvent and keyboardEvent.modifiers & keybindings.SHIFT_MODIFIER_MASK:
if debugOutput: if debugOutput:
msg = "WEB: Not using caret navigation: shift was used." _log("Not using caret navigation", "shift-modifier")
debug.printMessage(debug.LEVEL_INFO, msg, True)
return False return False
if debugOutput: if debugOutput:
tokens = ["WEB: Using caret navigation: in browse mode and locusOfFocus", _log_tokens(
cthulhu_state.locusOfFocus, "is in document content."] ["Using caret navigation with locusOfFocus", cthulhu_state.locusOfFocus,
debug.printTokens(debug.LEVEL_INFO, tokens, True) "in document content."],
"enabled"
)
return True return True
def useStructuralNavigationModel(self, debugOutput=True): def useStructuralNavigationModel(self, debugOutput=True):
@@ -1254,14 +1247,12 @@ class Script(default.Script):
if not self.structuralNavigation.enabled: if not self.structuralNavigation.enabled:
if debugOutput: if debugOutput:
msg = "WEB: Not using structural navigation: it's not enabled." _log("Not using structural navigation", "disabled")
debug.printMessage(debug.LEVEL_INFO, msg, True)
return False return False
if self._inFocusMode: if self._inFocusMode:
if debugOutput: if debugOutput:
msg = "WEB: Not using structural navigation: focus mode is active." _log("Not using structural navigation", "focus-mode")
debug.printMessage(debug.LEVEL_INFO, msg, True)
return False return False
inDoc = self._focusInDocumentContent() inDoc = self._focusInDocumentContent()
@@ -1271,21 +1262,24 @@ class Script(default.Script):
self._setNavigationSuspended(False, "focus confirmed in document content") self._setNavigationSuspended(False, "focus confirmed in document content")
else: else:
if debugOutput: if debugOutput:
msg = "WEB: Not using structural navigation: navigation suspended." _log("Not using structural navigation", "suspended")
debug.printMessage(debug.LEVEL_INFO, msg, True)
return False return False
if not inDoc: if not inDoc:
if debugOutput: if debugOutput:
tokens = ["WEB: Not using structural navigation: locusOfFocus", _log_tokens(
cthulhu_state.locusOfFocus, "is not in document content."] ["Not using structural navigation because locusOfFocus",
debug.printTokens(debug.LEVEL_INFO, tokens, True) cthulhu_state.locusOfFocus, "is not in document content."],
"focus-not-document"
)
return False return False
if debugOutput: if debugOutput:
tokens = ["WEB: Using structural navigation: in browse mode and locusOfFocus", _log_tokens(
cthulhu_state.locusOfFocus, "is in document content."] ["Using structural navigation with locusOfFocus", cthulhu_state.locusOfFocus,
debug.printTokens(debug.LEVEL_INFO, tokens, True) "in document content."],
"enabled"
)
return True return True
def getTextLineAtCaret(self, obj, offset=None, startOffset=None, endOffset=None): def getTextLineAtCaret(self, obj, offset=None, startOffset=None, endOffset=None):