More attempts to fix keyboard.
This commit is contained in:
parent
ec90906052
commit
9790a8d494
src/cthulhu
@ -37,47 +37,35 @@ import faulthandler
|
||||
|
||||
class APIHelper:
|
||||
"""Helper class for plugin API interactions, including keybindings."""
|
||||
|
||||
|
||||
def __init__(self, app):
|
||||
"""Initialize the APIHelper.
|
||||
|
||||
|
||||
Arguments:
|
||||
- app: the Cthulhu application
|
||||
"""
|
||||
self.app = app
|
||||
self._gestureBindings = {}
|
||||
|
||||
|
||||
def registerGestureByString(self, function, name, gestureString, inputEventType='default', normalizer='cthulhu', learnModeEnabled=True, contextName=None):
|
||||
"""Register a gesture by string.
|
||||
|
||||
Arguments:
|
||||
- function: the function to call when the gesture is performed
|
||||
- name: a human-readable name for this gesture
|
||||
- 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
|
||||
"""
|
||||
"""Register a gesture by string."""
|
||||
if not gestureString.startswith("kb:"):
|
||||
return None
|
||||
|
||||
|
||||
# Extract the key portion from the gesture string
|
||||
key = gestureString.split(":", 1)[1]
|
||||
|
||||
|
||||
# Handle Cthulhu modifier specially
|
||||
if "cthulhu+" in key.lower():
|
||||
from . import keybindings
|
||||
key_parts = key.lower().split("+")
|
||||
|
||||
|
||||
# Determine appropriate modifier mask
|
||||
modifiers = keybindings.CTHULHU_MODIFIER_MASK
|
||||
|
||||
|
||||
# Extract the final key (without modifiers)
|
||||
final_key = key_parts[-1]
|
||||
|
||||
|
||||
# Check for additional modifiers
|
||||
if "shift" in key_parts:
|
||||
modifiers = keybindings.CTHULHU_SHIFT_MODIFIER_MASK
|
||||
@ -85,13 +73,13 @@ class APIHelper:
|
||||
modifiers = keybindings.CTHULHU_CTRL_MODIFIER_MASK
|
||||
elif "alt" in key_parts:
|
||||
modifiers = keybindings.CTHULHU_ALT_MODIFIER_MASK
|
||||
|
||||
|
||||
# Create a keybinding handler
|
||||
class GestureHandler:
|
||||
def __init__(self, function, description):
|
||||
self.function = function
|
||||
self.description = description
|
||||
|
||||
|
||||
def __call__(self, script, inputEvent):
|
||||
try:
|
||||
return function(script, inputEvent)
|
||||
@ -99,9 +87,9 @@ class APIHelper:
|
||||
import logging
|
||||
logging.getLogger(__name__).error(f"Error in keybinding handler: {e}")
|
||||
return True
|
||||
|
||||
|
||||
handler = GestureHandler(function, name)
|
||||
|
||||
|
||||
# Register the binding with the active script
|
||||
from . import cthulhu_state
|
||||
if cthulhu_state.activeScript:
|
||||
@ -111,29 +99,29 @@ class APIHelper:
|
||||
keybindings.defaultModifierMask,
|
||||
modifiers,
|
||||
handler)
|
||||
|
||||
|
||||
# Add the binding to the active script
|
||||
bindings.add(binding)
|
||||
|
||||
|
||||
# Store binding for later reference
|
||||
if contextName not in self._gestureBindings:
|
||||
self._gestureBindings[contextName] = []
|
||||
self._gestureBindings[contextName].append(binding)
|
||||
|
||||
|
||||
# Register key grab at the system level
|
||||
grab_ids = self.app.addKeyGrab(binding)
|
||||
|
||||
|
||||
# For later removal
|
||||
if grab_ids:
|
||||
binding._grab_ids = grab_ids
|
||||
|
||||
|
||||
return binding
|
||||
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def unregisterShortcut(self, binding, contextName=None):
|
||||
"""Unregister a previously registered shortcut.
|
||||
|
||||
|
||||
Arguments:
|
||||
- binding: the binding to unregister
|
||||
- contextName: the context for this gesture
|
||||
@ -143,12 +131,12 @@ class APIHelper:
|
||||
if cthulhu_state.activeScript:
|
||||
bindings = cthulhu_state.activeScript.getKeyBindings()
|
||||
bindings.remove(binding)
|
||||
|
||||
|
||||
# Remove key grab at system level
|
||||
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 binding in self._gestureBindings[contextName]:
|
||||
|
@ -24,4 +24,4 @@
|
||||
# Original source: https://gitlab.gnome.org/GNOME/orca
|
||||
|
||||
version = "2025.04.18"
|
||||
codeName = "testing"
|
||||
codeName = "plugins"
|
||||
|
@ -21,7 +21,7 @@ except ImportError:
|
||||
# Fallback if pluggy is not available
|
||||
def cthulhu_hookimpl(func=None, **kwargs):
|
||||
"""Fallback decorator when pluggy is not available.
|
||||
|
||||
|
||||
This is a no-op decorator that returns the original function.
|
||||
It allows the code to continue working without pluggy, though
|
||||
plugins will be disabled.
|
||||
@ -47,6 +47,8 @@ class Plugin:
|
||||
self.name = ''
|
||||
self.version = ''
|
||||
self.description = ''
|
||||
self._bindings = None
|
||||
self._gestureBindings = {}
|
||||
|
||||
def set_app(self, app):
|
||||
"""Set the application reference."""
|
||||
@ -75,12 +77,16 @@ class Plugin:
|
||||
return
|
||||
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):
|
||||
"""Register a gesture by string."""
|
||||
if self.app:
|
||||
api_helper = self.app.getAPIHelper()
|
||||
if api_helper:
|
||||
return api_helper.registerGestureByString(
|
||||
binding = api_helper.registerGestureByString(
|
||||
function,
|
||||
name,
|
||||
gestureString,
|
||||
@ -89,4 +95,13 @@ class Plugin:
|
||||
learnModeEnabled,
|
||||
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
|
||||
|
@ -112,11 +112,72 @@ class PluginSystemManager:
|
||||
|
||||
# Create plugin directories
|
||||
self._setup_plugin_dirs()
|
||||
|
||||
|
||||
# Log available plugins directory paths
|
||||
logger.info(f"System plugins directory: {PluginType.SYSTEM.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.connect('load-setting-completed', self._on_settings_changed)
|
||||
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 _on_settings_changed(self):
|
||||
"""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)
|
||||
grab_ids = self.app.addKeyGrab(binding)
|
||||
if grab_ids:
|
||||
binding._grab_ids = grab_ids
|
||||
|
||||
def _setup_plugin_dirs(self):
|
||||
"""Ensure plugin directories exist."""
|
||||
os.makedirs(PluginType.SYSTEM.get_root_dir(), exist_ok=True)
|
||||
@ -219,24 +280,24 @@ class PluginSystemManager:
|
||||
def setActivePlugins(self, activePlugins):
|
||||
"""Set active plugins and sync their state."""
|
||||
logger.info(f"Setting active plugins: {activePlugins}")
|
||||
|
||||
|
||||
# Make sure we have scanned for plugins first
|
||||
if not self._plugins:
|
||||
logger.info("No plugins found, rescanning...")
|
||||
self.rescanPlugins()
|
||||
|
||||
|
||||
self._active_plugins = activePlugins
|
||||
|
||||
|
||||
# Log active vs available plugins
|
||||
available_plugins = [p.get_module_name() for p in self.plugins]
|
||||
logger.info(f"Available plugins: {available_plugins}")
|
||||
logger.info(f"Active plugins: {self._active_plugins}")
|
||||
|
||||
|
||||
# Find missing plugins
|
||||
missing_plugins = [p for p in self._active_plugins if p not in available_plugins]
|
||||
if missing_plugins:
|
||||
logger.warning(f"Active plugins not found: {missing_plugins}")
|
||||
|
||||
|
||||
self.syncAllPluginsActive()
|
||||
|
||||
def setPluginActive(self, pluginInfo, active):
|
||||
@ -258,7 +319,7 @@ class PluginSystemManager:
|
||||
def isPluginActive(self, pluginInfo):
|
||||
"""Check if a plugin is active."""
|
||||
module_name = pluginInfo.get_module_name()
|
||||
|
||||
|
||||
# Builtin plugins are always active
|
||||
if pluginInfo.builtin:
|
||||
logger.debug(f"Plugin {module_name} is builtin, active by default")
|
||||
@ -271,34 +332,34 @@ class PluginSystemManager:
|
||||
|
||||
# Check case-insensitive match in active plugins list
|
||||
active_plugins = self.getActivePlugins()
|
||||
|
||||
|
||||
# Try exact match first
|
||||
if module_name in active_plugins:
|
||||
logger.debug(f"Plugin {module_name} found in active plugins list")
|
||||
return True
|
||||
|
||||
|
||||
# Try case-insensitive match
|
||||
module_name_lower = module_name.lower()
|
||||
is_active = any(plugin.lower() == module_name_lower for plugin in active_plugins)
|
||||
|
||||
|
||||
if is_active:
|
||||
logger.debug(f"Plugin {module_name} found in active plugins list (case-insensitive match)")
|
||||
else:
|
||||
logger.debug(f"Plugin {module_name} not found in active plugins list")
|
||||
|
||||
|
||||
return is_active
|
||||
|
||||
def syncAllPluginsActive(self):
|
||||
"""Sync the active state of all plugins."""
|
||||
logger.info("Syncing active state of all plugins")
|
||||
|
||||
|
||||
# Log plugin status before syncing
|
||||
if PLUGIN_DEBUG:
|
||||
for pluginInfo in self.plugins:
|
||||
is_active = self.isPluginActive(pluginInfo)
|
||||
is_loaded = pluginInfo.loaded
|
||||
logger.debug(f"Plugin {pluginInfo.get_module_name()}: active={is_active}, loaded={is_loaded}")
|
||||
|
||||
|
||||
# First unload inactive plugins
|
||||
for pluginInfo in self.plugins:
|
||||
if not self.isPluginActive(pluginInfo) and pluginInfo.loaded:
|
||||
@ -311,7 +372,7 @@ class PluginSystemManager:
|
||||
logger.info(f"Loading active plugin: {pluginInfo.get_module_name()}")
|
||||
result = self.loadPlugin(pluginInfo)
|
||||
logger.info(f"Plugin {pluginInfo.get_module_name()} load result: {result}")
|
||||
|
||||
|
||||
# Log final plugin status
|
||||
active_plugins = [p.get_module_name() for p in self.plugins if p.loaded]
|
||||
logger.info(f"Active plugins after sync: {active_plugins}")
|
||||
@ -337,27 +398,27 @@ class PluginSystemManager:
|
||||
# Try to find the plugin file
|
||||
module_name = pluginInfo.get_module_name()
|
||||
plugin_dir = pluginInfo.get_module_dir()
|
||||
|
||||
|
||||
# Check for plugin.py first (standard format)
|
||||
plugin_file = os.path.join(plugin_dir, "plugin.py")
|
||||
|
||||
|
||||
# Fall back to [PluginName].py if plugin.py doesn't exist
|
||||
if not os.path.exists(plugin_file):
|
||||
alternative_plugin_file = os.path.join(plugin_dir, f"{module_name}.py")
|
||||
if os.path.exists(alternative_plugin_file):
|
||||
plugin_file = alternative_plugin_file
|
||||
logger.info(f"Using alternative plugin file: {alternative_plugin_file}")
|
||||
|
||||
|
||||
if not os.path.exists(plugin_file):
|
||||
logger.error(f"Plugin file not found: {plugin_file}")
|
||||
return False
|
||||
|
||||
|
||||
logger.info(f"Loading plugin from: {plugin_file}")
|
||||
spec = importlib.util.spec_from_file_location(module_name, plugin_file)
|
||||
if spec is None:
|
||||
logger.error(f"Failed to create spec for plugin: {module_name}")
|
||||
return False
|
||||
|
||||
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(module)
|
||||
pluginInfo.module = module
|
||||
@ -385,7 +446,7 @@ class PluginSystemManager:
|
||||
# Ensure plugins have a reference to the app
|
||||
plugin_instance.app = self.getApp()
|
||||
logger.info(f"Set app reference for plugin: {module_name}")
|
||||
|
||||
|
||||
if hasattr(plugin_instance, 'set_app'):
|
||||
plugin_instance.set_app(self.getApp())
|
||||
logger.info(f"Called set_app() for plugin: {module_name}")
|
||||
@ -398,10 +459,10 @@ class PluginSystemManager:
|
||||
if self.plugin_manager is None:
|
||||
logger.error(f"Plugin manager is None when loading {module_name}")
|
||||
return False
|
||||
|
||||
|
||||
logger.info(f"Registering plugin with pluggy: {module_name}")
|
||||
self.plugin_manager.register(plugin_instance)
|
||||
|
||||
|
||||
try:
|
||||
logger.info(f"Activating plugin: {module_name}")
|
||||
self.plugin_manager.hook.activate(plugin=plugin_instance)
|
||||
@ -413,6 +474,10 @@ class PluginSystemManager:
|
||||
|
||||
pluginInfo.loaded = True
|
||||
logger.info(f"Successfully loaded plugin: {module_name}")
|
||||
|
||||
# Register any global keybindings from the plugin
|
||||
self.register_plugin_global_keybindings(pluginInfo.instance)
|
||||
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
|
@ -40,8 +40,11 @@ class DisplayVersion(Plugin):
|
||||
f'Cthulhu screen reader version {cthulhuVersion.version}-{cthulhuVersion.codeName}',
|
||||
'kb:cthulhu+shift+v'
|
||||
)
|
||||
logger.info(f"Registered keybinding: {self._kb_binding}")
|
||||
except Exception as e:
|
||||
logger.error(f"Error activating DisplayVersion plugin: {e}")
|
||||
import traceback
|
||||
logger.error(traceback.format_exc())
|
||||
|
||||
@cthulhu_hookimpl
|
||||
def deactivate(self, plugin=None):
|
||||
@ -55,6 +58,7 @@ class DisplayVersion(Plugin):
|
||||
def speakText(self, script=None, inputEvent=None):
|
||||
"""Speak the Cthulhu version when shortcut is pressed."""
|
||||
try:
|
||||
logger.info("DisplayVersion plugin: speakText called")
|
||||
if self.app:
|
||||
state = self.app.getDynamicApiManager().getAPI('CthulhuState')
|
||||
if state.activeScript:
|
||||
@ -65,4 +69,4 @@ class DisplayVersion(Plugin):
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Error in DisplayVersion speakText: {e}")
|
||||
return False
|
||||
return False
|
||||
|
Loading…
x
Reference in New Issue
Block a user