Code cleanup withe left overs from old plugin manager removed.

This commit is contained in:
Storm Dragon
2026-07-03 02:46:26 -04:00
parent 85d30d331a
commit b08f7095cb
14 changed files with 200 additions and 552 deletions
+8 -5
View File
@@ -619,7 +619,7 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
for plugin_info in sorted(plugin_infos, key=lambda item: (item.get_name() or item.get_module_name()).lower()):
plugin_name = plugin_info.get_module_name()
canonical_name = plugin_info.get_canonical_name()
if plugin_info.hidden or canonical_name == "PluginManager":
if plugin_info.hidden:
continue
self._plugin_canonical_map[plugin_name] = canonical_name
@@ -3911,6 +3911,11 @@ print(json.dumps(result))
combobox.set_model(self.hardwareDeviceModel)
self._setHardwareDeviceChoice(saved_device)
@staticmethod
def _isHardwareSpeechFactory(factory):
moduleName = getattr(factory, "__name__", "")
return moduleName.split(".")[-1] == "hardwarefactory"
def _setHardwareDeviceChoice(self, device_name):
"""Set the active item in the hardware device combo box.
@@ -3941,9 +3946,7 @@ print(json.dumps(result))
is_hardware = False
if self.speechSystemsChoice:
try:
is_hardware = (
self.speechSystemsChoice.__name__ == "hardwarefactory"
)
is_hardware = self._isHardwareSpeechFactory(self.speechSystemsChoice)
except Exception:
pass
@@ -5052,7 +5055,7 @@ print(json.dumps(result))
# Save hardware speech device setting when hardware factory is active
if self.speechSystemsChoice and \
self.speechSystemsChoice.__name__ == "hardwarefactory":
self._isHardwareSpeechFactory(self.speechSystemsChoice):
if self.hardwareDeviceChoice is not None:
self.prefsDict["hardwareSpeechDevice"] = self.hardwareDeviceChoice
else:
+7
View File
@@ -36,6 +36,10 @@ LEGACY_PLUGIN_NAME_ALIASES: Dict[str, str] = {
"ocrdesktop": "OCR",
}
OBSOLETE_PLUGIN_NAMES = {
"pluginmanager",
}
LEGACY_PLUGIN_DIR_ALIASES: Dict[str, str] = {
"OCRDesktop": "OCR",
}
@@ -721,6 +725,9 @@ class PluginSystemManager:
if not name:
continue
name_lower = name.lower()
if name_lower in OBSOLETE_PLUGIN_NAMES:
logger.info(f"Dropping obsolete plugin name {name}")
continue
alias = LEGACY_PLUGIN_NAME_ALIASES.get(name_lower)
if alias:
logger.info(f"Mapping legacy plugin name {name} to {alias}")
@@ -1,14 +0,0 @@
#!/usr/bin/env python3
#
# Copyright (c) 2025 Stormux
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
"""PluginManager plugin package."""
from .plugin import PluginManager
__all__ = ['PluginManager']
@@ -1,14 +0,0 @@
pluginmanager_python_sources = files([
'__init__.py',
'plugin.py'
])
python3.install_sources(
pluginmanager_python_sources,
subdir: 'cthulhu/plugins/PluginManager'
)
install_data(
'plugin.info',
install_dir: python3.get_install_dir() / 'cthulhu' / 'plugins' / 'PluginManager'
)
@@ -1,8 +0,0 @@
name = PluginManager
version = 1.0.0
description = GUI interface for managing Cthulhu plugins - enable/disable plugins with checkboxes
authors = Stormux <storm_dragon@stormux.org>
website = https://git.stormux.org/storm/cthulhu
copyright = Copyright 2025
builtin = false
hidden = false
-505
View File
@@ -1,505 +0,0 @@
#!/usr/bin/env python3
#
# Copyright (c) 2025 Stormux
#
# This library is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
"""PluginManager plugin for Cthulhu - GUI interface for managing plugins."""
import logging
import os
import configparser
from pathlib import Path
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, Pango
from cthulhu.plugin import Plugin, cthulhu_hookimpl
from cthulhu import cthulhu
from cthulhu import debug
logger = logging.getLogger(__name__)
class PluginManager(Plugin):
"""Plugin that provides a GUI interface for managing other plugins."""
PLUGIN_COL_ENABLED = 0
PLUGIN_COL_DISPLAY = 1
PLUGIN_COL_CAN_TOGGLE = 2
PLUGIN_COL_NAME = 3
def __init__(self, *args, **kwargs):
"""Initialize the PluginManager plugin."""
super().__init__(*args, **kwargs)
self._kb_binding = None
self._dialog = None
self._plugin_treeview = None
self._plugin_model = None
self._activated = False
debug.printMessage(debug.LEVEL_INFO, "PluginManager: Plugin initialized", True)
@cthulhu_hookimpl
def activate(self, plugin=None):
"""Activate the PluginManager plugin."""
if plugin is not None and plugin is not self:
return
# Prevent duplicate activation
if self._activated:
debug.printMessage(debug.LEVEL_INFO, "PluginManager: Already activated, skipping", True)
return
try:
debug.printMessage(debug.LEVEL_INFO, "PluginManager: Plugin activation starting", True)
self._activated = True
debug.printMessage(debug.LEVEL_INFO, "PluginManager: Plugin activated successfully", True)
return True
except Exception as e:
debug.printMessage(debug.LEVEL_INFO, f"PluginManager: ERROR activating plugin: {e}", True)
return False
@cthulhu_hookimpl
def deactivate(self, plugin=None):
"""Deactivate the PluginManager plugin."""
if plugin is not None and plugin is not self:
return
try:
debug.printMessage(debug.LEVEL_INFO, "PluginManager: Plugin deactivation starting", True)
# Close dialog if open
if self._dialog:
self._dialog.destroy()
self._dialog = None
self._activated = False
debug.printMessage(debug.LEVEL_INFO, "PluginManager: Plugin deactivated successfully", True)
return True
except Exception as e:
debug.printMessage(debug.LEVEL_INFO, f"PluginManager: ERROR deactivating plugin: {e}", True)
return False
def _register_keybinding(self):
"""Register the Cthulhu+Shift+P keybinding for opening plugin manager."""
try:
if not self.app:
debug.printMessage(debug.LEVEL_INFO, "PluginManager: ERROR - No app reference available for keybinding", True)
return
# Register Cthulhu+Shift+P keybinding
gesture_string = "kb:cthulhu+shift+p"
description = "Open plugin manager"
self._kb_binding = self.registerGestureByString(
self._open_plugin_manager,
description,
gesture_string
)
if self._kb_binding:
debug.printMessage(debug.LEVEL_INFO, f"PluginManager: Registered keybinding {gesture_string}", True)
else:
debug.printMessage(debug.LEVEL_INFO, f"PluginManager: ERROR - Failed to register keybinding {gesture_string}", True)
except Exception as e:
debug.printMessage(debug.LEVEL_INFO, f"PluginManager: ERROR registering keybinding: {e}", True)
def _open_plugin_manager(self, script, inputEvent=None):
"""Open the plugin manager dialog."""
try:
debug.printMessage(debug.LEVEL_INFO, "PluginManager: Opening plugin manager dialog", True)
# Close existing dialog if open
if self._dialog:
self._dialog.destroy()
# Create new dialog
self._create_dialog()
return True
except Exception as e:
logger.error(f"PluginManager: Error opening plugin manager: {e}")
debug.printMessage(debug.LEVEL_INFO, f"PluginManager: Error opening dialog: {e}", True)
return False
def _create_dialog(self):
"""Create and show the plugin manager dialog."""
try:
# Create dialog window
self._dialog = Gtk.Dialog(
title="Cthulhu Plugin Manager",
parent=None,
flags=Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT
)
# Set dialog properties
self._dialog.set_default_size(400, 300)
self._dialog.set_border_width(10)
# Add buttons
self._dialog.add_button("Close", Gtk.ResponseType.CLOSE)
# Create main content area
content_area = self._dialog.get_content_area()
# Add title label
title_label = Gtk.Label()
title_label.set_markup("<b>Available Plugins</b>")
title_label.set_halign(Gtk.Align.CENTER)
content_area.pack_start(title_label, False, False, 10)
# Add info label about PluginManager
info_label = Gtk.Label()
info_label.set_markup("<i>Note:To apply changes, restart Cthulhu</i>")
info_label.set_halign(Gtk.Align.CENTER)
content_area.pack_start(info_label, False, False, 5)
# Create scrolled window for plugin list
scrolled = Gtk.ScrolledWindow()
scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
scrolled.set_size_request(-1, 200)
self._plugin_model = Gtk.TreeStore(bool, str, bool, str)
self._plugin_treeview = Gtk.TreeView(model=self._plugin_model)
self._plugin_treeview.set_headers_visible(True)
self._plugin_treeview.set_enable_search(False)
self._plugin_treeview.connect("key-press-event", self._on_plugin_list_key_press)
self._plugin_treeview.connect("row-activated", self._on_plugin_row_activated)
toggle_renderer = Gtk.CellRendererToggle()
toggle_renderer.set_activatable(True)
toggle_renderer.connect("toggled", self._on_plugin_toggled)
toggle_column = Gtk.TreeViewColumn(
"Enabled",
toggle_renderer,
active=self.PLUGIN_COL_ENABLED,
activatable=self.PLUGIN_COL_CAN_TOGGLE
)
toggle_column.add_attribute(toggle_renderer, "visible", self.PLUGIN_COL_CAN_TOGGLE)
self._plugin_treeview.append_column(toggle_column)
text_renderer = Gtk.CellRendererText()
text_renderer.set_property("ellipsize", Pango.EllipsizeMode.END)
text_column = Gtk.TreeViewColumn(
"Plugin",
text_renderer,
text=self.PLUGIN_COL_DISPLAY
)
text_column.set_expand(True)
self._plugin_treeview.append_column(text_column)
scrolled.add(self._plugin_treeview)
content_area.pack_start(scrolled, True, True, 0)
# Populate plugin list
self._populate_plugin_list()
# Connect signals
self._dialog.connect("response", self._on_dialog_response)
# Show dialog
self._dialog.show_all()
debug.printMessage(debug.LEVEL_INFO, "PluginManager: Dialog created and shown", True)
except Exception as e:
logger.error(f"PluginManager: Error creating dialog: {e}")
debug.printMessage(debug.LEVEL_INFO, f"PluginManager: Error creating dialog: {e}", True)
def _populate_plugin_list(self):
"""Populate the plugin list with available plugins."""
try:
# Get available plugins
available_plugins = self._discover_plugins()
# Get currently active plugins
active_plugins = cthulhu.cthulhuApp.settingsManager.getSetting('activePlugins') or []
debug.printMessage(debug.LEVEL_INFO, f"PluginManager: Found {len(available_plugins)} plugins", True)
# Clear existing model rows
self._plugin_model.clear()
# Add each plugin as a checkbox (except PluginManager itself)
enabled_iter = self._plugin_model.append(
None,
[False, "Enabled plugins", False, ""]
)
disabled_iter = self._plugin_model.append(
None,
[False, "Disabled plugins", False, ""]
)
for plugin_name, plugin_info in sorted(available_plugins.items()):
# Skip PluginManager to prevent users from disabling plugin management
if plugin_name == "PluginManager":
continue
display_name = plugin_info.get('name', plugin_name)
display_text = display_name
description = plugin_info.get('description')
if description:
display_text += f" - {description}"
version = plugin_info.get('version')
if version:
display_text += f" (v{version})"
is_active = plugin_name in active_plugins
parent_iter = enabled_iter if is_active else disabled_iter
self._plugin_model.append(
parent_iter,
[is_active, display_text, True, plugin_name]
)
self._plugin_treeview.collapse_all()
except Exception as e:
logger.error(f"PluginManager: Error populating plugin list: {e}")
debug.printMessage(debug.LEVEL_INFO, f"PluginManager: Error populating list: {e}", True)
def _discover_plugins(self):
"""Discover all available plugins."""
plugins = {}
try:
# Get plugin system manager
from cthulhu import plugin_system_manager
# Use existing plugin manager to get plugins
manager = plugin_system_manager.getManager()
if manager:
manager.rescanPlugins()
for plugin_info in manager.plugins:
plugin_name = plugin_info.get_module_name()
plugins[plugin_name] = {
'name': plugin_info.get_name(),
'description': plugin_info.get_description(),
'version': plugin_info.get_version(),
'path': plugin_info.get_module_dir()
}
else:
# Fallback: manually scan plugin directories
plugins = self._manual_plugin_discovery()
except Exception as e:
logger.error(f"PluginManager: Error in plugin discovery: {e}")
debug.printMessage(debug.LEVEL_INFO, f"PluginManager: Plugin discovery error: {e}", True)
# Fallback to manual discovery
plugins = self._manual_plugin_discovery()
return plugins
def _manual_plugin_discovery(self):
"""Manually discover plugins by scanning directories."""
plugins = {}
try:
# Get plugin directories
from cthulhu.plugin_system_manager import PluginType
system_dir = PluginType.SYSTEM.get_root_dir()
user_dir = PluginType.USER.get_root_dir()
for plugin_dir in [system_dir, user_dir]:
if os.path.exists(plugin_dir):
self._scan_plugin_directory(plugin_dir, plugins)
except Exception as e:
logger.error(f"PluginManager: Error in manual discovery: {e}")
debug.printMessage(debug.LEVEL_INFO, f"PluginManager: Manual discovery error: {e}", True)
return plugins
def _scan_plugin_directory(self, directory, plugins):
"""Scan a directory for plugins."""
try:
for item in os.listdir(directory):
plugin_path = os.path.join(directory, item)
if os.path.isdir(plugin_path):
# Check for plugin.py and plugin.info
plugin_py = os.path.join(plugin_path, "plugin.py")
plugin_info_file = os.path.join(plugin_path, "plugin.info")
if os.path.exists(plugin_py):
plugin_info = {'name': item, 'description': '', 'version': ''}
# Try to read plugin.info
if os.path.exists(plugin_info_file):
try:
config = configparser.ConfigParser()
config.read(plugin_info_file)
# Handle both INI-style and simple key=value format
if config.sections():
# INI-style format
for section in config.sections():
for key, value in config[section].items():
if key.lower() in ['description', 'version']:
plugin_info[key.lower()] = value
else:
# Simple key=value format
with open(plugin_info_file, 'r') as f:
for line in f:
line = line.strip()
if '=' in line and not line.startswith('#'):
key, value = line.split('=', 1)
key = key.strip().lower()
value = value.strip()
if key in ['description', 'version']:
plugin_info[key] = value
except Exception as info_e:
debug.printMessage(debug.LEVEL_INFO, f"PluginManager: Error reading {plugin_info_file}: {info_e}", True)
plugins[item] = plugin_info
except Exception as e:
logger.error(f"PluginManager: Error scanning directory {directory}: {e}")
def _on_plugin_toggled(self, renderer, path):
"""Handle plugin toggle via TreeView."""
try:
tree_iter = self._plugin_model.get_iter(path)
if not tree_iter:
return
can_toggle = self._plugin_model.get_value(tree_iter, self.PLUGIN_COL_CAN_TOGGLE)
if not can_toggle:
return
is_active = self._plugin_model.get_value(tree_iter, self.PLUGIN_COL_ENABLED)
plugin_name = self._plugin_model.get_value(tree_iter, self.PLUGIN_COL_NAME)
new_active = not is_active
self._plugin_model.set_value(tree_iter, self.PLUGIN_COL_ENABLED, new_active)
self._set_plugin_active(plugin_name, new_active)
self._rebuild_plugin_groups()
except Exception as error:
logger.error(f"PluginManager: Error toggling plugin at path {path}: {error}")
debug.printMessage(debug.LEVEL_INFO, f"PluginManager: Error toggling plugin at path {path}: {error}", True)
def _on_plugin_list_key_press(self, widget, event):
"""Toggle plugin on Space while keeping Tab navigation outside the list."""
if event.keyval != Gdk.KEY_space:
return False
selection = self._plugin_treeview.get_selection()
model, tree_iter = selection.get_selected()
if not tree_iter:
return False
path = model.get_path(tree_iter)
self._on_plugin_toggled(None, path)
return True
def _on_plugin_row_activated(self, treeview, path, column):
"""Toggle plugin when activating a row."""
self._on_plugin_toggled(None, path)
def _rebuild_plugin_groups(self):
"""Rebuild the tree so enabled/disabled groups stay accurate."""
if not self._plugin_model:
return
selection = self._plugin_treeview.get_selection()
model, tree_iter = selection.get_selected()
selected_name = None
if tree_iter:
selected_name = model.get_value(tree_iter, self.PLUGIN_COL_NAME)
self._populate_plugin_list()
if selected_name:
self._select_plugin_row(selected_name)
def _select_plugin_row(self, plugin_name):
"""Select the row for the given plugin name."""
def _walk(model, tree_iter):
while tree_iter:
name = model.get_value(tree_iter, self.PLUGIN_COL_NAME)
if name == plugin_name:
path = model.get_path(tree_iter)
self._plugin_treeview.expand_to_path(path)
self._plugin_treeview.get_selection().select_path(path)
return True
if model.iter_has_child(tree_iter):
child = model.iter_children(tree_iter)
if _walk(model, child):
return True
tree_iter = model.iter_next(tree_iter)
return False
root = self._plugin_model.get_iter_first()
_walk(self._plugin_model, root)
def _set_plugin_active(self, plugin_name, is_active):
"""Update settings to enable or disable a plugin."""
try:
debug.printMessage(debug.LEVEL_INFO, f"PluginManager: Plugin {plugin_name} toggled to {'active' if is_active else 'inactive'}", True)
# Get current active plugins
active_plugins = cthulhu.cthulhuApp.settingsManager.getSetting('activePlugins') or []
active_plugins = list(active_plugins)
if is_active and plugin_name not in active_plugins:
active_plugins.append(plugin_name)
elif not is_active and plugin_name in active_plugins:
active_plugins.remove(plugin_name)
cthulhu.cthulhuApp.settingsManager.setSetting('activePlugins', active_plugins)
if hasattr(cthulhu.cthulhuApp.settingsManager, "general") and isinstance(cthulhu.cthulhuApp.settingsManager.general, dict):
cthulhu.cthulhuApp.settingsManager.general['activePlugins'] = active_plugins
try:
active_profile = cthulhu.cthulhuApp.settingsManager.getSetting('activeProfile')
if isinstance(active_profile, (list, tuple)) and len(active_profile) > 1:
profile_name = active_profile[1]
else:
profile_name = cthulhu.cthulhuApp.settingsManager.profile or 'default'
current_general = cthulhu.cthulhuApp.settingsManager.getGeneralSettings(profile_name) or {}
current_general['activePlugins'] = active_plugins
cthulhu.cthulhuApp.settingsManager.profile = profile_name
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 plugin state: {save_error}", 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:
logger.error(f"PluginManager: Error toggling plugin {plugin_name}: {error}")
debug.printMessage(debug.LEVEL_INFO, f"PluginManager: Error toggling {plugin_name}: {error}", True)
def _on_dialog_response(self, dialog, response_id):
"""Handle dialog response (close button clicked)."""
try:
if response_id == Gtk.ResponseType.CLOSE:
dialog.destroy()
self._dialog = None
debug.printMessage(debug.LEVEL_INFO, "PluginManager: Dialog closed", True)
except Exception as e:
logger.error(f"PluginManager: Error handling dialog response: {e}")
-1
View File
@@ -8,7 +8,6 @@ subdir('HelloCthulhu')
subdir('GameMode')
subdir('nvda2cthulhu')
subdir('OCR')
subdir('PluginManager')
subdir('SpeechHistory')
subdir('SimplePluginSystem')
subdir('hello_world')
@@ -164,6 +164,16 @@ class Script(Chromium.Script):
super().onTextInserted(event)
def onCaretMoved(self, event):
"""Callback for object:text-caret-moved accessibility events."""
if self._isSteamLoadingSpinnerCaretEvent(event):
msg = "STEAM: Ignoring caret event from transient loading spinner"
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True
return super().onCaretMoved(event)
def onSelectionChanged(self, event):
"""Callback for object:selection-changed accessibility events."""
@@ -193,6 +203,34 @@ class Script(Chromium.Script):
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True
def _isSteamLoadingSpinnerCaretEvent(self, event) -> bool:
if not (event and event.type.startswith("object:text-caret-moved")):
return False
source = getattr(event, "source", None)
if not source or not self.utilities.inDocumentContent(source):
return False
if AXObject.get_name(source):
return False
try:
children = list(AXObject.iter_children(source))
except Exception:
return False
if not children:
return False
for child in children:
if not AXUtilities.is_image_or_canvas(child):
continue
name = AXObject.get_name(child) or ""
if name.strip().casefold() == "steam spinner":
return True
return False
def _trySteamButtonActivation(self, keyboardEvent) -> bool:
if keyboardEvent.event_string not in ["Return", "KP_Enter"]:
return False
+16 -1
View File
@@ -1758,7 +1758,8 @@ class Script(default.Script):
if not document:
msg = "WEB: Locus of focus changed to non-document obj"
self._madeFindAnnouncement = False
self._inFocusMode = False
if not self._focusModeIsSticky:
self._inFocusMode = False
self._setNavigationSuspended(True, "focus left document content")
debug.printMessage(debug.LEVEL_INFO, msg, True)
oldDocument = self.utilities.getTopLevelDocumentForObject(oldFocus)
@@ -1779,6 +1780,19 @@ class Script(default.Script):
if self._navSuspended:
self._setNavigationSuspended(False, "focus entered document content")
if self._focusModeIsSticky and not self._inFocusMode:
msg = "WEB: Restoring sticky focus mode after returning to document content"
debug.printMessage(debug.LEVEL_INFO, msg, True)
self._inFocusMode = True
self.presentMessage(messages.MODE_FOCUS_IS_STICKY)
self.refreshKeyGrabs()
elif self._browseModeIsSticky and self._inFocusMode:
msg = "WEB: Restoring sticky browse mode after returning to document content"
debug.printMessage(debug.LEVEL_INFO, msg, True)
self._inFocusMode = False
self.presentMessage(messages.MODE_BROWSE_IS_STICKY)
self.refreshKeyGrabs()
if self.flatReviewPresenter.is_active():
self.flatReviewPresenter.quit()
@@ -2551,6 +2565,7 @@ class Script(default.Script):
if self._browseModeIsSticky:
msg = "WEB: Web app descendant claimed focus, but browse mode is sticky"
debug.printMessage(debug.LEVEL_INFO, msg, True)
return True
elif AXUtilities.is_tool_tip(event.source) \
and AXObject.find_ancestor(cthulhu_state.locusOfFocus, lambda x: x == event.source):
msg = "WEB: Event believed to be side effect of tooltip navigation."
-1
View File
@@ -497,7 +497,6 @@ presentLiveRegionFromInactiveTab = False
# Plugins
activePlugins = [
'PluginManager',
'ByeCthulhu',
'Clipboard',
'HelloCthulhu',