Start migration to pluggy for plugins.

This commit is contained in:
Storm Dragon 2025-03-25 19:41:57 -04:00
parent a8e16fcf01
commit d3d268004b
4 changed files with 1298 additions and 244 deletions

View File

@ -74,7 +74,8 @@ from .ax_object import AXObject
from .ax_utilities import AXUtilities from .ax_utilities import AXUtilities
from .input_event import BrailleEvent from .input_event import BrailleEvent
from . import cmdnames from . import cmdnames
from . import plugin_system_manager from . import plugin_system_manager # This will now be your pluggy-based implementation
from .plugin_api_helper import APIHelper # New import for the separated APIHelper
from . import guilabels from . import guilabels
from . import acss from . import acss
from . import text_attribute_names from . import text_attribute_names
@ -920,7 +921,7 @@ class Cthulhu(GObject.Object):
GObject.Object.__init__(self) GObject.Object.__init__(self)
# add members # add members
self.resourceManager = resource_manager.ResourceManager(self) self.resourceManager = resource_manager.ResourceManager(self)
self.APIHelper = plugin_system_manager.APIHelper(self) self.APIHelper = APIHelper(self)
self.eventManager = _eventManager self.eventManager = _eventManager
self.settingsManager = _settingsManager self.settingsManager = _settingsManager
self.scriptManager = _scriptManager self.scriptManager = _scriptManager

View File

@ -23,5 +23,5 @@
# Fork of Orca Screen Reader (GNOME) # Fork of Orca Screen Reader (GNOME)
# Original source: https://gitlab.gnome.org/GNOME/orca # Original source: https://gitlab.gnome.org/GNOME/orca
version = "2024.12.23" version = "2025.03.25"
codeName = "testing" codeName = "testing"

View File

@ -23,15 +23,32 @@
# Fork of Orca Screen Reader (GNOME) # Fork of Orca Screen Reader (GNOME)
# Original source: https://gitlab.gnome.org/GNOME/orca # Original source: https://gitlab.gnome.org/GNOME/orca
import os, inspect """Base class for Cthulhu plugins using pluggy."""
import gi
from gi.repository import GObject
class Plugin(): import os
#__gtype_name__ = 'BasePlugin' import inspect
import logging
# Import pluggy for hook specifications
try:
import pluggy
cthulhu_hookimpl = pluggy.HookimplMarker("cthulhu")
except ImportError:
# Fallback if pluggy is not available
def cthulhu_hookimpl(func=None, **kwargs):
"""Dummy decorator for systems without pluggy."""
if func is None:
return lambda f: f
return func
logger = logging.getLogger(__name__)
class Plugin:
"""Base class for Cthulhu plugins."""
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.API = None """Initialize the plugin with default attributes."""
self.app = None
self.pluginInfo = None self.pluginInfo = None
self.moduleDir = '' self.moduleDir = ''
self.hidden = False self.hidden = False
@ -48,19 +65,46 @@ class Plugin():
self.helpUri = '' self.helpUri = ''
self.dataDir = '' self.dataDir = ''
self.translationContext = None self.translationContext = None
self.dynamicApiManager = None
self.signalManager = None
def setApp(self, app): def setApp(self, app):
"""Set the application reference.
Args:
app: The Cthulhu application instance.
"""
self.app = app self.app = app
self.dynamicApiManager = app.getDynamicApiManager() self.dynamicApiManager = app.getDynamicApiManager()
self.signalManager = app.getSignalManager() self.signalManager = app.getSignalManager()
def getApp(self): def getApp(self):
"""Get the application reference.
Returns:
The Cthulhu application instance.
"""
return self.app return self.app
def setPluginInfo(self, pluginInfo): def setPluginInfo(self, pluginInfo):
"""Set the plugin information.
Args:
pluginInfo: The plugin information object.
"""
self.pluginInfo = pluginInfo self.pluginInfo = pluginInfo
self.updatePluginInfoAttributes() self.updatePluginInfoAttributes()
def getPluginInfo(self): def getPluginInfo(self):
"""Get the plugin information.
Returns:
The plugin information object.
"""
return self.pluginInfo return self.pluginInfo
def updatePluginInfoAttributes(self): def updatePluginInfoAttributes(self):
"""Update plugin attributes from the plugin information."""
self.moduleDir = '' self.moduleDir = ''
self.hidden = False self.hidden = False
self.moduleName = '' self.moduleName = ''
@ -75,44 +119,82 @@ class Plugin():
self.dependencies = False self.dependencies = False
self.helpUri = '' self.helpUri = ''
self.dataDir = '' self.dataDir = ''
pluginInfo = self.getPluginInfo() pluginInfo = self.getPluginInfo()
if pluginInfo == None: if pluginInfo is None:
return return
self.moduleName = self.getApp().getPluginSystemManager().getPluginModuleName(pluginInfo)
self.name = self.getApp().getPluginSystemManager().getPluginName(pluginInfo) if hasattr(self.app, 'getPluginSystemManager'):
self.version = self.getApp().getPluginSystemManager().getPluginVersion(pluginInfo) plugin_system = self.app.getPluginSystemManager()
self.moduleDir = self.getApp().getPluginSystemManager().getPluginModuleDir(pluginInfo)
self.buildIn = self.getApp().getPluginSystemManager().isPluginBuildIn(pluginInfo) self.moduleName = plugin_system.getPluginModuleName(pluginInfo)
self.description = self.getApp().getPluginSystemManager().getPluginDescription(pluginInfo) self.name = plugin_system.getPluginName(pluginInfo)
self.hidden = self.getApp().getPluginSystemManager().isPluginHidden(pluginInfo) self.version = plugin_system.getPluginVersion(pluginInfo)
self.website = self.getApp().getPluginSystemManager().getPluginWebsite(pluginInfo) self.moduleDir = plugin_system.getPluginModuleDir(pluginInfo)
self.authors = self.getApp().getPluginSystemManager().getPluginAuthors(pluginInfo) self.buildIn = plugin_system.isPluginBuildIn(pluginInfo)
self.iconName = self.getApp().getPluginSystemManager().getPluginIconName(pluginInfo) self.description = plugin_system.getPluginDescription(pluginInfo)
self.copyright = self.getApp().getPluginSystemManager().getPluginCopyright(pluginInfo) self.hidden = plugin_system.isPluginHidden(pluginInfo)
self.dependencies = self.getApp().getPluginSystemManager().getPluginDependencies(pluginInfo) self.website = plugin_system.getPluginWebsite(pluginInfo)
self.authors = plugin_system.getPluginAuthors(pluginInfo)
#settings = self.getApp().getPluginSystemManager().getPluginSettings(pluginInfo) self.iconName = plugin_system.getPluginIconName(pluginInfo)
#hasDependencies = self.getApp().getPluginSystemManager().hasPluginDependency(pluginInfo) self.copyright = plugin_system.getPluginCopyright(pluginInfo)
self.dependencies = plugin_system.getPluginDependencies(pluginInfo)
#externalData = self.getApp().getPluginSystemManager().getPluginExternalData(pluginInfo) self.helpUri = plugin_system.getPlugingetHelpUri(pluginInfo)
self.helpUri = self.getApp().getPluginSystemManager().getPlugingetHelpUri(pluginInfo) self.dataDir = plugin_system.getPluginDataDir(pluginInfo)
self.dataDir = self.getApp().getPluginSystemManager().getPluginDataDir(pluginInfo) else:
# Direct attribute access for pluggy-based plugins
self.moduleName = getattr(pluginInfo, 'module_name', '')
self.name = getattr(pluginInfo, 'name', '')
self.version = getattr(pluginInfo, 'version', '')
self.moduleDir = getattr(pluginInfo, 'module_dir', '')
self.buildIn = getattr(pluginInfo, 'builtin', False)
self.description = getattr(pluginInfo, 'description', '')
self.hidden = getattr(pluginInfo, 'hidden', False)
self.website = getattr(pluginInfo, 'website', '')
self.authors = getattr(pluginInfo, 'authors', [])
self.iconName = getattr(pluginInfo, 'icon_name', '')
self.copyright = getattr(pluginInfo, 'copyright', '')
self.dependencies = getattr(pluginInfo, 'dependencies', [])
self.helpUri = getattr(pluginInfo, 'help_uri', '')
self.dataDir = getattr(pluginInfo, 'data_dir', '')
self.updateTranslationContext() self.updateTranslationContext()
def updateTranslationContext(self, domain = None, localeDir = None, language = None, fallbackToCthulhuTranslation = True): def updateTranslationContext(self, domain=None, localeDir=None, language=None, fallbackToCthulhuTranslation=True):
"""Update the translation context.
Args:
domain: The translation domain.
localeDir: The locale directory.
language: The language to use.
fallbackToCthulhuTranslation: Whether to fall back to Cthulhu translations.
"""
self.translationContext = None self.translationContext = None
useLocaleDir = '{}/locale/'.format(self.getModuleDir()) useLocaleDir = f'{self.getModuleDir()}/locale/'
if localeDir: if localeDir:
if os.path.isdir(localeDir): if os.path.isdir(localeDir):
useLocaleDir = localeDir useLocaleDir = localeDir
useName = self.getModuleName() useName = self.getModuleName()
useDomain = useName useDomain = useName
if domain: if domain:
useDomain = domain useDomain = domain
useLanguage = None useLanguage = None
if language: if language:
useLanguage = language useLanguage = language
self.translationContext = self.getApp().getTranslationManager().initTranslation(useName, domain=useDomain, localeDir=useLocaleDir, language=useLanguage, fallbackToCthulhuTranslation=fallbackToCthulhuTranslation)
translation_manager = self.getApp().getTranslationManager()
self.translationContext = translation_manager.initTranslation(
useName,
domain=useDomain,
localeDir=useLocaleDir,
language=useLanguage,
fallbackToCthulhuTranslation=fallbackToCthulhuTranslation
)
# Point _ to the translation object in the globals namespace of the caller frame # Point _ to the translation object in the globals namespace of the caller frame
try: try:
callerFrame = inspect.currentframe().f_back callerFrame = inspect.currentframe().f_back
@ -120,66 +202,306 @@ class Plugin():
callerFrame.f_globals['_'] = self.translationContext.gettext callerFrame.f_globals['_'] = self.translationContext.gettext
callerFrame.f_globals['ngettext'] = self.translationContext.ngettext callerFrame.f_globals['ngettext'] = self.translationContext.ngettext
finally: finally:
del callerFrame # Avoid reference problems with frames (per python docs) del callerFrame # Avoid reference problems with frames (per python docs)
def getTranslationContext(self): def getTranslationContext(self):
"""Get the translation context.
Returns:
The translation context.
"""
return self.translationContext return self.translationContext
def isPluginBuildIn(self): def isPluginBuildIn(self):
"""Check if the plugin is built-in.
Returns:
True if the plugin is built-in, False otherwise.
"""
return self.buildIn return self.buildIn
def isPluginHidden(self): def isPluginHidden(self):
"""Check if the plugin is hidden.
Returns:
True if the plugin is hidden, False otherwise.
"""
return self.hidden return self.hidden
def getAuthors(self): def getAuthors(self):
"""Get the plugin authors.
Returns:
A list of plugin authors.
"""
return self.authors return self.authors
def getCopyright(self): def getCopyright(self):
"""Get the plugin copyright.
Returns:
The plugin copyright.
"""
return self.copyright return self.copyright
def getDataDir(self): def getDataDir(self):
"""Get the plugin data directory.
Returns:
The plugin data directory.
"""
return self.dataDir return self.dataDir
def getDependencies(self): def getDependencies(self):
"""Get the plugin dependencies.
Returns:
The plugin dependencies.
"""
return self.dependencies return self.dependencies
def getDescription(self): def getDescription(self):
"""Get the plugin description.
Returns:
The plugin description.
"""
return self.description return self.description
def getgetHelpUri(self): def getgetHelpUri(self):
"""Get the plugin help URI.
Returns:
The plugin help URI.
"""
return self.helpUri return self.helpUri
def getIconName(self): def getIconName(self):
"""Get the plugin icon name.
Returns:
The plugin icon name.
"""
return self.iconName return self.iconName
def getModuleDir(self): def getModuleDir(self):
"""Get the plugin module directory.
Returns:
The plugin module directory.
"""
return self.moduleDir return self.moduleDir
def getModuleName(self): def getModuleName(self):
"""Get the plugin module name.
Returns:
The plugin module name.
"""
return self.moduleName return self.moduleName
def getName(self): def getName(self):
"""Get the plugin name.
Returns:
The plugin name.
"""
return self.name return self.name
def getVersion(self): def getVersion(self):
"""Get the plugin version.
Returns:
The plugin version.
"""
return self.version return self.version
def getWebsite(self): def getWebsite(self):
"""Get the plugin website.
Returns:
The plugin website.
"""
return self.website return self.website
def getSetting(key):
#self.getModuleName()) def getSetting(self, key):
"""Get a plugin setting.
Args:
key: The setting key.
Returns:
The setting value, or None if not found.
"""
# To be implemented
return None return None
def setSetting(key, value):
#self.getModuleName()) def setSetting(self, key, value):
"""Set a plugin setting.
Args:
key: The setting key.
value: The setting value.
"""
# To be implemented
pass pass
def registerGestureByString(self, function, name, gestureString, learnModeEnabled = True):
keybinding = self.getApp().getAPIHelper().registerGestureByString(function, name, gestureString, 'default', 'cthulhu', learnModeEnabled, contextName = self.getModuleName()) def registerGestureByString(self, function, name, gestureString, learnModeEnabled=True):
"""Register a gesture by string.
Args:
function: The function to call when the gesture is triggered.
name: The name of the gesture.
gestureString: The gesture string.
learnModeEnabled: Whether the gesture is enabled in learn mode.
Returns:
The registered keybinding.
"""
keybinding = self.getApp().getAPIHelper().registerGestureByString(
function,
name,
gestureString,
'default',
'cthulhu',
learnModeEnabled,
contextName=self.getModuleName()
)
return keybinding return keybinding
def unregisterShortcut(self, function, name, gestureString, learnModeEnabled = True):
ok = self.getApp().getAPIHelper().unregisterShortcut(keybinding, contextName = self.getModuleName()) def unregisterShortcut(self, keybinding, learnModeEnabled=True):
"""Unregister a shortcut.
Args:
keybinding: The keybinding to unregister.
learnModeEnabled: Whether the shortcut is enabled in learn mode.
Returns:
True if the shortcut was unregistered, False otherwise.
"""
ok = self.getApp().getAPIHelper().unregisterShortcut(
keybinding,
contextName=self.getModuleName()
)
return ok return ok
def registerSignal(self, signalName, signalFlag = GObject.SignalFlags.RUN_LAST, closure = GObject.TYPE_NONE, accumulator=()):
ok = self.signalManager.registerSignal(signalName, signalFlag, closure, accumulator, contextName = self.getModuleName()) def registerSignal(self, signalName, signalFlag=None, closure=None, accumulator=()):
"""Register a signal.
Args:
signalName: The signal name.
signalFlag: The signal flags.
closure: The closure.
accumulator: The accumulator.
Returns:
True if the signal was registered, False otherwise.
"""
if signalFlag is None:
# Import GObject if available, otherwise use a dummy value
try:
from gi.repository import GObject
signalFlag = GObject.SignalFlags.RUN_LAST
except ImportError:
signalFlag = 1 # Default value
if closure is None:
try:
from gi.repository import GObject
closure = GObject.TYPE_NONE
except ImportError:
closure = None # Default value
ok = self.signalManager.registerSignal(
signalName,
signalFlag,
closure,
accumulator,
contextName=self.getModuleName()
)
return ok return ok
def unregisterSignal(self, signalName): def unregisterSignal(self, signalName):
# how to unregister? """Unregister a signal.
Args:
signalName: The signal name.
"""
# To be implemented
pass pass
def connectSignal(self, signalName, function, param = None): def connectSignal(self, signalName, function, param=None):
signalID = self.signalManager.connectSignal(signalName, function, param, contextName = self.getModuleName()) """Connect to a signal.
Args:
signalName: The signal name.
function: The function to call when the signal is triggered.
param: The parameter to pass to the function.
Returns:
The signal ID.
"""
signalID = self.signalManager.connectSignal(
signalName,
function,
param,
contextName=self.getModuleName()
)
return signalID return signalID
def disconnectSignalByFunction(self, function): def disconnectSignalByFunction(self, function):
# need get mapped function """Disconnect a signal by function.
Args:
function: The function to disconnect.
"""
# Need to get mapped function
mappedFunction = function mappedFunction = function
self.signalManager.disconnectSignalByFunction(mappedFunction, contextName = self.getModuleName()) self.signalManager.disconnectSignalByFunction(
mappedFunction,
def registerAPI(self, key, value, application = ''): contextName=self.getModuleName()
ok = self.dynamicApiManager.registerAPI(key, value, application = application, contextName = self.getModuleName()) )
def registerAPI(self, key, value, application=''):
"""Register an API.
Args:
key: The API key.
value: The API value.
application: The application.
Returns:
True if the API was registered, False otherwise.
"""
ok = self.dynamicApiManager.registerAPI(
key,
value,
application=application,
contextName=self.getModuleName()
)
return ok return ok
def unregisterAPI(self, key, application = ''):
self.dynamicApiManager.unregisterAPI(key, application = application, contextName = self.getModuleName()) def unregisterAPI(self, key, application=''):
"""Unregister an API.
Args:
key: The API key.
application: The application.
"""
self.dynamicApiManager.unregisterAPI(
key,
application=application,
contextName=self.getModuleName()
)
# Pluggy-specific hook implementations
@cthulhu_hookimpl
def activate(self):
"""Activate the plugin. This method should be overridden by subclasses."""
logger.info(f"Activating plugin: {self.getName()}")
@cthulhu_hookimpl
def deactivate(self):
"""Deactivate the plugin. This method should be overridden by subclasses."""
logger.info(f"Deactivating plugin: {self.getName()}")

File diff suppressed because it is too large Load Diff