First round of bug fixes and plugin capability extensions. Creating a preferences tab should no longer require editing Cthulhu itself.
This commit is contained in:
@@ -29,6 +29,7 @@ toolkit, OpenOffice/LibreOffice, Gecko, WebKitGtk, and KDE Qt toolkit.
|
|||||||
- **Extensible architecture**: Plugin system using pluggy framework
|
- **Extensible architecture**: Plugin system using pluggy framework
|
||||||
- **Hot-reloadable plugins**: Add functionality without restarting
|
- **Hot-reloadable plugins**: Add functionality without restarting
|
||||||
- **Community plugins**: User and system plugin directories
|
- **Community plugins**: User and system plugin directories
|
||||||
|
- **Plugin preferences**: Plugins can provide preferences pages that appear only when the plugin is active
|
||||||
|
|
||||||
### Remote Control
|
### Remote Control
|
||||||
- **D-Bus interface**: External control via D-Bus service
|
- **D-Bus interface**: External control via D-Bus service
|
||||||
@@ -83,6 +84,13 @@ The `PluginSystemManager` module provides **session-only** plugin control (no pr
|
|||||||
- `SetPluginActive` (parameterized)
|
- `SetPluginActive` (parameterized)
|
||||||
- `RescanPlugins`
|
- `RescanPlugins`
|
||||||
|
|
||||||
|
### Plugin Preferences Pages
|
||||||
|
|
||||||
|
Plugins can add their own Preferences tab without modifying Cthulhu core code.
|
||||||
|
Implement `getPreferencesGUI()` to return a Gtk widget (or `(widget, label)`),
|
||||||
|
and `getPreferencesFromGUI()` to return a settings dict. The tab appears only
|
||||||
|
when the plugin is active.
|
||||||
|
|
||||||
### More Documentation
|
### More Documentation
|
||||||
|
|
||||||
See `README-REMOTE-CONTROLLER.md` and `REMOTE-CONTROLLER-COMMANDS.md` for the full D-Bus API
|
See `README-REMOTE-CONTROLLER.md` and `REMOTE-CONTROLLER-COMMANDS.md` for the full D-Bus API
|
||||||
|
|||||||
+12
-1
@@ -470,7 +470,18 @@ def loadUserSettings(script=None, inputEvent=None, skipReloadMessage=False):
|
|||||||
cthulhuApp.getSignalManager().emitSignal('load-setting-begin')
|
cthulhuApp.getSignalManager().emitSignal('load-setting-begin')
|
||||||
|
|
||||||
# NOW load plugins after script system is ready
|
# NOW load plugins after script system is ready
|
||||||
activePlugins = list(_settingsManager.getSetting('activePlugins'))
|
activePluginsSetting = _settingsManager.getSetting('activePlugins') or []
|
||||||
|
debug.printMessage(
|
||||||
|
debug.LEVEL_INFO,
|
||||||
|
f"CTHULHU: Active plugins defaults: {settings.activePlugins}",
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
debug.printMessage(
|
||||||
|
debug.LEVEL_INFO,
|
||||||
|
f"CTHULHU: Active plugins from settings manager: {activePluginsSetting}",
|
||||||
|
True,
|
||||||
|
)
|
||||||
|
activePlugins = list(activePluginsSetting)
|
||||||
debug.printMessage(debug.LEVEL_INFO, f'CTHULHU: Loading active plugins: {activePlugins}', True)
|
debug.printMessage(debug.LEVEL_INFO, f'CTHULHU: Loading active plugins: {activePlugins}', True)
|
||||||
cthulhuApp.getPluginSystemManager().setActivePlugins(activePlugins)
|
cthulhuApp.getPluginSystemManager().setActivePlugins(activePlugins)
|
||||||
|
|
||||||
|
|||||||
@@ -155,6 +155,9 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
|||||||
self._plugin_update_progress = None
|
self._plugin_update_progress = None
|
||||||
self._plugin_update_status = None
|
self._plugin_update_status = None
|
||||||
self._plugin_update_in_progress = False
|
self._plugin_update_in_progress = False
|
||||||
|
self._plugin_tabs = {}
|
||||||
|
self._plugin_tabs_cached = False
|
||||||
|
self._dynamic_plugin_tabs = {}
|
||||||
self.screenHeight = None
|
self.screenHeight = None
|
||||||
self.screenWidth = None
|
self.screenWidth = None
|
||||||
self.speechFamiliesChoice = None
|
self.speechFamiliesChoice = None
|
||||||
@@ -396,6 +399,9 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
|||||||
label = Gtk.Label(label=AXObject.get_name(self.script.app))
|
label = Gtk.Label(label=AXObject.get_name(self.script.app))
|
||||||
self.get_widget("notebook").append_page(appPage, label)
|
self.get_widget("notebook").append_page(appPage, label)
|
||||||
|
|
||||||
|
self._cache_plugin_tabs()
|
||||||
|
self._update_plugin_tabs()
|
||||||
|
self._refresh_dynamic_plugin_tabs()
|
||||||
self._initGUIState()
|
self._initGUIState()
|
||||||
self._initSoundThemeState()
|
self._initSoundThemeState()
|
||||||
|
|
||||||
@@ -799,6 +805,7 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
|||||||
path = self._plugin_model.get_path(new_iter)
|
path = self._plugin_model.get_path(new_iter)
|
||||||
self._plugin_treeview.expand_to_path(path)
|
self._plugin_treeview.expand_to_path(path)
|
||||||
selection.select_path(path)
|
selection.select_path(path)
|
||||||
|
self._update_plugin_tabs()
|
||||||
|
|
||||||
def _get_active_plugins_from_ui(self):
|
def _get_active_plugins_from_ui(self):
|
||||||
existing_plugins = list(self.prefsDict.get("activePlugins", settings.activePlugins) or [])
|
existing_plugins = list(self.prefsDict.get("activePlugins", settings.activePlugins) or [])
|
||||||
@@ -835,6 +842,147 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
|||||||
|
|
||||||
self._plugin_sources_original = list(plugin_sources)
|
self._plugin_sources_original = list(plugin_sources)
|
||||||
self._populate_plugin_list()
|
self._populate_plugin_list()
|
||||||
|
self._update_plugin_tabs(active_plugins)
|
||||||
|
|
||||||
|
def _get_active_plugin_infos(self):
|
||||||
|
manager = cthulhu.cthulhuApp.getPluginSystemManager()
|
||||||
|
if not manager:
|
||||||
|
return []
|
||||||
|
return [info for info in manager.plugins if info.loaded and info.instance]
|
||||||
|
|
||||||
|
def _get_plugin_preferences_page(self, plugin_info):
|
||||||
|
plugin_instance = plugin_info.instance
|
||||||
|
if not plugin_instance:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if hasattr(plugin_instance, "getPreferencesGUI"):
|
||||||
|
page = plugin_instance.getPreferencesGUI()
|
||||||
|
elif hasattr(plugin_instance, "get_preferences_gui"):
|
||||||
|
page = plugin_instance.get_preferences_gui()
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not page:
|
||||||
|
return None
|
||||||
|
|
||||||
|
label = plugin_info.get_name() or plugin_info.get_module_name()
|
||||||
|
if isinstance(page, (list, tuple)) and len(page) == 2:
|
||||||
|
page, label = page
|
||||||
|
|
||||||
|
if isinstance(label, str):
|
||||||
|
label_widget = Gtk.Label(label=label)
|
||||||
|
else:
|
||||||
|
label_widget = label
|
||||||
|
|
||||||
|
return page, label_widget
|
||||||
|
|
||||||
|
def _refresh_dynamic_plugin_tabs(self):
|
||||||
|
notebook = self.get_widget("notebook")
|
||||||
|
if not notebook:
|
||||||
|
return
|
||||||
|
|
||||||
|
for tab in list(self._dynamic_plugin_tabs.values()):
|
||||||
|
page = tab.get("page")
|
||||||
|
if page:
|
||||||
|
page_num = notebook.page_num(page)
|
||||||
|
if page_num != -1:
|
||||||
|
notebook.remove_page(page_num)
|
||||||
|
|
||||||
|
self._dynamic_plugin_tabs = {}
|
||||||
|
|
||||||
|
plugin_infos = self._get_active_plugin_infos()
|
||||||
|
for plugin_info in sorted(plugin_infos, key=lambda item: (item.get_name() or item.get_module_name()).lower()):
|
||||||
|
result = self._get_plugin_preferences_page(plugin_info)
|
||||||
|
if not result:
|
||||||
|
continue
|
||||||
|
|
||||||
|
page, label = result
|
||||||
|
if not page or not label:
|
||||||
|
continue
|
||||||
|
|
||||||
|
notebook.append_page(page, label)
|
||||||
|
page.show_all()
|
||||||
|
label.show()
|
||||||
|
self._dynamic_plugin_tabs[plugin_info.get_module_name()] = {
|
||||||
|
"page": page,
|
||||||
|
"label": label,
|
||||||
|
}
|
||||||
|
|
||||||
|
for plugin_name in self._dynamic_plugin_tabs:
|
||||||
|
tab = self._plugin_tabs.get(plugin_name)
|
||||||
|
if not tab:
|
||||||
|
continue
|
||||||
|
page = tab.get("page")
|
||||||
|
if not page:
|
||||||
|
continue
|
||||||
|
page_num = notebook.page_num(page)
|
||||||
|
if page_num != -1:
|
||||||
|
notebook.remove_page(page_num)
|
||||||
|
|
||||||
|
def _cache_plugin_tabs(self):
|
||||||
|
if self._plugin_tabs_cached:
|
||||||
|
return
|
||||||
|
|
||||||
|
notebook = self.get_widget("notebook")
|
||||||
|
if not notebook:
|
||||||
|
return
|
||||||
|
|
||||||
|
tab_specs = [
|
||||||
|
("AIAssistant", "aiPage", "aiTabLabel"),
|
||||||
|
("OCR", "ocrGrid", "ocrTabLabel"),
|
||||||
|
]
|
||||||
|
|
||||||
|
for plugin_name, page_id, label_id in tab_specs:
|
||||||
|
page = self.get_widget(page_id)
|
||||||
|
label = self.get_widget(label_id)
|
||||||
|
if not page or not label:
|
||||||
|
continue
|
||||||
|
position = notebook.page_num(page)
|
||||||
|
self._plugin_tabs[plugin_name] = {
|
||||||
|
"page": page,
|
||||||
|
"label": label,
|
||||||
|
"position": position,
|
||||||
|
}
|
||||||
|
|
||||||
|
self._plugin_tabs_cached = True
|
||||||
|
|
||||||
|
def _get_active_plugins_for_tabs(self):
|
||||||
|
if self._plugin_iters:
|
||||||
|
return self._get_active_plugins_from_ui()
|
||||||
|
return list(self.prefsDict.get("activePlugins", settings.activePlugins) or [])
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _plugin_active(active_plugins, names):
|
||||||
|
active_lower = {name.lower() for name in active_plugins}
|
||||||
|
return any(name.lower() in active_lower for name in names)
|
||||||
|
|
||||||
|
def _update_plugin_tabs(self, active_plugins=None):
|
||||||
|
if not self._plugin_tabs:
|
||||||
|
return
|
||||||
|
|
||||||
|
notebook = self.get_widget("notebook")
|
||||||
|
if not notebook:
|
||||||
|
return
|
||||||
|
|
||||||
|
if active_plugins is None:
|
||||||
|
active_plugins = self._get_active_plugins_for_tabs()
|
||||||
|
|
||||||
|
for plugin_name, tab in self._plugin_tabs.items():
|
||||||
|
if plugin_name in self._dynamic_plugin_tabs:
|
||||||
|
continue
|
||||||
|
page = tab["page"]
|
||||||
|
label = tab["label"]
|
||||||
|
position = tab["position"]
|
||||||
|
page_num = notebook.page_num(page)
|
||||||
|
is_active = self._plugin_active(active_plugins, [plugin_name])
|
||||||
|
|
||||||
|
if is_active and page_num == -1:
|
||||||
|
insert_pos = min(max(position, 0), notebook.get_n_pages())
|
||||||
|
notebook.insert_page(page, label, insert_pos)
|
||||||
|
page.show_all()
|
||||||
|
label.show()
|
||||||
|
elif not is_active and page_num != -1:
|
||||||
|
notebook.remove_page(page_num)
|
||||||
|
|
||||||
def _getACSSForVoiceType(self, voiceType):
|
def _getACSSForVoiceType(self, voiceType):
|
||||||
"""Return the ACSS value for the given voice type.
|
"""Return the ACSS value for the given voice type.
|
||||||
@@ -864,11 +1012,31 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
|||||||
pronunciationDict = self.getModelDict(self.pronunciationModel)
|
pronunciationDict = self.getModelDict(self.pronunciationModel)
|
||||||
keyBindingsDict = self.getKeyBindingsModelDict(self.keyBindingsModel)
|
keyBindingsDict = self.getKeyBindingsModelDict(self.keyBindingsModel)
|
||||||
self.prefsDict.update(self.script.getPreferencesFromGUI())
|
self.prefsDict.update(self.script.getPreferencesFromGUI())
|
||||||
|
self.prefsDict.update(self._get_plugin_preferences_from_gui())
|
||||||
_settingsManager.saveSettings(self.script,
|
_settingsManager.saveSettings(self.script,
|
||||||
self.prefsDict,
|
self.prefsDict,
|
||||||
pronunciationDict,
|
pronunciationDict,
|
||||||
keyBindingsDict)
|
keyBindingsDict)
|
||||||
|
|
||||||
|
def _get_plugin_preferences_from_gui(self):
|
||||||
|
preferences = {}
|
||||||
|
for plugin_info in self._get_active_plugin_infos():
|
||||||
|
plugin_instance = plugin_info.instance
|
||||||
|
if not plugin_instance:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if hasattr(plugin_instance, "getPreferencesFromGUI"):
|
||||||
|
plugin_prefs = plugin_instance.getPreferencesFromGUI()
|
||||||
|
elif hasattr(plugin_instance, "get_preferences_from_gui"):
|
||||||
|
plugin_prefs = plugin_instance.get_preferences_from_gui()
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if isinstance(plugin_prefs, dict):
|
||||||
|
preferences.update(plugin_prefs)
|
||||||
|
|
||||||
|
return preferences
|
||||||
|
|
||||||
def _getKeyValueForVoiceType(self, voiceType, key, useDefault=True):
|
def _getKeyValueForVoiceType(self, voiceType, key, useDefault=True):
|
||||||
"""Look for the value of the given key in the voice dictionary
|
"""Look for the value of the given key in the voice dictionary
|
||||||
for the given voice type.
|
for the given voice type.
|
||||||
@@ -2283,9 +2451,13 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
|||||||
if self.script.app:
|
if self.script.app:
|
||||||
self.get_widget('profilesFrame').set_sensitive(False)
|
self.get_widget('profilesFrame').set_sensitive(False)
|
||||||
|
|
||||||
|
active_plugins = self._get_active_plugins_for_tabs()
|
||||||
|
self._update_plugin_tabs(active_plugins)
|
||||||
|
|
||||||
# AI Assistant settings
|
# AI Assistant settings
|
||||||
#
|
#
|
||||||
self._initAIState()
|
if self._plugin_active(active_plugins, ["AIAssistant"]):
|
||||||
|
self._initAIState()
|
||||||
|
|
||||||
# Indentation settings
|
# Indentation settings
|
||||||
#
|
#
|
||||||
@@ -2293,7 +2465,8 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
|||||||
|
|
||||||
# OCR Plugin settings
|
# OCR Plugin settings
|
||||||
#
|
#
|
||||||
self._initOCRState()
|
if self._plugin_active(active_plugins, ["OCR"]):
|
||||||
|
self._initOCRState()
|
||||||
|
|
||||||
def __initProfileCombo(self):
|
def __initProfileCombo(self):
|
||||||
"""Adding available profiles and setting active as the active one"""
|
"""Adding available profiles and setting active as the active one"""
|
||||||
@@ -4149,6 +4322,7 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
|||||||
self._apply_plugin_changes()
|
self._apply_plugin_changes()
|
||||||
self.writeUserPreferences()
|
self.writeUserPreferences()
|
||||||
cthulhu.loadUserSettings(self.script)
|
cthulhu.loadUserSettings(self.script)
|
||||||
|
self._refresh_dynamic_plugin_tabs()
|
||||||
braille.checkBrailleSetting()
|
braille.checkBrailleSetting()
|
||||||
self._initSpeechState()
|
self._initSpeechState()
|
||||||
self._populateKeyBindings()
|
self._populateKeyBindings()
|
||||||
|
|||||||
@@ -87,6 +87,14 @@ class Plugin:
|
|||||||
"""Get keybindings for this plugin. Override in subclasses."""
|
"""Get keybindings for this plugin. Override in subclasses."""
|
||||||
return self._bindings
|
return self._bindings
|
||||||
|
|
||||||
|
def getPreferencesGUI(self):
|
||||||
|
"""Return a Gtk widget for plugin preferences, or None if not provided."""
|
||||||
|
return None
|
||||||
|
|
||||||
|
def getPreferencesFromGUI(self):
|
||||||
|
"""Return a dict of plugin preferences from the GUI."""
|
||||||
|
return {}
|
||||||
|
|
||||||
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:
|
||||||
|
|||||||
@@ -3,5 +3,5 @@ Name = AI Assistant
|
|||||||
Module = AIAssistant
|
Module = AIAssistant
|
||||||
Description = AI-powered accessibility assistant for analyzing screens and taking actions
|
Description = AI-powered accessibility assistant for analyzing screens and taking actions
|
||||||
Authors = Stormux <storm_dragon@stormux.org>
|
Authors = Stormux <storm_dragon@stormux.org>
|
||||||
Version = 1.0.0
|
Version = 2.0.0
|
||||||
Category = Accessibility
|
Category = Accessibility
|
||||||
|
|||||||
@@ -69,6 +69,8 @@ class AIAssistant(Plugin):
|
|||||||
|
|
||||||
# Pre-captured screen data (to avoid capturing dialog itself)
|
# Pre-captured screen data (to avoid capturing dialog itself)
|
||||||
self._current_screen_data = None
|
self._current_screen_data = None
|
||||||
|
self._prefs_grid = None
|
||||||
|
self._prefs_widgets = {}
|
||||||
|
|
||||||
@cthulhu_hookimpl
|
@cthulhu_hookimpl
|
||||||
def activate(self, plugin=None):
|
def activate(self, plugin=None):
|
||||||
@@ -128,6 +130,215 @@ class AIAssistant(Plugin):
|
|||||||
self._unregister_keybindings()
|
self._unregister_keybindings()
|
||||||
|
|
||||||
self._enabled = False
|
self._enabled = False
|
||||||
|
|
||||||
|
def getPreferencesGUI(self):
|
||||||
|
if not self._prefs_grid:
|
||||||
|
self._prefs_grid = self._build_preferences_gui()
|
||||||
|
self._load_preferences_into_widgets()
|
||||||
|
return self._prefs_grid, "AI Assistant"
|
||||||
|
|
||||||
|
def getPreferencesFromGUI(self):
|
||||||
|
if not self._prefs_widgets:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
provider_values = self._prefs_widgets.get("provider_values", [])
|
||||||
|
quality_values = self._prefs_widgets.get("quality_values", [])
|
||||||
|
provider_index = self._prefs_widgets["provider_combo"].get_active()
|
||||||
|
quality_index = self._prefs_widgets["quality_combo"].get_active()
|
||||||
|
|
||||||
|
if provider_index < 0 or provider_index >= len(provider_values):
|
||||||
|
provider = settings.aiProvider
|
||||||
|
else:
|
||||||
|
provider = provider_values[provider_index]
|
||||||
|
|
||||||
|
if quality_index < 0 or quality_index >= len(quality_values):
|
||||||
|
screenshot_quality = settings.aiScreenshotQuality
|
||||||
|
else:
|
||||||
|
screenshot_quality = quality_values[quality_index]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"aiAssistantEnabled": self._prefs_widgets["enable_check"].get_active(),
|
||||||
|
"aiProvider": provider,
|
||||||
|
"aiApiKeyFile": self._prefs_widgets["api_key_entry"].get_text().strip(),
|
||||||
|
"aiOllamaModel": self._prefs_widgets["ollama_model_entry"].get_text().strip(),
|
||||||
|
"aiOllamaEndpoint": self._prefs_widgets["ollama_endpoint_entry"].get_text().strip(),
|
||||||
|
"aiConfirmationRequired": self._prefs_widgets["confirmation_check"].get_active(),
|
||||||
|
"aiScreenshotQuality": screenshot_quality,
|
||||||
|
}
|
||||||
|
|
||||||
|
def _build_preferences_gui(self):
|
||||||
|
grid = Gtk.Grid(
|
||||||
|
row_spacing=6,
|
||||||
|
column_spacing=12,
|
||||||
|
margin_left=12,
|
||||||
|
margin_right=12,
|
||||||
|
margin_top=12,
|
||||||
|
margin_bottom=12,
|
||||||
|
)
|
||||||
|
|
||||||
|
row = 0
|
||||||
|
|
||||||
|
enable_check = Gtk.CheckButton(label="Enable AI Assistant")
|
||||||
|
enable_check.connect("toggled", self._on_ai_enabled_toggled)
|
||||||
|
grid.attach(enable_check, 0, row, 2, 1)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
provider_label = Gtk.Label(label="_Provider:")
|
||||||
|
provider_label.set_use_underline(True)
|
||||||
|
provider_label.set_halign(Gtk.Align.START)
|
||||||
|
|
||||||
|
provider_combo = Gtk.ComboBoxText()
|
||||||
|
provider_combo.set_hexpand(True)
|
||||||
|
provider_values = [
|
||||||
|
settings.AI_PROVIDER_CLAUDE_CODE,
|
||||||
|
settings.AI_PROVIDER_CODEX,
|
||||||
|
settings.AI_PROVIDER_GEMINI,
|
||||||
|
settings.AI_PROVIDER_OLLAMA,
|
||||||
|
]
|
||||||
|
provider_labels = ["Claude Code", "Codex", "Gemini", "Ollama"]
|
||||||
|
for label in provider_labels:
|
||||||
|
provider_combo.append_text(label)
|
||||||
|
provider_combo.connect("changed", self._on_provider_changed)
|
||||||
|
provider_label.set_mnemonic_widget(provider_combo)
|
||||||
|
|
||||||
|
grid.attach(provider_label, 0, row, 1, 1)
|
||||||
|
grid.attach(provider_combo, 1, row, 1, 1)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
api_key_label = Gtk.Label(label="API _Key File:")
|
||||||
|
api_key_label.set_use_underline(True)
|
||||||
|
api_key_label.set_halign(Gtk.Align.START)
|
||||||
|
api_key_entry = Gtk.Entry()
|
||||||
|
api_key_entry.set_hexpand(True)
|
||||||
|
api_key_label.set_mnemonic_widget(api_key_entry)
|
||||||
|
|
||||||
|
grid.attach(api_key_label, 0, row, 1, 1)
|
||||||
|
grid.attach(api_key_entry, 1, row, 1, 1)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
ollama_model_label = Gtk.Label(label="_Ollama Model:")
|
||||||
|
ollama_model_label.set_use_underline(True)
|
||||||
|
ollama_model_label.set_halign(Gtk.Align.START)
|
||||||
|
ollama_model_entry = Gtk.Entry()
|
||||||
|
ollama_model_entry.set_hexpand(True)
|
||||||
|
ollama_model_label.set_mnemonic_widget(ollama_model_entry)
|
||||||
|
|
||||||
|
grid.attach(ollama_model_label, 0, row, 1, 1)
|
||||||
|
grid.attach(ollama_model_entry, 1, row, 1, 1)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
ollama_endpoint_label = Gtk.Label(label="Ollama _Endpoint:")
|
||||||
|
ollama_endpoint_label.set_use_underline(True)
|
||||||
|
ollama_endpoint_label.set_halign(Gtk.Align.START)
|
||||||
|
ollama_endpoint_entry = Gtk.Entry()
|
||||||
|
ollama_endpoint_entry.set_hexpand(True)
|
||||||
|
ollama_endpoint_label.set_mnemonic_widget(ollama_endpoint_entry)
|
||||||
|
|
||||||
|
grid.attach(ollama_endpoint_label, 0, row, 1, 1)
|
||||||
|
grid.attach(ollama_endpoint_entry, 1, row, 1, 1)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
confirmation_check = Gtk.CheckButton(label="Require confirmation before actions")
|
||||||
|
grid.attach(confirmation_check, 0, row, 2, 1)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
quality_label = Gtk.Label(label="Screenshot _Quality:")
|
||||||
|
quality_label.set_use_underline(True)
|
||||||
|
quality_label.set_halign(Gtk.Align.START)
|
||||||
|
quality_combo = Gtk.ComboBoxText()
|
||||||
|
quality_combo.set_hexpand(True)
|
||||||
|
quality_values = [
|
||||||
|
settings.AI_SCREENSHOT_QUALITY_LOW,
|
||||||
|
settings.AI_SCREENSHOT_QUALITY_MEDIUM,
|
||||||
|
settings.AI_SCREENSHOT_QUALITY_HIGH,
|
||||||
|
]
|
||||||
|
quality_labels = ["Low", "Medium", "High"]
|
||||||
|
for label in quality_labels:
|
||||||
|
quality_combo.append_text(label)
|
||||||
|
quality_label.set_mnemonic_widget(quality_combo)
|
||||||
|
|
||||||
|
grid.attach(quality_label, 0, row, 1, 1)
|
||||||
|
grid.attach(quality_combo, 1, row, 1, 1)
|
||||||
|
|
||||||
|
self._prefs_widgets = {
|
||||||
|
"enable_check": enable_check,
|
||||||
|
"provider_combo": provider_combo,
|
||||||
|
"provider_values": provider_values,
|
||||||
|
"api_key_entry": api_key_entry,
|
||||||
|
"ollama_model_entry": ollama_model_entry,
|
||||||
|
"ollama_endpoint_entry": ollama_endpoint_entry,
|
||||||
|
"confirmation_check": confirmation_check,
|
||||||
|
"quality_combo": quality_combo,
|
||||||
|
"quality_values": quality_values,
|
||||||
|
}
|
||||||
|
|
||||||
|
return grid
|
||||||
|
|
||||||
|
def _load_preferences_into_widgets(self):
|
||||||
|
if not self._prefs_widgets:
|
||||||
|
return
|
||||||
|
|
||||||
|
enabled = self._settings_manager.getSetting("aiAssistantEnabled")
|
||||||
|
if enabled is None:
|
||||||
|
enabled = settings.aiAssistantEnabled
|
||||||
|
self._prefs_widgets["enable_check"].set_active(enabled)
|
||||||
|
|
||||||
|
provider = self._settings_manager.getSetting("aiProvider") or settings.aiProvider
|
||||||
|
provider_values = self._prefs_widgets.get("provider_values", [])
|
||||||
|
try:
|
||||||
|
provider_index = provider_values.index(provider)
|
||||||
|
except ValueError:
|
||||||
|
provider_index = 0
|
||||||
|
self._prefs_widgets["provider_combo"].set_active(provider_index)
|
||||||
|
|
||||||
|
api_key_file = self._settings_manager.getSetting("aiApiKeyFile") or settings.aiApiKeyFile
|
||||||
|
self._prefs_widgets["api_key_entry"].set_text(api_key_file or "")
|
||||||
|
|
||||||
|
ollama_model = self._settings_manager.getSetting("aiOllamaModel") or settings.aiOllamaModel
|
||||||
|
self._prefs_widgets["ollama_model_entry"].set_text(ollama_model or "")
|
||||||
|
|
||||||
|
ollama_endpoint = self._settings_manager.getSetting("aiOllamaEndpoint") or settings.aiOllamaEndpoint
|
||||||
|
self._prefs_widgets["ollama_endpoint_entry"].set_text(ollama_endpoint or "")
|
||||||
|
|
||||||
|
confirmation_required = self._settings_manager.getSetting("aiConfirmationRequired")
|
||||||
|
if confirmation_required is None:
|
||||||
|
confirmation_required = settings.aiConfirmationRequired
|
||||||
|
self._prefs_widgets["confirmation_check"].set_active(confirmation_required)
|
||||||
|
|
||||||
|
screenshot_quality = self._settings_manager.getSetting("aiScreenshotQuality") or settings.aiScreenshotQuality
|
||||||
|
quality_values = self._prefs_widgets.get("quality_values", [])
|
||||||
|
try:
|
||||||
|
quality_index = quality_values.index(screenshot_quality)
|
||||||
|
except ValueError:
|
||||||
|
quality_index = 0
|
||||||
|
self._prefs_widgets["quality_combo"].set_active(quality_index)
|
||||||
|
|
||||||
|
self._update_preferences_sensitivity()
|
||||||
|
|
||||||
|
def _on_ai_enabled_toggled(self, widget):
|
||||||
|
self._update_preferences_sensitivity()
|
||||||
|
|
||||||
|
def _on_provider_changed(self, widget):
|
||||||
|
self._update_preferences_sensitivity()
|
||||||
|
|
||||||
|
def _update_preferences_sensitivity(self):
|
||||||
|
if not self._prefs_widgets:
|
||||||
|
return
|
||||||
|
|
||||||
|
enabled = self._prefs_widgets["enable_check"].get_active()
|
||||||
|
provider_values = self._prefs_widgets.get("provider_values", [])
|
||||||
|
provider_index = self._prefs_widgets["provider_combo"].get_active()
|
||||||
|
provider = provider_values[provider_index] if 0 <= provider_index < len(provider_values) else settings.aiProvider
|
||||||
|
|
||||||
|
is_gemini = provider == settings.AI_PROVIDER_GEMINI
|
||||||
|
is_ollama = provider == settings.AI_PROVIDER_OLLAMA
|
||||||
|
|
||||||
|
self._prefs_widgets["provider_combo"].set_sensitive(enabled)
|
||||||
|
self._prefs_widgets["api_key_entry"].set_sensitive(enabled and is_gemini)
|
||||||
|
self._prefs_widgets["ollama_model_entry"].set_sensitive(enabled and is_ollama)
|
||||||
|
self._prefs_widgets["ollama_endpoint_entry"].set_sensitive(enabled and is_ollama)
|
||||||
|
self._prefs_widgets["confirmation_check"].set_sensitive(enabled)
|
||||||
|
self._prefs_widgets["quality_combo"].set_sensitive(enabled)
|
||||||
|
|
||||||
def refresh_settings(self):
|
def refresh_settings(self):
|
||||||
"""Refresh plugin settings and reinitialize provider. Called when settings change."""
|
"""Refresh plugin settings and reinitialize provider. Called when settings change."""
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
name = IndentationAudio
|
name = IndentationAudio
|
||||||
version = 1.0.0
|
version = 2.0.0
|
||||||
description = Provides audio feedback for indentation level changes when navigating code or text
|
description = Provides audio feedback for indentation level changes when navigating code or text
|
||||||
authors = Stormux <storm_dragon@stormux.org>
|
authors = Stormux <storm_dragon@stormux.org>
|
||||||
website = https://git.stormux.org/storm/cthulhu
|
website = https://git.stormux.org/storm/cthulhu
|
||||||
copyright = Copyright 2025
|
copyright = Copyright 2025
|
||||||
builtin = false
|
builtin = false
|
||||||
hidden = false
|
hidden = false
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
import logging
|
import logging
|
||||||
import math
|
import math
|
||||||
import re
|
import re
|
||||||
from gi.repository import GLib
|
from gi.repository import GLib, Gtk
|
||||||
|
|
||||||
from cthulhu.plugin import Plugin, cthulhu_hookimpl
|
from cthulhu.plugin import Plugin, cthulhu_hookimpl
|
||||||
from cthulhu import debug
|
from cthulhu import debug
|
||||||
@@ -56,6 +56,8 @@ class IndentationAudio(Plugin):
|
|||||||
self._saved_speech_indentation = None
|
self._saved_speech_indentation = None
|
||||||
|
|
||||||
self._activated = False
|
self._activated = False
|
||||||
|
self._prefs_grid = None
|
||||||
|
self._prefs_widgets = {}
|
||||||
debug.printMessage(debug.LEVEL_INFO, "IndentationAudio: Plugin initialized", True)
|
debug.printMessage(debug.LEVEL_INFO, "IndentationAudio: Plugin initialized", True)
|
||||||
|
|
||||||
@cthulhu_hookimpl
|
@cthulhu_hookimpl
|
||||||
@@ -86,6 +88,7 @@ class IndentationAudio(Plugin):
|
|||||||
# Connect to text caret movement events
|
# Connect to text caret movement events
|
||||||
self._connect_to_events()
|
self._connect_to_events()
|
||||||
|
|
||||||
|
self._enabled = True
|
||||||
self._activated = True
|
self._activated = True
|
||||||
debug.printMessage(debug.LEVEL_INFO, "IndentationAudio: Plugin activated successfully", True)
|
debug.printMessage(debug.LEVEL_INFO, "IndentationAudio: Plugin activated successfully", True)
|
||||||
return True
|
return True
|
||||||
@@ -109,6 +112,7 @@ class IndentationAudio(Plugin):
|
|||||||
# Clear tracking data
|
# Clear tracking data
|
||||||
self._last_indentation_data.clear()
|
self._last_indentation_data.clear()
|
||||||
|
|
||||||
|
self._enabled = False
|
||||||
self._activated = False
|
self._activated = False
|
||||||
debug.printMessage(debug.LEVEL_INFO, "IndentationAudio: Plugin deactivated successfully", True)
|
debug.printMessage(debug.LEVEL_INFO, "IndentationAudio: Plugin deactivated successfully", True)
|
||||||
return True
|
return True
|
||||||
@@ -116,6 +120,164 @@ class IndentationAudio(Plugin):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
debug.printMessage(debug.LEVEL_INFO, f"IndentationAudio: ERROR deactivating plugin: {e}", True)
|
debug.printMessage(debug.LEVEL_INFO, f"IndentationAudio: ERROR deactivating plugin: {e}", True)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def getPreferencesGUI(self):
|
||||||
|
if not self._prefs_grid:
|
||||||
|
self._prefs_grid = self._build_preferences_gui()
|
||||||
|
self._load_preferences_into_widgets()
|
||||||
|
return self._prefs_grid, "Indentation Audio"
|
||||||
|
|
||||||
|
def getPreferencesFromGUI(self):
|
||||||
|
if not self._prefs_widgets:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
audio_unit = settings.INDENTATION_UNIT_COLUMNS
|
||||||
|
if self._prefs_widgets["audio_unit_levels"].get_active():
|
||||||
|
audio_unit = settings.INDENTATION_UNIT_LEVELS
|
||||||
|
|
||||||
|
return {
|
||||||
|
"indentationAudioUnit": audio_unit,
|
||||||
|
"indentationAudioBaseFrequency": self._prefs_widgets["base_frequency_spin"].get_value_as_int(),
|
||||||
|
"indentationAudioStepFrequency": self._prefs_widgets["step_frequency_spin"].get_value_as_int(),
|
||||||
|
"indentationAudioMaxFrequency": self._prefs_widgets["max_frequency_spin"].get_value_as_int(),
|
||||||
|
"indentationAudioDuration": float(self._prefs_widgets["duration_spin"].get_value()),
|
||||||
|
"indentationAudioVolume": float(self._prefs_widgets["volume_spin"].get_value()),
|
||||||
|
}
|
||||||
|
|
||||||
|
def _build_preferences_gui(self):
|
||||||
|
grid = Gtk.Grid(
|
||||||
|
row_spacing=6,
|
||||||
|
column_spacing=12,
|
||||||
|
margin_left=12,
|
||||||
|
margin_right=12,
|
||||||
|
margin_top=12,
|
||||||
|
margin_bottom=12,
|
||||||
|
)
|
||||||
|
|
||||||
|
row = 0
|
||||||
|
|
||||||
|
description = Gtk.Label(label="Configure audio feedback for indentation changes.")
|
||||||
|
description.set_line_wrap(True)
|
||||||
|
description.set_halign(Gtk.Align.START)
|
||||||
|
grid.attach(description, 0, row, 2, 1)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
audio_unit_label = Gtk.Label(label="Audio _Unit:")
|
||||||
|
audio_unit_label.set_use_underline(True)
|
||||||
|
audio_unit_label.set_halign(Gtk.Align.START)
|
||||||
|
|
||||||
|
audio_unit_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=4)
|
||||||
|
audio_unit_levels = Gtk.RadioButton(label="Levels")
|
||||||
|
audio_unit_columns = Gtk.RadioButton.new_with_label_from_widget(audio_unit_levels, "Columns")
|
||||||
|
audio_unit_box.pack_start(audio_unit_levels, False, False, 0)
|
||||||
|
audio_unit_box.pack_start(audio_unit_columns, False, False, 0)
|
||||||
|
|
||||||
|
audio_unit_label.set_mnemonic_widget(audio_unit_levels)
|
||||||
|
grid.attach(audio_unit_label, 0, row, 1, 1)
|
||||||
|
grid.attach(audio_unit_box, 1, row, 1, 1)
|
||||||
|
row += 1
|
||||||
|
|
||||||
|
base_adjust = Gtk.Adjustment(200, 50, 2000, 10, 50, 0)
|
||||||
|
step_adjust = Gtk.Adjustment(80, 1, 500, 5, 20, 0)
|
||||||
|
max_adjust = Gtk.Adjustment(1200, 200, 5000, 50, 100, 0)
|
||||||
|
duration_adjust = Gtk.Adjustment(0.15, 0.01, 2, 0.01, 0.1, 0)
|
||||||
|
volume_adjust = Gtk.Adjustment(0.7, 0, 1, 0.05, 0.1, 0)
|
||||||
|
|
||||||
|
base_frequency_spin, row = self._add_spin_row(
|
||||||
|
grid,
|
||||||
|
row,
|
||||||
|
"Base _Frequency (Hz):",
|
||||||
|
base_adjust,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
step_frequency_spin, row = self._add_spin_row(
|
||||||
|
grid,
|
||||||
|
row,
|
||||||
|
"_Step per Unit (Hz):",
|
||||||
|
step_adjust,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
max_frequency_spin, row = self._add_spin_row(
|
||||||
|
grid,
|
||||||
|
row,
|
||||||
|
"_Maximum Frequency (Hz):",
|
||||||
|
max_adjust,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
duration_spin, row = self._add_spin_row(
|
||||||
|
grid,
|
||||||
|
row,
|
||||||
|
"Tone _Duration (sec):",
|
||||||
|
duration_adjust,
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
volume_spin, row = self._add_spin_row(
|
||||||
|
grid,
|
||||||
|
row,
|
||||||
|
"_Volume Multiplier:",
|
||||||
|
volume_adjust,
|
||||||
|
2,
|
||||||
|
)
|
||||||
|
|
||||||
|
self._prefs_widgets = {
|
||||||
|
"audio_unit_levels": audio_unit_levels,
|
||||||
|
"audio_unit_columns": audio_unit_columns,
|
||||||
|
"base_frequency_spin": base_frequency_spin,
|
||||||
|
"step_frequency_spin": step_frequency_spin,
|
||||||
|
"max_frequency_spin": max_frequency_spin,
|
||||||
|
"duration_spin": duration_spin,
|
||||||
|
"volume_spin": volume_spin,
|
||||||
|
}
|
||||||
|
|
||||||
|
return grid
|
||||||
|
|
||||||
|
def _add_spin_row(self, grid, row, label_text, adjustment, digits):
|
||||||
|
label = Gtk.Label(label=label_text)
|
||||||
|
label.set_use_underline(True)
|
||||||
|
label.set_halign(Gtk.Align.START)
|
||||||
|
spin = Gtk.SpinButton(adjustment=adjustment, climb_rate=1, numeric=True)
|
||||||
|
if digits:
|
||||||
|
spin.set_digits(digits)
|
||||||
|
label.set_mnemonic_widget(spin)
|
||||||
|
|
||||||
|
grid.attach(label, 0, row, 1, 1)
|
||||||
|
grid.attach(spin, 1, row, 1, 1)
|
||||||
|
return spin, row + 1
|
||||||
|
|
||||||
|
def _load_preferences_into_widgets(self):
|
||||||
|
if not self._prefs_widgets:
|
||||||
|
return
|
||||||
|
|
||||||
|
audio_unit = _settingsManager.getSetting("indentationAudioUnit") or settings.indentationAudioUnit
|
||||||
|
if audio_unit == settings.INDENTATION_UNIT_LEVELS:
|
||||||
|
self._prefs_widgets["audio_unit_levels"].set_active(True)
|
||||||
|
else:
|
||||||
|
self._prefs_widgets["audio_unit_columns"].set_active(True)
|
||||||
|
|
||||||
|
base_frequency = _settingsManager.getSetting("indentationAudioBaseFrequency")
|
||||||
|
if base_frequency is None:
|
||||||
|
base_frequency = settings.indentationAudioBaseFrequency
|
||||||
|
self._prefs_widgets["base_frequency_spin"].set_value(base_frequency)
|
||||||
|
|
||||||
|
step_frequency = _settingsManager.getSetting("indentationAudioStepFrequency")
|
||||||
|
if step_frequency is None:
|
||||||
|
step_frequency = settings.indentationAudioStepFrequency
|
||||||
|
self._prefs_widgets["step_frequency_spin"].set_value(step_frequency)
|
||||||
|
|
||||||
|
max_frequency = _settingsManager.getSetting("indentationAudioMaxFrequency")
|
||||||
|
if max_frequency is None:
|
||||||
|
max_frequency = settings.indentationAudioMaxFrequency
|
||||||
|
self._prefs_widgets["max_frequency_spin"].set_value(max_frequency)
|
||||||
|
|
||||||
|
duration_value = _settingsManager.getSetting("indentationAudioDuration")
|
||||||
|
if duration_value is None:
|
||||||
|
duration_value = settings.indentationAudioDuration
|
||||||
|
self._prefs_widgets["duration_spin"].set_value(duration_value)
|
||||||
|
|
||||||
|
volume_value = _settingsManager.getSetting("indentationAudioVolume")
|
||||||
|
if volume_value is None:
|
||||||
|
volume_value = settings.indentationAudioVolume
|
||||||
|
self._prefs_widgets["volume_spin"].set_value(volume_value)
|
||||||
|
|
||||||
def _register_keybinding(self):
|
def _register_keybinding(self):
|
||||||
"""Register the Cthulhu+I keybinding for toggling the plugin."""
|
"""Register the Cthulhu+I keybinding for toggling the plugin."""
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ To add support for other languages, install additional Tesseract language packs:
|
|||||||
|
|
||||||
1. **Enable the Plugin**: The OCR plugin is enabled by default in Cthulhu. If disabled, you can enable it through:
|
1. **Enable the Plugin**: The OCR plugin is enabled by default in Cthulhu. If disabled, you can enable it through:
|
||||||
- Cthulhu Preferences → Plugins → Check "OCR"
|
- Cthulhu Preferences → Plugins → Check "OCR"
|
||||||
- Or ensure `'OCR'` is in the `activePlugins` list in settings.py
|
- Or add `OCR` to your `activePlugins` preference
|
||||||
|
|
||||||
2. **Basic OCR Workflow**:
|
2. **Basic OCR Workflow**:
|
||||||
- Navigate to content you want to OCR
|
- Navigate to content you want to OCR
|
||||||
@@ -115,7 +115,7 @@ To add support for other languages, install additional Tesseract language packs:
|
|||||||
Access comprehensive OCR settings through Cthulhu Preferences:
|
Access comprehensive OCR settings through Cthulhu Preferences:
|
||||||
|
|
||||||
1. **Open Cthulhu Preferences**: `~/.local/bin/cthulhu -s`
|
1. **Open Cthulhu Preferences**: `~/.local/bin/cthulhu -s`
|
||||||
2. **Navigate to OCR Tab**: Use keyboard navigation to find the OCR settings tab
|
2. **Navigate to OCR Tab**: Use keyboard navigation to find the OCR settings tab (only shown when the plugin is active)
|
||||||
3. **Configure Settings**: Adjust all OCR parameters through the accessible interface
|
3. **Configure Settings**: Adjust all OCR parameters through the accessible interface
|
||||||
|
|
||||||
### Available Settings
|
### Available Settings
|
||||||
@@ -318,4 +318,4 @@ For issues, questions, or contributions:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
*Part of the Cthulhu Screen Reader project - Making the desktop accessible for everyone.*
|
*Part of the Cthulhu Screen Reader project - Making the desktop accessible for everyone.*
|
||||||
|
|||||||
@@ -494,6 +494,15 @@ class PluginManager(Plugin):
|
|||||||
|
|
||||||
debug.printMessage(debug.LEVEL_INFO, f"PluginManager: Updated active plugins: {active_plugins}", True)
|
debug.printMessage(debug.LEVEL_INFO, f"PluginManager: Updated active plugins: {active_plugins}", True)
|
||||||
|
|
||||||
|
try:
|
||||||
|
from cthulhu import plugin_system_manager
|
||||||
|
manager = plugin_system_manager.getManager()
|
||||||
|
if manager:
|
||||||
|
manager.setActivePlugins(active_plugins)
|
||||||
|
debug.printMessage(debug.LEVEL_INFO, "PluginManager: Applied active plugin changes", True)
|
||||||
|
except Exception as apply_error:
|
||||||
|
debug.printMessage(debug.LEVEL_INFO, f"PluginManager: Failed to apply plugin changes: {apply_error}", True)
|
||||||
|
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.error(f"PluginManager: Error toggling plugin {plugin_name}: {error}")
|
logger.error(f"PluginManager: Error toggling plugin {plugin_name}: {error}")
|
||||||
debug.printMessage(debug.LEVEL_INFO, f"PluginManager: Error toggling {plugin_name}: {error}", True)
|
debug.printMessage(debug.LEVEL_INFO, f"PluginManager: Error toggling {plugin_name}: {error}", True)
|
||||||
|
|||||||
@@ -30,4 +30,13 @@ pip install pluggy
|
|||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
The plugin will be automatically loaded when Cthulhu starts if it's listed in the activePlugins setting.
|
Enable the plugin in Cthulhu Preferences → Plugins to load it at startup. You can also add it to your
|
||||||
|
`activePlugins` preference.
|
||||||
|
|
||||||
|
## Preferences Pages (Optional)
|
||||||
|
|
||||||
|
Plugins can add their own Preferences tab by implementing:
|
||||||
|
- `getPreferencesGUI()` → returns a Gtk widget (or `(widget, label)`).
|
||||||
|
- `getPreferencesFromGUI()` → returns a dict of settings to save.
|
||||||
|
|
||||||
|
Tabs are only shown when the plugin is active.
|
||||||
|
|||||||
@@ -494,12 +494,12 @@ presentChatRoomLast = False
|
|||||||
presentLiveRegionFromInactiveTab = False
|
presentLiveRegionFromInactiveTab = False
|
||||||
|
|
||||||
# Plugins
|
# Plugins
|
||||||
activePlugins = ['AIAssistant', 'DisplayVersion', 'OCR', 'PluginManager', 'HelloCthulhu', 'ByeCthulhu', 'IndentationAudio', 'WindowTitleReader']
|
activePlugins = ['DisplayVersion', 'OCR', 'PluginManager', 'HelloCthulhu', 'ByeCthulhu', 'WindowTitleReader']
|
||||||
pluginSources = []
|
pluginSources = []
|
||||||
|
|
||||||
# AI Assistant settings (disabled by default for opt-in behavior)
|
# AI Assistant settings (disabled by default for opt-in behavior)
|
||||||
aiAssistantEnabled = True
|
aiAssistantEnabled = False
|
||||||
aiProvider = AI_PROVIDER_CLAUDE_CODE
|
aiProvider = AI_PROVIDER_OLLAMA
|
||||||
aiApiKeyFile = ""
|
aiApiKeyFile = ""
|
||||||
aiOllamaModel = "llama3.2-vision"
|
aiOllamaModel = "llama3.2-vision"
|
||||||
aiOllamaEndpoint = "http://localhost:11434"
|
aiOllamaEndpoint = "http://localhost:11434"
|
||||||
|
|||||||
Reference in New Issue
Block a user