From 11bd7107d2d5d930bafece2e86cfaa2ffcc59502 Mon Sep 17 00:00:00 2001 From: Storm Dragon Date: Thu, 19 Feb 2026 02:40:46 -0500 Subject: [PATCH] Attempt to stop plugins from spontaneously re-enabling themselves. --- src/cthulhu/backends/toml_backend.py | 16 ++++- src/cthulhu/plugins/PluginManager/plugin.py | 22 +------ src/cthulhu/settings_manager.py | 68 ++++++++++++++++++--- 3 files changed, 76 insertions(+), 30 deletions(-) diff --git a/src/cthulhu/backends/toml_backend.py b/src/cthulhu/backends/toml_backend.py index 400c6dd..f147888 100644 --- a/src/cthulhu/backends/toml_backend.py +++ b/src/cthulhu/backends/toml_backend.py @@ -85,12 +85,16 @@ class Backend: with open(fileName, 'w', encoding='utf-8') as settingsFile: settingsFile.write(dumps(prefsDoc)) - def _updateTable(self, targetTable, newValues): + def _updateTable(self, targetTable, newValues, preserveMissingKeys=None): if not isinstance(newValues, dict): return + preserveMissingKeys = set(preserveMissingKeys or []) + for key in list(targetTable.keys()): if key not in newValues: + if key in preserveMissingKeys: + continue del targetTable[key] continue newValue = newValues[key] @@ -173,7 +177,12 @@ class Backend: profiles[profile] = {} profileTable = profiles[profile] - self._updateTable(profileTable, general) + # Keep plugin persistence keys when callers provide partial updates. + self._updateTable( + profileTable, + general, + preserveMissingKeys={"activePlugins", "pluginSources"}, + ) self._writeDocument(self.settingsFile, prefsDoc) def _getSettings(self): @@ -219,6 +228,9 @@ class Backend: self._getSettings() generalSettings = self.general.copy() generalSettings = self._migrateSettings(generalSettings) + # Plugin state is profile-scoped; ignore legacy/global values. + generalSettings.pop('activePlugins', None) + generalSettings.pop('pluginSources', None) defaultProfile = generalSettings.get('startingProfile', ['Default', 'default']) if profile is None: diff --git a/src/cthulhu/plugins/PluginManager/plugin.py b/src/cthulhu/plugins/PluginManager/plugin.py index 0693f2d..a45c58a 100644 --- a/src/cthulhu/plugins/PluginManager/plugin.py +++ b/src/cthulhu/plugins/PluginManager/plugin.py @@ -21,10 +21,8 @@ from gi.repository import Gtk, Gdk, Pango from cthulhu.plugin import Plugin, cthulhu_hookimpl from cthulhu import cthulhu from cthulhu import debug -from cthulhu import settings_manager logger = logging.getLogger(__name__) -_settingsManager = None # Removed - use cthulhu.cthulhuApp.settingsManager class PluginManager(Plugin): @@ -473,25 +471,11 @@ class PluginManager(Plugin): current_general['activePlugins'] = active_plugins cthulhu.cthulhuApp.settingsManager.profile = profile_name - cthulhu.cthulhuApp.settingsManager._setProfileGeneral(current_general) - - pronunciations = cthulhu.cthulhuApp.settingsManager.getPronunciations(profile_name) or {} - keybindings = cthulhu.cthulhuApp.settingsManager.getKeybindings(profile_name) or {} - - backend = cthulhu.cthulhuApp.settingsManager._backend - if backend: - backend.saveProfileSettings( - profile_name, - cthulhu.cthulhuApp.settingsManager.profileGeneral, - pronunciations, - keybindings - ) - debug.printMessage(debug.LEVEL_INFO, f"PluginManager: Settings saved to backend (profile {profile_name})", True) - else: - debug.printMessage(debug.LEVEL_INFO, "PluginManager: No backend available for saving", True) + cthulhu.cthulhuApp.settingsManager.saveProfileSettings(current_general) + debug.printMessage(debug.LEVEL_INFO, f"PluginManager: Settings saved via settings manager (profile {profile_name})", True) except Exception as save_error: - debug.printMessage(debug.LEVEL_INFO, f"PluginManager: Error saving via backend: {save_error}", True) + debug.printMessage(debug.LEVEL_INFO, f"PluginManager: Error saving plugin state: {save_error}", True) debug.printMessage(debug.LEVEL_INFO, f"PluginManager: Updated active plugins: {active_plugins}", True) diff --git a/src/cthulhu/settings_manager.py b/src/cthulhu/settings_manager.py index 2b18db7..c5dfd10 100644 --- a/src/cthulhu/settings_manager.py +++ b/src/cthulhu/settings_manager.py @@ -379,6 +379,18 @@ class SettingsManager(object): def getSetting(self, settingName: str) -> Any: return getattr(settings, settingName, None) + def _getListSetting(self, settingName: str) -> List[str]: + value = self.getSetting(settingName) + if isinstance(value, (list, tuple)): + return [item for item in value if isinstance(item, str)] + return [] + + def _ensurePluginPersistenceSettings(self, general: Dict[str, Any]) -> None: + if 'activePlugins' not in general: + general['activePlugins'] = self._getListSetting('activePlugins') + if 'pluginSources' not in general: + general['pluginSources'] = self._getListSetting('pluginSources') + def getVoiceLocale(self, voice: str = 'default') -> str: voices = self.getSetting('voices') v = ACSS(voices.get(voice, {})) @@ -626,6 +638,8 @@ class SettingsManager(object): continue elif key == 'profile': self.profileGeneral[key] = value + elif key in ['activePlugins', 'pluginSources']: + self.profileGeneral[key] = copy.deepcopy(value) elif value != self.defaultGeneral.get(key): self.profileGeneral[key] = value elif self.general.get(key) != value: @@ -634,6 +648,46 @@ class SettingsManager(object): msg = 'SETTINGS MANAGER: General settings for profile set' debug.printMessage(debug.LEVEL_INFO, msg, True) + def saveProfileSettings( + self, + general: Dict[str, Any], + pronunciations: Optional[Dict[str, Any]] = None, + keybindings: Optional[Dict[str, Any]] = None, + ) -> None: + profileName: Optional[str] = self.profile + profileSetting = general.get('profile') + if isinstance(profileSetting, (list, tuple)) and len(profileSetting) > 1: + profileName = profileSetting[1] + elif not profileName: + defaultProfile = settings.profile + if isinstance(defaultProfile, (list, tuple)) and len(defaultProfile) > 1: + profileName = defaultProfile[1] + else: + profileName = 'default' + + self.profile = profileName + generalCopy = dict(general) + self._ensurePluginPersistenceSettings(generalCopy) + self._setProfileGeneral(generalCopy) + + if self.profile is None: + return + + profileName = self.profile + if pronunciations is None: + pronunciations = self.getPronunciations(profileName) or {} + if keybindings is None: + keybindings = self.getKeybindings(profileName) or {} + + self._setProfilePronunciations(pronunciations) + self._setProfileKeybindings(keybindings) + + if self._backend: + self._backend.saveProfileSettings(self.profile, + self.profileGeneral, + self.profilePronunciations, + self.profileKeybindings) + def _setProfilePronunciations(self, pronunciations: Dict[str, Any]) -> None: """Set the changed pronunciations settings from the defaults' ones as the profile's.""" @@ -664,9 +718,12 @@ class SettingsManager(object): if not self._backend: return + profileScopedKeys = {'activePlugins', 'pluginSources'} appGeneral = {} profileGeneral = self.getGeneralSettings(self.profile) if self.profile else {} for key, value in general.items(): + if key in profileScopedKeys: + continue if value != profileGeneral.get(key): appGeneral[key] = value @@ -705,24 +762,17 @@ class SettingsManager(object): currentProfile = _profile[1] self.profile = currentProfile + self._ensurePluginPersistenceSettings(general) # Elements that need to stay updated in main configuration. self.defaultGeneral['startingProfile'] = general.get('startingProfile', _profile) - self._setProfileGeneral(general) - self._setProfilePronunciations(pronunciations) - self._setProfileKeybindings(keybindings) + self.saveProfileSettings(general, pronunciations, keybindings) tokens = ["SETTINGS MANAGER: Saving for backend", self._backend] debug.printTokens(debug.LEVEL_INFO, tokens, True) - if self._backend and self.profile: - self._backend.saveProfileSettings(self.profile, - self.profileGeneral, - self.profilePronunciations, - self.profileKeybindings) - tokens = ["SETTINGS MANAGER: Settings for", script, "(app:", script.app, ") saved"] debug.printTokens(debug.LEVEL_INFO, tokens, True) return self._enableAccessibility()