diff --git a/src/cthulhu/plugin_system_manager.py b/src/cthulhu/plugin_system_manager.py index 2d6d5cd..a02c06b 100644 --- a/src/cthulhu/plugin_system_manager.py +++ b/src/cthulhu/plugin_system_manager.py @@ -18,9 +18,15 @@ from enum import IntEnum try: import pluggy PLUGGY_AVAILABLE = True + logging.getLogger(__name__).info("Pluggy loaded successfully") except ImportError: PLUGGY_AVAILABLE = False - logging.getLogger(__name__).info("Pluggy not available, plugins will be disabled") + logging.getLogger(__name__).warning("Pluggy not available (ImportError), plugins will be disabled") +except Exception as e: + PLUGGY_AVAILABLE = False + logging.getLogger(__name__).warning(f"Error loading pluggy: {str(e)}, plugins will be disabled") + import traceback + logging.getLogger(__name__).debug(traceback.format_exc()) # Set to True for more detailed plugin loading debug info PLUGIN_DEBUG = True @@ -292,6 +298,11 @@ class PluginSystemManager: """Sync the active state of all plugins.""" logger.info("Syncing active state of all plugins") + # Skip if pluggy is not available + if not PLUGGY_AVAILABLE: + logger.warning("Pluggy not available, skipping plugin sync") + return + # Log plugin status before syncing if PLUGIN_DEBUG: for pluginInfo in self.plugins: @@ -299,24 +310,29 @@ class PluginSystemManager: 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: - logger.info(f"Unloading inactive plugin: {pluginInfo.get_module_name()}") - self.unloadPlugin(pluginInfo) + try: + # First unload inactive plugins + for pluginInfo in self.plugins: + if not self.isPluginActive(pluginInfo) and pluginInfo.loaded: + logger.info(f"Unloading inactive plugin: {pluginInfo.get_module_name()}") + self.unloadPlugin(pluginInfo) - # Then load active plugins - for pluginInfo in self.plugins: - if self.isPluginActive(pluginInfo) and not pluginInfo.loaded: - 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}") - inactive_plugins = [p.get_module_name() for p in self.plugins if not p.loaded] - logger.info(f"Inactive plugins after sync: {inactive_plugins}") + # Then load active plugins + for pluginInfo in self.plugins: + if self.isPluginActive(pluginInfo) and not pluginInfo.loaded: + 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}") + inactive_plugins = [p.get_module_name() for p in self.plugins if not p.loaded] + logger.info(f"Inactive plugins after sync: {inactive_plugins}") + except Exception as e: + logger.error(f"Error syncing plugins: {e}") + import traceback + logger.error(traceback.format_exc()) def loadPlugin(self, pluginInfo): """Load a plugin.""" diff --git a/src/cthulhu/plugins/Clipboard/Clipboard.py b/src/cthulhu/plugins/Clipboard/Clipboard.py index 08b7572..0707b9d 100644 --- a/src/cthulhu/plugins/Clipboard/Clipboard.py +++ b/src/cthulhu/plugins/Clipboard/Clipboard.py @@ -26,13 +26,14 @@ from cthulhu import plugin import gi, os -gi.require_version('Peas', '1.0') from gi.repository import GObject -from gi.repository import Peas +import logging + +logger = logging.getLogger(__name__) gi.require_version("Gtk", "3.0") from gi.repository import Gtk, Gdk -class Clipboard(GObject.Object, Peas.Activatable, plugin.Plugin): +class Clipboard(GObject.Object, plugin.Plugin): #__gtype_name__ = 'Clipboard' object = GObject.Property(type=GObject.Object) diff --git a/src/cthulhu/plugins/Date/Date.py b/src/cthulhu/plugins/Date/Date.py index 3d7887f..1a98de4 100644 --- a/src/cthulhu/plugins/Date/Date.py +++ b/src/cthulhu/plugins/Date/Date.py @@ -26,11 +26,11 @@ from cthulhu import plugin import gi, time -gi.require_version('Peas', '1.0') from gi.repository import GObject -from gi.repository import Peas +import logging -class Date(GObject.Object, Peas.Activatable, plugin.Plugin): +logger = logging.getLogger(__name__) +class Date(GObject.Object, plugin.Plugin): #__gtype_name__ = 'Date' object = GObject.Property(type=GObject.Object) diff --git a/src/cthulhu/plugins/HelloCthulhu/HelloCthulhu.py b/src/cthulhu/plugins/HelloCthulhu/HelloCthulhu.py index a9235b2..a4375b2 100644 --- a/src/cthulhu/plugins/HelloCthulhu/HelloCthulhu.py +++ b/src/cthulhu/plugins/HelloCthulhu/HelloCthulhu.py @@ -26,23 +26,41 @@ from cthulhu import plugin import gi -gi.require_version('Peas', '1.0') from gi.repository import GObject -from gi.repository import Peas +import logging -class HelloCthulhu(GObject.Object, Peas.Activatable, plugin.Plugin): +logger = logging.getLogger(__name__) + +class HelloCthulhu(GObject.Object, plugin.Plugin): #__gtype_name__ = 'HelloCthulhu' object = GObject.Property(type=GObject.Object) def __init__(self): plugin.Plugin.__init__(self) - def do_activate(self): - API = self.object - self.connectSignal("start-application-completed", self.process) - def do_deactivate(self): - API = self.object - def do_update_state(self): - API = self.object + @plugin.cthulhu_hookimpl + def activate(self, plugin=None): + # Skip if this activation call isn't for us + if plugin is not None and plugin is not self: + return + + logger.info("Activating HelloCthulhu plugin") + try: + if self.app: + signal_manager = self.app.getSignalManager() + signal_manager.connectSignal("start-application-completed", self.process, "default") + else: + logger.error("No app reference available") + except Exception as e: + logger.error(f"Error activating HelloCthulhu plugin: {e}") + + @plugin.cthulhu_hookimpl + def deactivate(self, plugin=None): + # Skip if this deactivation call isn't for us + if plugin is not None and plugin is not self: + return + + logger.info("Deactivating HelloCthulhu plugin") + # No specific cleanup needed def process(self, app): messages = app.getDynamicApiManager().getAPI('Messages') app.getDynamicApiManager().getAPI('CthulhuState').activeScript.presentMessage(messages.START_CTHULHU, resetStyles=False) diff --git a/src/cthulhu/plugins/MouseReview/MouseReview.py b/src/cthulhu/plugins/MouseReview/MouseReview.py index ad409c2..e51fe16 100644 --- a/src/cthulhu/plugins/MouseReview/MouseReview.py +++ b/src/cthulhu/plugins/MouseReview/MouseReview.py @@ -35,9 +35,10 @@ __license__ = "LGPL" from cthulhu import plugin import gi, math, time -gi.require_version('Peas', '1.0') from gi.repository import GObject -from gi.repository import Peas +import logging + +logger = logging.getLogger(__name__) gi.require_version("Atspi", "2.0") from gi.repository import Atspi @@ -68,7 +69,7 @@ AXUtilities = None keybindings = None input_event = None -class MouseReview(GObject.Object, Peas.Activatable, plugin.Plugin): +class MouseReview(GObject.Object, plugin.Plugin): #__gtype_name__ = 'MouseReview' object = GObject.Property(type=GObject.Object) diff --git a/src/cthulhu/plugins/PluginManager/PluginManager.py b/src/cthulhu/plugins/PluginManager/PluginManager.py index 6aadf1e..37615c3 100644 --- a/src/cthulhu/plugins/PluginManager/PluginManager.py +++ b/src/cthulhu/plugins/PluginManager/PluginManager.py @@ -26,25 +26,43 @@ from cthulhu import plugin import gi -gi.require_version('Peas', '1.0') from gi.repository import GObject -from gi.repository import Peas +import logging import PluginManagerUi -class PluginManager(GObject.Object, Peas.Activatable, plugin.Plugin): +logger = logging.getLogger(__name__) + +class PluginManager(GObject.Object, plugin.Plugin): #__gtype_name__ = 'PluginManager' object = GObject.Property(type=GObject.Object) def __init__(self): plugin.Plugin.__init__(self) self.pluginManagerUi = None - def do_activate(self): - API = self.object - self.registerGestureByString(self.startPluginManagerUi, _('plugin manager'), 'kb:cthulhu+e') - - def do_deactivate(self): - API = self.object + @plugin.cthulhu_hookimpl + def activate(self, plugin=None): + # Skip if this activation call isn't for us + if plugin is not None and plugin is not self: + return + + logger.info("Activating PluginManager plugin") + try: + if self.app: + self.registerGestureByString(self.startPluginManagerUi, 'plugin manager', 'kb:cthulhu+e') + else: + logger.error("No app reference available") + except Exception as e: + logger.error(f"Error activating PluginManager plugin: {e}") + + @plugin.cthulhu_hookimpl + def deactivate(self, plugin=None): + # Skip if this deactivation call isn't for us + if plugin is not None and plugin is not self: + return + + logger.info("Deactivating PluginManager plugin") + # No specific cleanup needed def startPluginManagerUi(self, script=None, inputEvent=None): self.showUI() diff --git a/src/cthulhu/plugins/SimplePluginSystem/SimplePluginSystem.py b/src/cthulhu/plugins/SimplePluginSystem/SimplePluginSystem.py index 5fa6b64..1b6699f 100644 --- a/src/cthulhu/plugins/SimplePluginSystem/SimplePluginSystem.py +++ b/src/cthulhu/plugins/SimplePluginSystem/SimplePluginSystem.py @@ -25,7 +25,7 @@ from cthulhu import plugin -from gi.repository import GObject, Peas +from gi.repository import GObject import glob import os import importlib.util @@ -33,21 +33,23 @@ import random import string import _thread from subprocess import Popen, PIPE +import logging + +logger = logging.getLogger(__name__) settings = None speech = None braille = None input_event = None -def outputMessage( Message): - if (settings.enableSpeech): +def outputMessage(Message): + if (settings and settings.enableSpeech and speech): speech.speak(Message) - if (settings.enableBraille): + if (settings and settings.enableBraille and braille): braille.displayMessage(Message) -class SimplePluginSystem(GObject.Object, Peas.Activatable, plugin.Plugin): +class SimplePluginSystem(GObject.Object, plugin.Plugin): __gtype_name__ = 'SimplePluginSystem' - object = GObject.Property(type=GObject.Object) def __init__(self): plugin.Plugin.__init__(self) @@ -55,27 +57,56 @@ class SimplePluginSystem(GObject.Object, Peas.Activatable, plugin.Plugin): self.loaded = False self.plugin_repo = os.path.expanduser('~') + "/.local/share/cthulhu/simple-plugins-enabled/" - def do_activate(self): - API = self.object - global settings - global speech - global braille - global input_event - settings = API.app.getDynamicApiManager().getAPI('Settings') - speech = API.app.getDynamicApiManager().getAPI('Speech') - braille = API.app.getDynamicApiManager().getAPI('Braille') - input_event = API.app.getDynamicApiManager().getAPI('InputEvent') - """Required method for plugins""" - if not self.loaded: - self.load_plugins() + @plugin.cthulhu_hookimpl + def activate(self, plugin=None): + """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 SimplePluginSystem plugin") + + try: + global settings + global speech + global braille + global input_event + + if self.app: + settings = self.app.getDynamicApiManager().getAPI('Settings') + speech = self.app.getDynamicApiManager().getAPI('Speech') + braille = self.app.getDynamicApiManager().getAPI('Braille') + input_event = self.app.getDynamicApiManager().getAPI('InputEvent') + + if not self.loaded: + self.load_plugins() + else: + logger.error("SimplePluginSystem: No app reference available") + except Exception as e: + logger.error(f"Error activating SimplePluginSystem plugin: {e}") + import traceback + logger.error(traceback.format_exc()) - def do_deactivate(self): - """Required method for plugins""" - # Remove all registered keybindings - for plugin in self.plugin_list: - self.unregisterShortcut(plugin['function'], plugin['shortcut']) - self.loaded = False - self.plugin_list = [] + @plugin.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 SimplePluginSystem plugin") + + try: + # Remove all registered keybindings + for plugin in self.plugin_list: + if 'function' in plugin and 'shortcut' in plugin: + self.unregisterShortcut(plugin['function'], plugin['shortcut']) + self.loaded = False + self.plugin_list = [] + except Exception as e: + logger.error(f"Error deactivating SimplePluginSystem plugin: {e}") + import traceback + logger.error(traceback.format_exc()) def SetupShortcutAndHandle(self, currPluginSetting): shortcut = '' diff --git a/src/cthulhu/plugins/Time/Time.py b/src/cthulhu/plugins/Time/Time.py index 02114c2..11c4e84 100644 --- a/src/cthulhu/plugins/Time/Time.py +++ b/src/cthulhu/plugins/Time/Time.py @@ -24,30 +24,57 @@ # Original source: https://gitlab.gnome.org/GNOME/orca import gi, time -gi.require_version('Peas', '1.0') from gi.repository import GObject -from gi.repository import Peas +import logging from cthulhu import plugin -class Time(GObject.Object, Peas.Activatable, plugin.Plugin): +logger = logging.getLogger(__name__) + +class Time(GObject.Object, plugin.Plugin): #__gtype_name__ = 'Time' object = GObject.Property(type=GObject.Object) def __init__(self): plugin.Plugin.__init__(self) - def do_activate(self): - API = self.object - self.connectSignal("setup-inputeventhandlers-completed", self.setupCompatBinding) + @plugin.cthulhu_hookimpl + def activate(self, plugin=None): + # Skip if this activation call isn't for us + if plugin is not None and plugin is not self: + return + + logger.info("Activating Time plugin") + try: + if self.app: + signal_manager = self.app.getSignalManager() + signal_manager.connectSignal("setup-inputeventhandlers-completed", self.setupCompatBinding, "default") + else: + logger.error("No app reference available") + except Exception as e: + logger.error(f"Error activating Time plugin: {e}") + def setupCompatBinding(self, app): - cmdnames = app.getDynamicApiManager().getAPI('Cmdnames') - inputEventHandlers = app.getDynamicApiManager().getAPI('inputEventHandlers') - inputEventHandlers['presentTimeHandler'] = app.getAPIHelper().createInputEventHandler(self.presentTime, cmdnames.PRESENT_CURRENT_TIME) - def do_deactivate(self): - API = self.object - inputEventHandlers = API.app.getDynamicApiManager().getAPI('inputEventHandlers') - del inputEventHandlers['presentTimeHandler'] - def do_update_state(self): + try: + cmdnames = app.getDynamicApiManager().getAPI('Cmdnames') + inputEventHandlers = app.getDynamicApiManager().getAPI('inputEventHandlers') + inputEventHandlers['presentTimeHandler'] = app.getAPIHelper().createInputEventHandler(self.presentTime, cmdnames.PRESENT_CURRENT_TIME) + except Exception as e: + logger.error(f"Error setting up Time plugin: {e}") + + @plugin.cthulhu_hookimpl + def deactivate(self, plugin=None): + # Skip if this deactivation call isn't for us + if plugin is not None and plugin is not self: + return + + logger.info("Deactivating Time plugin") + try: + if self.app: + inputEventHandlers = self.app.getDynamicApiManager().getAPI('inputEventHandlers') + if 'presentTimeHandler' in inputEventHandlers: + del inputEventHandlers['presentTimeHandler'] + except Exception as e: + logger.error(f"Error deactivating Time plugin: {e}") API = self.object def presentTime(self, script=None, inputEvent=None): """ Presents the current time. """