7 Commits

Author SHA1 Message Date
Storm Dragon d36b664319 Merge branch 'testing'
Plugins are in a much better state now, mostly working. The exception is, plugins that create a keyboard shortcut don't actually bind the shortcut. That one is turning out to be a lot harder to fix than I originally thought.
2025-04-05 16:32:17 -04:00
Storm Dragon 3f7d60763d Merge branch 'testing'
Plugins are currently broken as Cthulhu moves over to pluggy. Libpeas and pygobject no longer play nicely together after latest updates. I really did not want to make a new release yet, because it is not ready, but a screen reader that at least reads instead of crashing at launch is better than nothing.
2025-04-03 20:17:14 -04:00
Storm Dragon 6bbf3d0e67 Merge branch 'testing' latest changes merged. 2024-12-22 19:04:57 -05:00
Storm Dragon cbe3424e29 Fix the version.py file in preparation for merging. 2024-12-22 19:04:39 -05:00
Storm Dragon 327ad99e49 Preparing for stable tag release. 2024-12-18 19:49:25 -05:00
Storm Dragon c46cf1c939 Merge branch 'testing' fixed preferences GUI. 2024-12-18 19:45:59 -05:00
Storm Dragon a97bb30ed3 New version system merged. 2024-12-18 11:42:52 -05:00
15 changed files with 125 additions and 495 deletions
+1
View File
@@ -0,0 +1 @@
See http://wiki.gnome.org/Projects/Cthulhu
+2 -6
View File
@@ -1,10 +1,5 @@
# Cthulhu # Cthulhu
## Note
If you somehow stumbled across this while looking for a desktop screen reader for Linux, you most likely want [Orca](https://orca.gnome.org/) instead. Cthulhu is currently a supplemental screen reader that fills a nitch for some advanced users. E.g. some older QT based programs may work with Cthulhu, and if you use certain window managers like i3, Mozilla applications like Firefox and Thunderbird may work better.
## Introduction ## Introduction
Cthulhu is a free, open source, flexible, and extensible screen reader Cthulhu is a free, open source, flexible, and extensible screen reader
@@ -25,7 +20,7 @@ Cthulhu has the following dependencies:
* Python 3 - Python platform * Python 3 - Python platform
* pygobject-3.0 - Python bindings for the GObject library * pygobject-3.0 - Python bindings for the GObject library
* pluggy - Plugin and hook calling mechanisms for python * libpeas - GObject based Plugin engine
* gtk+-3.0 - GTK+ toolkit * gtk+-3.0 - GTK+ toolkit
* json-py - a JSON (<https://json.org/>) reader and writer in Python * json-py - a JSON (<https://json.org/>) reader and writer in Python
* python-speechd - Python bindings for Speech Dispatcher (optional) * python-speechd - Python bindings for Speech Dispatcher (optional)
@@ -35,6 +30,7 @@ Cthulhu has the following dependencies:
* py-setproctitle - Python library to set the process title (optional) * py-setproctitle - Python library to set the process title (optional)
* gstreamer-1.0 - GStreamer - Streaming media framework (optional) * gstreamer-1.0 - GStreamer - Streaming media framework (optional)
* socat - Used for self-voicing functionality. * socat - Used for self-voicing functionality.
* libpeas - For the plugin system.
You are strongly encouraged to also have the latest stable versions You are strongly encouraged to also have the latest stable versions
of AT-SPI2 and ATK. of AT-SPI2 and ATK.
+26 -66
View File
@@ -47,44 +47,46 @@ class APIHelper:
self.app = app self.app = app
self._gestureBindings = {} self._gestureBindings = {}
def registerGestureByString(self, function, name, gestureString, inputEventType='default', normalizer='cthulhu', learnModeEnabled=True, contextName=None): def registerGestureByString(self, function, name, gestureString,
"""Register a gesture by string.""" inputEventType='default', normalizer='cthulhu',
import logging learnModeEnabled=True, contextName=None):
logger = logging.getLogger(__name__) """Register a gesture by string.
logger.info(f"=== APIHelper.registerGestureByString called ===") Arguments:
logger.info(f"gestureString: {gestureString}") - function: the function to call when the gesture is performed
logger.info(f"name: {name}") - name: a human-readable name for this gesture
logger.info(f"contextName: {contextName}") - gestureString: string representation of the gesture (e.g., 'kb:cthulhu+z')
- inputEventType: the type of input event
- normalizer: the normalizer to use
- learnModeEnabled: whether this should be available in learn mode
- contextName: the context for this gesture (e.g., plugin name)
Returns the binding ID or None if registration failed
"""
if not gestureString.startswith("kb:"): if not gestureString.startswith("kb:"):
logger.warning(f"Gesture string doesn't start with 'kb:': {gestureString}")
return None return None
# Extract the key portion from the gesture string # Extract the key portion from the gesture string
key = gestureString.split(":", 1)[1] key = gestureString.split(":", 1)[1]
logger.info(f"Extracted key: {key}")
# Handle Cthulhu modifier specially # Handle Cthulhu modifier specially
if "cthulhu+" in key.lower(): if "cthulhu+" in key.lower():
from . import keybindings from . import keybindings
key_parts = key.lower().split("+") key_parts = key.lower().split("+")
logger.info(f"Key parts: {key_parts}")
# Start with the base Cthulhu modifier # Determine appropriate modifier mask
modifiers = keybindings.CTHULHU_MODIFIER_MASK modifiers = keybindings.CTHULHU_MODIFIER_MASK
# Extract the final key (without modifiers) # Extract the final key (without modifiers)
final_key = key_parts[-1] final_key = key_parts[-1]
logger.info(f"Final key: {final_key}")
# Check for additional modifiers and combine them properly # Check for additional modifiers
if "shift" in key_parts: if "shift" in key_parts:
# Use the pre-defined combined mask rather than trying to OR them
modifiers = keybindings.CTHULHU_SHIFT_MODIFIER_MASK modifiers = keybindings.CTHULHU_SHIFT_MODIFIER_MASK
logger.info(f"Using CTHULHU_SHIFT_MODIFIER_MASK: {modifiers}") elif "ctrl" in key_parts or "control" in key_parts:
else: modifiers = keybindings.CTHULHU_CTRL_MODIFIER_MASK
logger.info(f"Using CTHULHU_MODIFIER_MASK: {modifiers}") elif "alt" in key_parts:
modifiers = keybindings.CTHULHU_ALT_MODIFIER_MASK
# Create a keybinding handler # Create a keybinding handler
class GestureHandler: class GestureHandler:
@@ -93,56 +95,27 @@ class APIHelper:
self.description = description self.description = description
def __call__(self, script, inputEvent): def __call__(self, script, inputEvent):
try: return self.function(script, inputEvent)
logger.info(f"=== DisplayVersion keybinding handler called! ===")
return function(script, inputEvent)
except Exception as e:
import logging
logging.getLogger(__name__).error(f"Error in keybinding handler: {e}")
return True
handler = GestureHandler(function, name) handler = GestureHandler(function, name)
logger.info(f"Created handler: {handler}")
# Create the binding object regardless of whether there's an active script # Register the binding with the active script
# This allows plugins to define bindings that will work when a script becomes active
from . import cthulhu_state from . import cthulhu_state
from . import keybindings if cthulhu_state.activeScript:
bindings = cthulhu_state.activeScript.getKeyBindings()
binding = keybindings.KeyBinding( binding = keybindings.KeyBinding(
final_key, final_key,
keybindings.defaultModifierMask, keybindings.defaultModifierMask,
modifiers, modifiers,
handler) handler)
bindings.add(binding)
logger.info(f"Created binding: keysym={binding.keysymstring}, modifiers={binding.modifiers}, mask={binding.modifier_mask}")
# Store binding for later reference # Store binding for later reference
if contextName not in self._gestureBindings: if contextName not in self._gestureBindings:
self._gestureBindings[contextName] = [] self._gestureBindings[contextName] = []
self._gestureBindings[contextName].append(binding) self._gestureBindings[contextName].append(binding)
logger.info(f"Stored binding in context '{contextName}'")
# Only add to active script if one exists
if cthulhu_state.activeScript:
logger.info(f"Adding binding to active script: {cthulhu_state.activeScript}")
bindings = cthulhu_state.activeScript.getKeyBindings()
bindings.add(binding)
# Register key grab at the system level
grab_ids = self.app.addKeyGrab(binding)
logger.info(f"Key grab IDs: {grab_ids}")
# For later removal
if grab_ids:
binding._grab_ids = grab_ids
else:
logger.warning("No active script available - binding stored for later registration")
debug.printMessage(debug.LEVEL_INFO, f"Created binding: {binding.keysymstring} with modifiers {binding.modifiers}", True)
logger.info("=== APIHelper.registerGestureByString completed ===")
return binding return binding
else:
logger.warning(f"Key doesn't contain 'cthulhu+': {key}")
return None return None
@@ -159,17 +132,10 @@ class APIHelper:
bindings = cthulhu_state.activeScript.getKeyBindings() bindings = cthulhu_state.activeScript.getKeyBindings()
bindings.remove(binding) bindings.remove(binding)
# Remove key grab at system level # Remove from our tracking
if hasattr(binding, '_grab_ids'):
for grab_id in binding._grab_ids:
self.app.removeKeyGrab(grab_id)
# Remove from tracking
if contextName in self._gestureBindings: if contextName in self._gestureBindings:
if binding in self._gestureBindings[contextName]: if binding in self._gestureBindings[contextName]:
self._gestureBindings[contextName].remove(binding) self._gestureBindings[contextName].remove(binding)
import gi import gi
import importlib import importlib
import os import os
@@ -664,12 +630,6 @@ def loadUserSettings(script=None, inputEvent=None, skipReloadMessage=False):
_scriptManager.activate() _scriptManager.activate()
_eventManager.activate() _eventManager.activate()
# Refresh keybindings to include plugin bindings (after script manager is active)
cthulhuApp.getPluginSystemManager().refresh_active_script_keybindings()
cthulhuApp.getSignalManager().emitSignal('load-setting-begin')
# cthulhuApp.getPluginSystemManager().register_plugin_keybindings_with_active_script()
cthulhuApp.getSignalManager().emitSignal('load-setting-completed') cthulhuApp.getSignalManager().emitSignal('load-setting-completed')
debug.printMessage(debug.LEVEL_INFO, 'CTHULHU: User Settings Loaded', True) debug.printMessage(debug.LEVEL_INFO, 'CTHULHU: User Settings Loaded', True)
+2 -2
View File
@@ -23,5 +23,5 @@
# Fork of Orca Screen Reader (GNOME) # Fork of Orca Screen Reader (GNOME)
# Original source: https://gitlab.gnome.org/GNOME/orca # Original source: https://gitlab.gnome.org/GNOME/orca
version = "2025.06.05" version = "2025.04.04"
codeName = "plugins" codeName = "testing"
+1 -1
View File
@@ -298,7 +298,7 @@ def println(level, text="", timestamp=False, stack=False):
text = text.replace("\ufffc", "[OBJ]") text = text.replace("\ufffc", "[OBJ]")
if timestamp: if timestamp:
text = text.replace("\n", f"\n{' ' * 18}") text = text.replace("\n", f"\n{' ' * 18}")
text = f"{text} - {datetime.now().strftime('%H:%M:%S.%f')}" text = f"{datetime.now().strftime('%H:%M:%S.%f')} - {text}"
if stack: if stack:
text += f" {_stackAsString()}" text += f" {_stackAsString()}"
-7
View File
@@ -113,13 +113,6 @@ class EventManager:
self._processNewKeyboardEvent) self._processNewKeyboardEvent)
self.newKeyHandlingActive = True self.newKeyHandlingActive = True
# Notify plugin system that device is now available for keybinding registration
from . import cthulhu
if hasattr(cthulhu, 'cthulhuApp') and cthulhu.cthulhuApp:
plugin_manager = cthulhu.cthulhuApp.getPluginSystemManager()
if plugin_manager:
pass # plugin_manager.register_plugin_keybindings_with_active_script()
def activateLegacyKeyHandling(self): def activateLegacyKeyHandling(self):
if not self.legacyKeyHandlingActive: if not self.legacyKeyHandlingActive:
self.registerKeystrokeListener(self._processKeyboardEvent) self.registerKeystrokeListener(self._processKeyboardEvent)
+1 -59
View File
@@ -251,23 +251,6 @@ class KeyBinding:
if not self.keycode: if not self.keycode:
self.keycode = getKeycode(self.keysymstring) self.keycode = getKeycode(self.keysymstring)
# Debug logging for DisplayVersion plugin specifically
if self.keysymstring == 'v' and self.modifiers == 257:
with open('/tmp/displayversion_matches.log', 'a') as f:
f.write(f"=== DisplayVersion matches() debug ===\n")
f.write(f"Self keycode: {self.keycode}\n")
f.write(f"Self keysymstring: {self.keysymstring}\n")
f.write(f"Self modifiers: {self.modifiers}\n")
f.write(f"Self modifier_mask: {self.modifier_mask}\n")
f.write(f"Input keycode: {keycode}\n")
f.write(f"Input modifiers: {modifiers}\n")
f.write(f"Keycode match: {self.keycode == keycode}\n")
if self.keycode == keycode:
result = modifiers & self.modifier_mask
f.write(f"Modifier calculation: {modifiers} & {self.modifier_mask} = {result}\n")
f.write(f"Modifier match: {result == self.modifiers}\n")
f.write(f"Overall match: {self.keycode == keycode and (modifiers & self.modifier_mask) == self.modifiers}\n")
if self.keycode == keycode: if self.keycode == keycode:
result = modifiers & self.modifier_mask result = modifiers & self.modifier_mask
return result == self.modifiers return result == self.modifiers
@@ -487,40 +470,12 @@ class KeyBindings:
given keycode and modifiers, or None if no match exists. given keycode and modifiers, or None if no match exists.
""" """
import logging
logger = logging.getLogger(__name__)
# Check if this might be the DisplayVersion key combination
event_str = keyboardEvent.event_string if hasattr(keyboardEvent, 'event_string') else 'unknown'
if event_str.lower() == 'v':
logger.info(f"=== KeyBindings.getInputHandler: Looking for handler ===")
logger.info(f"Event string: {event_str}")
logger.info(f"Hardware code: {keyboardEvent.hw_code}")
logger.info(f"Modifiers: {keyboardEvent.modifiers}")
logger.info(f"Total keybindings to check: {len(self.keyBindings)}")
with open('/tmp/keybinding_lookup.log', 'a') as f:
f.write(f"=== Looking for 'v' key handler ===\n")
f.write(f"Event string: {event_str}\n")
f.write(f"Hardware code: {keyboardEvent.hw_code}\n")
f.write(f"Modifiers: {keyboardEvent.modifiers}\n")
f.write(f"Total keybindings: {len(self.keyBindings)}\n")
# Log all keybindings for comparison
for i, kb in enumerate(self.keyBindings):
if 'v' in kb.keysymstring.lower() or 'version' in kb.handler.description.lower():
logger.info(f"Binding {i}: keysym={kb.keysymstring}, modifiers={kb.modifiers}, mask={kb.modifier_mask}, desc={kb.handler.description}")
with open('/tmp/keybinding_lookup.log', 'a') as f:
f.write(f"Found V-related binding {i}: keysym={kb.keysymstring}, modifiers={kb.modifiers}, mask={kb.modifier_mask}, desc={kb.handler.description}\n")
matches = [] matches = []
candidates = [] candidates = []
clickCount = keyboardEvent.getClickCount() clickCount = keyboardEvent.getClickCount()
for keyBinding in self.keyBindings: for keyBinding in self.keyBindings:
if keyBinding.matches(keyboardEvent.hw_code, keyboardEvent.modifiers): if keyBinding.matches(keyboardEvent.hw_code, keyboardEvent.modifiers):
if event_str.lower() == 'v': if keyBinding.modifier_mask == keyboardEvent.modifiers and \
logger.info(f"MATCH found! keysym={keyBinding.keysymstring}, desc={keyBinding.handler.description}")
if (keyboardEvent.modifiers & keyBinding.modifier_mask) == keyBinding.modifiers and \
keyBinding.click_count == clickCount: keyBinding.click_count == clickCount:
matches.append(keyBinding) matches.append(keyBinding)
# If there's no keysymstring, it's unbound and cannot be # If there's no keysymstring, it's unbound and cannot be
@@ -529,17 +484,8 @@ class KeyBindings:
if keyBinding.keysymstring: if keyBinding.keysymstring:
candidates.append(keyBinding) candidates.append(keyBinding)
if event_str.lower() == 'v':
logger.info(f"Exact matches: {len(matches)}")
logger.info(f"Candidates: {len(candidates)}")
with open('/tmp/keybinding_lookup.log', 'a') as f:
f.write(f"Exact matches: {len(matches)}\n")
f.write(f"Candidates: {len(candidates)}\n")
self._checkMatchingBindings(keyboardEvent, matches) self._checkMatchingBindings(keyboardEvent, matches)
if matches: if matches:
if event_str.lower() == 'v':
logger.info(f"Returning exact match handler: {matches[0].handler.description}")
return matches[0].handler return matches[0].handler
if keyboardEvent.isKeyPadKeyWithNumlockOn(): if keyboardEvent.isKeyPadKeyWithNumlockOn():
@@ -553,12 +499,8 @@ class KeyBindings:
self._checkMatchingBindings(keyboardEvent, candidates) self._checkMatchingBindings(keyboardEvent, candidates)
for candidate in candidates: for candidate in candidates:
if candidate.click_count <= clickCount: if candidate.click_count <= clickCount:
if event_str.lower() == 'v':
logger.info(f"Returning candidate handler: {candidate.handler.description}")
return candidate.handler return candidate.handler
if event_str.lower() == 'v':
logger.info("No handler found!")
return None return None
def load(self, keymap, handlers): def load(self, keymap, handlers):
+1 -16
View File
@@ -47,8 +47,6 @@ class Plugin:
self.name = '' self.name = ''
self.version = '' self.version = ''
self.description = '' self.description = ''
self._bindings = None
self._gestureBindings = {}
def set_app(self, app): def set_app(self, app):
"""Set the application reference.""" """Set the application reference."""
@@ -77,16 +75,12 @@ class Plugin:
return return
logger.info(f"Deactivating plugin: {self.name}") logger.info(f"Deactivating plugin: {self.name}")
def get_bindings(self):
"""Get keybindings for this plugin. Override in subclasses."""
return self._bindings
def registerGestureByString(self, function, name, gestureString, learnModeEnabled=True): def registerGestureByString(self, function, name, gestureString, learnModeEnabled=True):
"""Register a gesture by string.""" """Register a gesture by string."""
if self.app: if self.app:
api_helper = self.app.getAPIHelper() api_helper = self.app.getAPIHelper()
if api_helper: if api_helper:
binding = api_helper.registerGestureByString( return api_helper.registerGestureByString(
function, function,
name, name,
gestureString, gestureString,
@@ -95,13 +89,4 @@ class Plugin:
learnModeEnabled, learnModeEnabled,
contextName=self.module_name contextName=self.module_name
) )
# Also store the binding locally so get_bindings() can use it
if binding:
if not self._bindings:
from . import keybindings
self._bindings = keybindings.KeyBindings()
self._bindings.add(binding)
return binding
return None return None
+1 -192
View File
@@ -117,185 +117,6 @@ class PluginSystemManager:
logger.info(f"System plugins directory: {PluginType.SYSTEM.get_root_dir()}") logger.info(f"System plugins directory: {PluginType.SYSTEM.get_root_dir()}")
logger.info(f"User plugins directory: {PluginType.USER.get_root_dir()}") logger.info(f"User plugins directory: {PluginType.USER.get_root_dir()}")
def register_plugin_global_keybindings(self, plugin):
"""Register a plugin's keybindings with all scripts."""
if not hasattr(plugin, 'get_bindings'):
return
try:
bindings = plugin.get_bindings()
if not bindings or not bindings.keyBindings:
return
logger.info(f"Registering global keybindings for plugin: {plugin.name}")
# First register with the active script
from . import cthulhu_state
if cthulhu_state.activeScript:
active_script = cthulhu_state.activeScript
for binding in bindings.keyBindings:
active_script.getKeyBindings().add(binding)
grab_ids = self.app.addKeyGrab(binding)
if grab_ids:
binding._grab_ids = grab_ids
# Store these bindings for future script changes
plugin_name = plugin.name or plugin.module_name
if not hasattr(self, '_plugin_global_bindings'):
self._plugin_global_bindings = {}
self._plugin_global_bindings[plugin_name] = bindings
# Connect to script changes to ensure bindings work with all scripts
if not hasattr(self, '_connected_to_script_changes'):
signal_manager = self.app.getSignalManager()
if signal_manager:
signal_manager.connectSignal('load-setting-completed', self._on_settings_changed, None)
self._connected_to_script_changes = True
except Exception as e:
logger.error(f"Error registering global keybindings for plugin {plugin.name}: {e}")
import traceback
logger.error(traceback.format_exc())
def refresh_active_script_keybindings(self):
"""Force active script to refresh its keybindings to include plugin bindings."""
from . import cthulhu_state
if cthulhu_state.activeScript:
active_script = cthulhu_state.activeScript
with open('/tmp/plugin_registration.log', 'a') as f:
f.write(f"=== refresh_active_script_keybindings() CALLED ===\n")
f.write(f"Active script: {active_script.name}\n")
# Force the script to recreate its keybindings to include plugin bindings
old_keybindings = active_script.keyBindings
active_script.keyBindings = active_script.getKeyBindings()
with open('/tmp/plugin_registration.log', 'a') as f:
f.write(f"Keybindings refreshed: old={len(old_keybindings.keyBindings) if old_keybindings else 0}, new={len(active_script.keyBindings.keyBindings)}\n")
def register_plugin_keybindings_with_active_script(self):
"""Register all plugin keybindings with the active script."""
logger.info("=== register_plugin_keybindings_with_active_script() CALLED ===")
with open('/tmp/plugin_registration.log', 'a') as f:
f.write("=== register_plugin_keybindings_with_active_script() CALLED ===\n")
if not PLUGGY_AVAILABLE:
logger.warning("PLUGGY not available")
with open('/tmp/plugin_registration.log', 'a') as f:
f.write("ERROR: PLUGGY not available\n")
return
from . import cthulhu_state
if not cthulhu_state.activeScript:
logger.warning("No active script available to register plugin keybindings")
with open('/tmp/plugin_registration.log', 'a') as f:
f.write("ERROR: No active script available\n")
return
active_script = cthulhu_state.activeScript
logger.info(f"Registering plugin keybindings with active script: {active_script}")
with open('/tmp/plugin_registration.log', 'a') as f:
f.write(f"Active script: {active_script}\n")
# First, register keybindings from APIHelper's stored bindings
# This is where plugin keybindings actually get stored
from . import cthulhu
api_helper = cthulhu.cthulhuApp.getAPIHelper()
if api_helper and hasattr(api_helper, '_gestureBindings'):
logger.info("=== FOUND APIHelper with _gestureBindings ===")
with open('/tmp/plugin_registration.log', 'a') as f:
f.write("=== Registering stored gesture bindings from APIHelper ===\n")
f.write(f"Total contexts: {len(api_helper._gestureBindings)}\n")
for context_name, bindings_list in api_helper._gestureBindings.items():
logger.info(f"Processing context '{context_name}' with {len(bindings_list)} bindings")
with open('/tmp/plugin_registration.log', 'a') as f:
f.write(f"Context '{context_name}': {len(bindings_list)} bindings\n")
for binding in bindings_list:
logger.info(f"Adding stored binding: {binding.keysymstring} with modifiers {binding.modifiers}")
with open('/tmp/plugin_registration.log', 'a') as f:
f.write(f" Binding: {binding.keysymstring} modifiers={binding.modifiers} desc={binding.handler.description}\n")
# Check if binding already exists to avoid duplicates
if not active_script.getKeyBindings().hasKeyBinding(binding, "keysNoMask"):
active_script.getKeyBindings().add(binding)
with open('/tmp/plugin_registration.log', 'a') as f:
f.write(f" ADDED to active script!\n")
# Force recalculation of keycode if it wasn't set when device was None
if not binding.keycode and binding.keysymstring:
from . import keybindings
binding.keycode = keybindings.getKeycode(binding.keysymstring)
# Register key grab at system level - this was missing!
grab_ids = cthulhu.addKeyGrab(binding)
if grab_ids:
binding._grab_ids = grab_ids
else:
logger.warning(f"Failed to create key grab for {binding.keysymstring} - device may not be available")
else:
logger.info(f"Binding already exists: {binding.keysymstring}")
with open('/tmp/plugin_registration.log', 'a') as f:
f.write(f" Already exists, skipped\n")
else:
logger.warning("=== NO APIHelper or no _gestureBindings found ===")
with open('/tmp/plugin_registration.log', 'a') as f:
f.write("ERROR: No APIHelper or no _gestureBindings found!\n")
if api_helper:
f.write(f"APIHelper exists but _gestureBindings: {hasattr(api_helper, '_gestureBindings')}\n")
else:
f.write("No APIHelper found!\n")
# Also check the old method for any plugins that use get_bindings()
for pluginInfo in self._plugins.values():
if not pluginInfo.loaded or not pluginInfo.instance:
continue
plugin = pluginInfo.instance
if not hasattr(plugin, 'get_bindings') or not plugin.get_bindings():
continue
logger.info(f"Registering keybindings for plugin: {plugin.name}")
bindings = plugin.get_bindings()
for binding in bindings.keyBindings:
logger.info(f"Adding binding: {binding.keysymstring} with modifiers {binding.modifiers}")
# Check if binding already exists to avoid duplicates
if not active_script.getKeyBindings().hasKeyBinding(binding, "keysNoMask"):
active_script.getKeyBindings().add(binding)
# Force recalculation of keycode if it wasn't set when device was None
if not binding.keycode and binding.keysymstring:
from . import keybindings
binding.keycode = keybindings.getKeycode(binding.keysymstring)
# Register key grab at system level - this was missing!
grab_ids = cthulhu.addKeyGrab(binding)
if grab_ids:
binding._grab_ids = grab_ids
else:
logger.warning(f"Failed to create key grab for {binding.keysymstring} - device may not be available")
def _on_settings_changed(self, app=None):
"""Re-register all plugin keybindings when settings change."""
if not hasattr(self, '_plugin_global_bindings'):
return
from . import cthulhu_state
if not cthulhu_state.activeScript:
return
active_script = cthulhu_state.activeScript
for plugin_name, bindings in self._plugin_global_bindings.items():
logger.info(f"Re-registering keybindings for plugin: {plugin_name}")
for binding in bindings.keyBindings:
# Check if binding already exists
if active_script.getKeyBindings().hasKeyBinding(binding, "keysNoMask"):
continue
active_script.getKeyBindings().add(binding)
from . import cthulhu
grab_ids = cthulhu.addKeyGrab(binding)
if grab_ids:
binding._grab_ids = grab_ids
def _setup_plugin_dirs(self): def _setup_plugin_dirs(self):
"""Ensure plugin directories exist.""" """Ensure plugin directories exist."""
os.makedirs(PluginType.SYSTEM.get_root_dir(), exist_ok=True) os.makedirs(PluginType.SYSTEM.get_root_dir(), exist_ok=True)
@@ -397,7 +218,6 @@ class PluginSystemManager:
def setActivePlugins(self, activePlugins): def setActivePlugins(self, activePlugins):
"""Set active plugins and sync their state.""" """Set active plugins and sync their state."""
logger.info(f"=== PluginSystemManager.setActivePlugins called ===")
logger.info(f"Setting active plugins: {activePlugins}") logger.info(f"Setting active plugins: {activePlugins}")
# Make sure we have scanned for plugins first # Make sure we have scanned for plugins first
@@ -412,19 +232,12 @@ class PluginSystemManager:
logger.info(f"Available plugins: {available_plugins}") logger.info(f"Available plugins: {available_plugins}")
logger.info(f"Active plugins: {self._active_plugins}") logger.info(f"Active plugins: {self._active_plugins}")
# Check specifically for DisplayVersion
if 'DisplayVersion' in self._active_plugins:
logger.info("DisplayVersion is in active plugins list!")
else:
logger.warning("DisplayVersion is NOT in active plugins list!")
# Find missing plugins # Find missing plugins
missing_plugins = [p for p in self._active_plugins if p not in available_plugins] missing_plugins = [p for p in self._active_plugins if p not in available_plugins]
if missing_plugins: if missing_plugins:
logger.warning(f"Active plugins not found: {missing_plugins}") logger.warning(f"Active plugins not found: {missing_plugins}")
self.syncAllPluginsActive() self.syncAllPluginsActive()
logger.info("=== PluginSystemManager.setActivePlugins completed ===")
def setPluginActive(self, pluginInfo, active): def setPluginActive(self, pluginInfo, active):
"""Set the active state of a plugin.""" """Set the active state of a plugin."""
@@ -513,7 +326,7 @@ class PluginSystemManager:
return False return False
module_name = pluginInfo.get_module_name() module_name = pluginInfo.get_module_name()
logger.info(f"=== PluginSystemManager.loadPlugin starting for: {module_name} ===") logger.info(f"Attempting to load plugin: {module_name}")
try: try:
# Already loaded? # Already loaded?
@@ -600,10 +413,6 @@ class PluginSystemManager:
pluginInfo.loaded = True pluginInfo.loaded = True
logger.info(f"Successfully loaded plugin: {module_name}") logger.info(f"Successfully loaded plugin: {module_name}")
# Register any global keybindings from the plugin
self.register_plugin_global_keybindings(pluginInfo.instance)
return True return True
except Exception as e: except Exception as e:
@@ -23,8 +23,3 @@
# Fork of Orca Screen Reader (GNOME) # Fork of Orca Screen Reader (GNOME)
# Original source: https://gitlab.gnome.org/GNOME/orca # Original source: https://gitlab.gnome.org/GNOME/orca
"""Clipboard plugin package."""
from .plugin import Clipboard
__all__ = ['Clipboard']
+36 -1
View File
@@ -72,7 +72,42 @@ class Clipboard(Plugin):
return return
logger.info("Deactivating Clipboard plugin") logger.info("Deactivating Clipboard plugin")
# Note: Currently no unregister method needed as keybindings are managed by APIHelper try:
# Unregister keyboard shortcut
if self.app:
api_helper = self.app.getAPIHelper()
if api_helper and hasattr(api_helper, 'unregisterShortcut'):
api_helper.unregisterShortcut('kb:cthulhu+shift+c')
logger.debug("Unregistered clipboard shortcut")
except Exception as e:
logger.error(f"Error deactivating Clipboard plugin: {e}")
"""Activate the plugin."""
# Skip if this activation call isn't for us
if plugin is not None and plugin is not self:
return
logger.info("Activating Clipboard plugin")
try:
# Register keyboard shortcut
self.registerGestureByString(self.speakClipboard, _('clipboard'), 'kb:cthulhu+shift+c')
logger.debug("Registered shortcut for clipboard")
except Exception as e:
logger.error(f"Error activating Clipboard plugin: {e}")
@cthulhu_hookimpl
def deactivate(self, plugin=None):
"""Deactivate the plugin."""
# Skip if this deactivation call isn't for us
if plugin is not None and plugin is not self:
return
logger.info("Deactivating Clipboard plugin")
try:
# Unregister keyboard shortcut
self.unregisterGestureByString('kb:cthulhu+shift+c')
logger.debug("Unregistered clipboard shortcut")
except Exception as e:
logger.error(f"Error deactivating Clipboard plugin: {e}")
def speakClipboard(self, script=None, inputEvent=None): def speakClipboard(self, script=None, inputEvent=None):
"""Present the contents of the clipboard.""" """Present the contents of the clipboard."""
+2 -49
View File
@@ -32,62 +32,16 @@ class DisplayVersion(Plugin):
return return
try: try:
logger.info("=== DisplayVersion plugin activation starting ===") logger.info("Activating DisplayVersion plugin")
logger.info(f"Plugin name: {self.name}")
logger.info(f"App reference: {self.app}")
# Also write to a debug file
with open('/tmp/displayversion_debug.log', 'a') as f:
f.write("=== DisplayVersion plugin activation starting ===\n")
f.write(f"Plugin name: {self.name}\n")
f.write(f"App reference: {self.app}\n")
# Check if we have access to the API helper
if not self.app:
logger.error("DisplayVersion: No app reference available!")
return
api_helper = self.app.getAPIHelper()
if not api_helper:
logger.error("DisplayVersion: No API helper available!")
return
logger.info(f"API helper: {api_helper}")
# Register keyboard shortcut # Register keyboard shortcut
gesture_string = 'kb:cthulhu+shift+v'
logger.info(f"DisplayVersion: Attempting to register gesture: {gesture_string}")
self._kb_binding = self.registerGestureByString( self._kb_binding = self.registerGestureByString(
self.speakText, self.speakText,
f'Cthulhu screen reader version {cthulhuVersion.version}-{cthulhuVersion.codeName}', f'Cthulhu screen reader version {cthulhuVersion.version}-{cthulhuVersion.codeName}',
gesture_string 'kb:cthulhu+shift+v'
) )
logger.info(f"DisplayVersion: Registered keybinding result: {self._kb_binding}")
if self._kb_binding:
logger.info(f"Binding keysymstring: {self._kb_binding.keysymstring}")
logger.info(f"Binding modifiers: {self._kb_binding.modifiers}")
logger.info(f"Binding modifier_mask: {self._kb_binding.modifier_mask}")
logger.info(f"Binding handler: {self._kb_binding.handler}")
with open('/tmp/displayversion_debug.log', 'a') as f:
f.write(f"SUCCESS: Keybinding created!\n")
f.write(f" keysymstring: {self._kb_binding.keysymstring}\n")
f.write(f" modifiers: {self._kb_binding.modifiers}\n")
f.write(f" modifier_mask: {self._kb_binding.modifier_mask}\n")
else:
logger.error("DisplayVersion: Failed to create keybinding!")
with open('/tmp/displayversion_debug.log', 'a') as f:
f.write("ERROR: Failed to create keybinding!\n")
logger.info("=== DisplayVersion plugin activation completed ===")
with open('/tmp/displayversion_debug.log', 'a') as f:
f.write("=== DisplayVersion plugin activation completed ===\n\n")
except Exception as e: except Exception as e:
logger.error(f"Error activating DisplayVersion plugin: {e}") logger.error(f"Error activating DisplayVersion plugin: {e}")
import traceback
logger.error(traceback.format_exc())
@cthulhu_hookimpl @cthulhu_hookimpl
def deactivate(self, plugin=None): def deactivate(self, plugin=None):
@@ -101,7 +55,6 @@ class DisplayVersion(Plugin):
def speakText(self, script=None, inputEvent=None): def speakText(self, script=None, inputEvent=None):
"""Speak the Cthulhu version when shortcut is pressed.""" """Speak the Cthulhu version when shortcut is pressed."""
try: try:
logger.info("DisplayVersion plugin: speakText called")
if self.app: if self.app:
state = self.app.getDynamicApiManager().getAPI('CthulhuState') state = self.app.getDynamicApiManager().getAPI('CthulhuState')
if state.activeScript: if state.activeScript:
+5
View File
@@ -1,6 +1,9 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
# #
# Copyright (c) 2024 Stormux # Copyright (c) 2024 Stormux
# Copyright (c) 2010-2012 The Orca Team
# Copyright (c) 2012 Igalia, S.L.
# Copyright (c) 2005-2010 Sun Microsystems Inc.
# #
# This library is free software; you can redistribute it and/or # This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public # modify it under the terms of the GNU Lesser General Public
@@ -17,6 +20,8 @@
# Free Software Foundation, Inc., Franklin Street, Fifth Floor, # Free Software Foundation, Inc., Franklin Street, Fifth Floor,
# Boston MA 02110-1301 USA. # Boston MA 02110-1301 USA.
# #
# Fork of Orca Screen Reader (GNOME)
# Original source: https://gitlab.gnome.org/GNOME/orca
"""Self Voice plugin for Cthulhu screen reader.""" """Self Voice plugin for Cthulhu screen reader."""
-7
View File
@@ -318,13 +318,6 @@ class ScriptManager:
return return
newScript.activate() newScript.activate()
# Register plugin keybindings with the new active script
from . import cthulhu
plugin_manager = cthulhu.cthulhuApp.getPluginSystemManager()
if plugin_manager:
pass # plugin_manager.register_plugin_keybindings_with_active_script()
tokens = ["SCRIPT MANAGER: Setting active script to", newScript, "reason:", reason] tokens = ["SCRIPT MANAGER: Setting active script to", newScript, "reason:", reason]
debug.printTokens(debug.LEVEL_INFO, tokens, True) debug.printTokens(debug.LEVEL_INFO, tokens, True)
-37
View File
@@ -363,8 +363,6 @@ class Script(script.Script):
return keyBindings return keyBindings
def getExtensionBindings(self): def getExtensionBindings(self):
with open('/tmp/extension_bindings_debug.log', 'a') as f:
f.write(f"=== getExtensionBindings() called ===\n")
keyBindings = keybindings.KeyBindings() keyBindings = keybindings.KeyBindings()
bindings = self.notificationPresenter.get_bindings() bindings = self.notificationPresenter.get_bindings()
@@ -409,41 +407,6 @@ class Script(script.Script):
for keyBinding in bindings.keyBindings: for keyBinding in bindings.keyBindings:
keyBindings.add(keyBinding) keyBindings.add(keyBinding)
# Add plugin keybindings from APIHelper storage
try:
import cthulhu.cthulhu as cthulhu
if hasattr(cthulhu, 'cthulhuApp') and cthulhu.cthulhuApp:
api_helper = cthulhu.cthulhuApp.getAPIHelper()
with open('/tmp/extension_bindings_debug.log', 'a') as f:
f.write(f"=== Checking for plugin bindings ===\n")
f.write(f"api_helper exists: {api_helper is not None}\n")
if api_helper:
f.write(f"api_helper has _gestureBindings: {hasattr(api_helper, '_gestureBindings')}\n")
if hasattr(api_helper, '_gestureBindings'):
f.write(f"_gestureBindings content: {api_helper._gestureBindings}\n")
f.write(f"Available contexts: {list(api_helper._gestureBindings.keys())}\n")
if api_helper and hasattr(api_helper, '_gestureBindings'):
with open('/tmp/extension_bindings_debug.log', 'a') as f:
f.write(f"=== Adding plugin bindings in getExtensionBindings() ===\n")
for context_name, context_bindings in api_helper._gestureBindings.items():
for binding in context_bindings:
keyBindings.add(binding)
with open('/tmp/extension_bindings_debug.log', 'a') as f:
f.write(f"Added plugin binding: {binding.keysymstring} modifiers={binding.modifiers} desc={binding.handler.description}\n")
else:
with open('/tmp/extension_bindings_debug.log', 'a') as f:
f.write(f"=== No plugin bindings available ===\n")
else:
with open('/tmp/extension_bindings_debug.log', 'a') as f:
f.write(f"=== cthulhuApp not available ===\n")
except Exception as e:
import cthulhu.debug as debug
debug.printMessage(debug.LEVEL_WARNING, f"Failed to add plugin bindings: {e}", True)
with open('/tmp/extension_bindings_debug.log', 'a') as f:
f.write(f"Exception in plugin binding addition: {e}\n")
return keyBindings return keyBindings
def getKeyBindings(self): def getKeyBindings(self):