diff --git a/src/cthulhu/cthulhu.py b/src/cthulhu/cthulhu.py index fcf628b..01e2367 100644 --- a/src/cthulhu/cthulhu.py +++ b/src/cthulhu/cthulhu.py @@ -610,8 +610,19 @@ def loadUserSettings(script=None, inputEvent=None, skipReloadMessage=False): _storeXmodmap(_cthulhuModifiers) _createCthulhuXmodmap() - activePlugins = list(_settingsManager.getSetting('activePlugins')) - cthulhuApp.getPluginSystemManager().setActivePlugins(activePlugins) + # Safe plugin initialization + try: + debug.printMessage(debug.LEVEL_INFO, 'CTHULHU: Loading plugins', True) + pluginManager = cthulhuApp.getPluginSystemManager() + pluginManager.rescanPlugins() # First scan for available plugins + + activePlugins = list(_settingsManager.getSetting('activePlugins')) + debug.printMessage(debug.LEVEL_INFO, f'CTHULHU: Setting active plugins: {activePlugins}', True) + pluginManager.setActivePlugins(activePlugins) + except Exception as e: + debug.printMessage(debug.LEVEL_WARNING, f'CTHULHU: Error loading plugins: {e}', True) + import traceback + debug.printMessage(debug.LEVEL_INFO, traceback.format_exc(), True) _scriptManager.activate() _eventManager.activate() @@ -1018,8 +1029,7 @@ class Cthulhu(GObject.Object): self.APIHelper = APIHelper(self) self.createCompatAPI() self.pluginSystemManager = plugin_system_manager.PluginSystemManager(self) - # Scan for available plugins at startup - self.pluginSystemManager.rescanPlugins() + # DO NOT scan for plugins during initialization - will be done later in loadUserSettings def getAPIHelper(self): return self.APIHelper def getPluginSystemManager(self): diff --git a/src/cthulhu/plugin_system_manager.py b/src/cthulhu/plugin_system_manager.py index 960c9b8..ac459cc 100644 --- a/src/cthulhu/plugin_system_manager.py +++ b/src/cthulhu/plugin_system_manager.py @@ -222,7 +222,8 @@ class PluginSystemManager: # Simple regex-like search for class definition that inherits from Plugin import re - matches = re.findall(r'class\s+(\w+)\s*\([^)]*Plugin[^)]*\)', content) + # Simpler regex to reduce chance of regex hang + matches = re.findall(r'class\s+(\w+).*Plugin', content) if matches: return matches[0] except Exception as e: @@ -271,11 +272,25 @@ class PluginSystemManager: 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] + missing_plugins = [p for p in self._active_plugins if p not in available_plugins and p.lower() not in [x.lower() for x in available_plugins]] if missing_plugins: logger.warning(f"Active plugins not found: {missing_plugins}") - - self.syncAllPluginsActive() + + # Start plugin activation in a background thread + import threading + def activate_plugins_thread(): + try: + self.syncAllPluginsActive() + except Exception as e: + logger.error(f"Error activating plugins: {e}") + import traceback + logger.error(traceback.format_exc()) + + # Start a background thread for plugin activation + thread = threading.Thread(target=activate_plugins_thread) + thread.daemon = True + thread.start() + logger.info("Plugin activation started in background thread") def setPluginActive(self, pluginInfo, active): """Set the active state of a plugin.""" @@ -335,12 +350,20 @@ class PluginSystemManager: logger.warning("Pluggy not available, skipping plugin sync") return + # Don't do anything if there are no plugins + if not self.plugins: + logger.warning("No plugins found, skipping plugin sync") + return + # 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}") + try: + is_active = self.isPluginActive(pluginInfo) + is_loaded = pluginInfo.loaded + logger.debug(f"Plugin {pluginInfo.get_module_name()}: active={is_active}, loaded={is_loaded}") + except Exception as e: + logger.error(f"Error checking plugin status: {e}") try: # First unload inactive plugins @@ -368,13 +391,52 @@ class PluginSystemManager: def loadPlugin(self, pluginInfo): """Load a plugin.""" - # Skip if pluggy is not available - if not PLUGGY_AVAILABLE: - logger.info(f"Skipping plugin {pluginInfo.get_name()}: pluggy not available") - return False + # Set a reasonable timeout for plugin loading - 10 seconds + import threading + import time + + # Create an event to signal when loading is done + load_complete = threading.Event() + result = [False] # Use a list to store result from the thread + + # Create a thread to load the plugin + def load_plugin_thread(): + try: + # Skip if pluggy is not available + if not PLUGGY_AVAILABLE: + logger.info(f"Skipping plugin {pluginInfo.get_name()}: pluggy not available") + result[0] = False + load_complete.set() + return + module_name = pluginInfo.get_module_name() + logger.info(f"Attempting to load plugin: {module_name}") + + # Actual loading logic will continue below + result[0] = self._load_plugin_impl(pluginInfo) + except Exception as e: + logger.error(f"Error in plugin loading thread: {e}") + import traceback + logger.error(traceback.format_exc()) + result[0] = False + finally: + load_complete.set() + + # Start the loading thread + thread = threading.Thread(target=load_plugin_thread) + thread.daemon = True + thread.start() + + # Wait for loading to complete with a timeout + if not load_complete.wait(10): # 10 second timeout + logger.error(f"Plugin loading timeout for {pluginInfo.get_name()}") + return False + + return result[0] + + def _load_plugin_impl(self, pluginInfo): + """Implementation of plugin loading that runs in a thread.""" module_name = pluginInfo.get_module_name() - logger.info(f"Attempting to load plugin: {module_name}") try: # Already loaded? @@ -400,11 +462,18 @@ class PluginSystemManager: logger.error(f"Plugin file not found: {plugin_file}") return False + # Add a delay to improve stability + import time + time.sleep(0.1) + 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 + + # Add another small delay + time.sleep(0.1) module = importlib.util.module_from_spec(spec) spec.loader.exec_module(module) @@ -450,6 +519,9 @@ class PluginSystemManager: logger.info(f"Registering plugin with pluggy: {module_name}") self.plugin_manager.register(plugin_instance) + # Add another small delay + time.sleep(0.1) + try: logger.info(f"Activating plugin: {module_name}") self.plugin_manager.hook.activate(plugin=plugin_instance)