Clickables now present a message when loading. Focus is preserved. Woogoo

This commit is contained in:
Storm Dragon
2025-08-10 21:53:49 -04:00
parent b715e9071b
commit a80bca78d1
2 changed files with 68 additions and 6 deletions

View File

@@ -1359,9 +1359,12 @@ class Script(default.Script):
# First try the standard clickable detection
if self.utilities.isClickableElement(obj):
from cthulhu import ax_event_synthesizer
# Give immediate feedback that activation is starting
self.presentMessage("Activating...")
result = ax_event_synthesizer.AXEventSynthesizer.click_object(obj)
if result:
self.presentMessage("Element activated")
# Schedule success message after a brief delay
self._presentDelayedMessage("Element activated", 50)
# Try to restore focus to the clicked element after a brief delay
self._restoreFocusAfterClick(original_focus)
return True
@@ -1370,9 +1373,12 @@ class Script(default.Script):
from cthulhu import ax_object
if ax_object.AXObject.has_action(obj, "click"):
from cthulhu import ax_event_synthesizer
# Give immediate feedback that activation is starting
self.presentMessage("Activating...")
result = ax_event_synthesizer.AXEventSynthesizer.click_object(obj)
if result:
self.presentMessage("Element activated")
# Schedule success message after a brief delay
self._presentDelayedMessage("Element activated", 50)
# Try to restore focus to the clicked element after a brief delay
self._restoreFocusAfterClick(original_focus)
return True
@@ -1382,24 +1388,80 @@ class Script(default.Script):
def _restoreFocusAfterClick(self, original_focus):
"""Try to restore focus after a click action that may have caused DOM changes."""
try:
# Schedule focus restoration after a brief delay to allow DOM changes to settle
from gi.repository import GObject
from cthulhu import ax_object
# Store the document and caret position to restore navigation context
document = None
caret_offset = -1
try:
# Find the document root
obj = original_focus
while obj and not ax_object.AXObject.is_dead(obj):
if self.utilities.isDocument(obj):
document = obj
break
parent = ax_object.AXObject.get_parent(obj)
if parent == obj: # Avoid infinite loops
break
obj = parent
# Get current caret position if available
if document:
try:
caret_offset = ax_object.AXObject.get_caret_offset(document)
except Exception:
pass
except Exception:
pass
def restore_focus():
try:
from cthulhu import ax_object
# First try direct focus restoration if object still exists
if not ax_object.AXObject.is_dead(original_focus):
original_focus.grabFocus()
return False
# If we have document and caret info, try to restore position
if document and not ax_object.AXObject.is_dead(document) and caret_offset >= 0:
try:
# Set caret back to where it was
ax_object.AXObject.set_caret_offset(document, caret_offset)
# Focus the document to make sure screen reader tracks properly
document.grabFocus()
return False
except Exception:
pass
# Last resort: try to focus the document if we have it
if document and not ax_object.AXObject.is_dead(document):
document.grabFocus()
except Exception:
pass # Focus restoration is best effort
return False # Don't repeat the timeout
# Delay restoration to allow JavaScript and DOM changes to complete
GObject.timeout_add(100, restore_focus) # 100ms delay
GObject.timeout_add(150, restore_focus) # 150ms delay for DOM changes
except Exception:
pass # Focus restoration is best effort
def _presentDelayedMessage(self, message, delay_ms):
"""Present a message after a specified delay in milliseconds."""
try:
from gi.repository import GObject
def present_message():
self.presentMessage(message)
return False # Don't repeat
GObject.timeout_add(delay_ms, present_message)
except Exception:
# If delay fails, present immediately
self.presentMessage(message)
def activateClickableElement(self, inputEvent):
"""Activates clickable element at current focus via Return key."""
return self._tryClickableActivation(inputEvent)