Attempt to get pluggy working.
This commit is contained in:
parent
6bbe6e47fc
commit
084d4fe85f
configure.ac
src/cthulhu
@ -46,6 +46,7 @@ AM_PATH_PYTHON(3.3)
|
|||||||
|
|
||||||
AM_CHECK_PYMOD(gi,,,[AC_MSG_ERROR(Could not find python module: gi)])
|
AM_CHECK_PYMOD(gi,,,[AC_MSG_ERROR(Could not find python module: gi)])
|
||||||
AM_CHECK_PYMOD(json,,,[AC_MSG_ERROR(Could not find python module: json)])
|
AM_CHECK_PYMOD(json,,,[AC_MSG_ERROR(Could not find python module: json)])
|
||||||
|
AM_CHECK_PYMOD(pluggy,,[pluggy_available="yes"],[pluggy_available="no"])
|
||||||
AM_CHECK_PYMOD(brlapi,,[brlapi_available="yes"],[brlapi_available="no"])
|
AM_CHECK_PYMOD(brlapi,,[brlapi_available="yes"],[brlapi_available="no"])
|
||||||
AM_CHECK_PYMOD(speechd,,[speechd_available="yes"],[speechd_available="no"])
|
AM_CHECK_PYMOD(speechd,,[speechd_available="yes"],[speechd_available="no"])
|
||||||
AC_ARG_WITH([liblouis],
|
AC_ARG_WITH([liblouis],
|
||||||
@ -168,6 +169,7 @@ if test "$have_libpeas" = "no"; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
echo
|
echo
|
||||||
|
echo Use pluggy: $pluggy_available
|
||||||
echo Use speech-dispatcher: $speechd_available
|
echo Use speech-dispatcher: $speechd_available
|
||||||
echo Use brltty: $brlapi_available
|
echo Use brltty: $brlapi_available
|
||||||
echo Use liblouis: $louis_available
|
echo Use liblouis: $louis_available
|
||||||
|
@ -34,6 +34,94 @@ __copyright__ = "Copyright (c) 2004-2009 Sun Microsystems Inc." \
|
|||||||
__license__ = "LGPL"
|
__license__ = "LGPL"
|
||||||
|
|
||||||
import faulthandler
|
import faulthandler
|
||||||
|
|
||||||
|
class APIHelper:
|
||||||
|
"""Helper class for plugin API interactions, including keybindings."""
|
||||||
|
|
||||||
|
def __init__(self, app):
|
||||||
|
"""Initialize the APIHelper.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
- app: the Cthulhu application
|
||||||
|
"""
|
||||||
|
self.app = app
|
||||||
|
self._gestureBindings = {}
|
||||||
|
|
||||||
|
def registerGestureByString(self, function, name, gestureString,
|
||||||
|
inputEventType='default', normalizer='cthulhu',
|
||||||
|
learnModeEnabled=True, contextName=None):
|
||||||
|
"""Register a gesture by string.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
- function: the function to call when the gesture is performed
|
||||||
|
- name: a human-readable name for this gesture
|
||||||
|
- gestureString: string representation of the gesture (e.g., 'kb:cthulhu+z')
|
||||||
|
- inputEventType: the type of input event
|
||||||
|
- normalizer: the normalizer to use
|
||||||
|
- learnModeEnabled: whether this should be available in learn mode
|
||||||
|
- contextName: the context for this gesture (e.g., plugin name)
|
||||||
|
|
||||||
|
Returns the binding ID or None if registration failed
|
||||||
|
"""
|
||||||
|
if not gestureString.startswith("kb:"):
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Extract the key portion from the gesture string
|
||||||
|
key = gestureString.split(":", 1)[1]
|
||||||
|
|
||||||
|
# Handle Cthulhu modifier specially
|
||||||
|
if "cthulhu+" in key.lower():
|
||||||
|
from . import keybindings
|
||||||
|
key = key.lower().replace("cthulhu+", "")
|
||||||
|
|
||||||
|
# Create a keybinding handler
|
||||||
|
class GestureHandler:
|
||||||
|
def __init__(self, function, description):
|
||||||
|
self.function = function
|
||||||
|
self.description = description
|
||||||
|
|
||||||
|
def __call__(self, script, inputEvent):
|
||||||
|
return self.function(script, inputEvent)
|
||||||
|
|
||||||
|
handler = GestureHandler(function, name)
|
||||||
|
|
||||||
|
# Register the binding with the active script
|
||||||
|
from . import cthulhu_state
|
||||||
|
if cthulhu_state.activeScript:
|
||||||
|
bindings = cthulhu_state.activeScript.getKeyBindings()
|
||||||
|
binding = keybindings.KeyBinding(
|
||||||
|
key,
|
||||||
|
keybindings.defaultModifierMask,
|
||||||
|
keybindings.CTHULHU_MODIFIER_MASK,
|
||||||
|
handler)
|
||||||
|
bindings.add(binding)
|
||||||
|
|
||||||
|
# Store binding for later reference
|
||||||
|
if contextName not in self._gestureBindings:
|
||||||
|
self._gestureBindings[contextName] = []
|
||||||
|
self._gestureBindings[contextName].append(binding)
|
||||||
|
|
||||||
|
return binding
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
def unregisterShortcut(self, binding, contextName=None):
|
||||||
|
"""Unregister a previously registered shortcut.
|
||||||
|
|
||||||
|
Arguments:
|
||||||
|
- binding: the binding to unregister
|
||||||
|
- contextName: the context for this gesture
|
||||||
|
"""
|
||||||
|
# Remove from script's keybindings
|
||||||
|
from . import cthulhu_state
|
||||||
|
if cthulhu_state.activeScript:
|
||||||
|
bindings = cthulhu_state.activeScript.getKeyBindings()
|
||||||
|
bindings.remove(binding)
|
||||||
|
|
||||||
|
# Remove from our tracking
|
||||||
|
if contextName in self._gestureBindings:
|
||||||
|
if binding in self._gestureBindings[contextName]:
|
||||||
|
self._gestureBindings[contextName].remove(binding)
|
||||||
import gi
|
import gi
|
||||||
import importlib
|
import importlib
|
||||||
import os
|
import os
|
||||||
@ -927,6 +1015,7 @@ class Cthulhu(GObject.Object):
|
|||||||
self.dynamicApiManager = dynamic_api_manager.DynamicApiManager(self)
|
self.dynamicApiManager = dynamic_api_manager.DynamicApiManager(self)
|
||||||
self.translationManager = translation_manager.TranslationManager(self)
|
self.translationManager = translation_manager.TranslationManager(self)
|
||||||
self.debugManager = debug
|
self.debugManager = debug
|
||||||
|
self.APIHelper = APIHelper(self)
|
||||||
self.createCompatAPI()
|
self.createCompatAPI()
|
||||||
self.pluginSystemManager = plugin_system_manager.PluginSystemManager(self)
|
self.pluginSystemManager = plugin_system_manager.PluginSystemManager(self)
|
||||||
def getAPIHelper(self):
|
def getAPIHelper(self):
|
||||||
@ -986,6 +1075,7 @@ class Cthulhu(GObject.Object):
|
|||||||
# cthulhu lets say, special compat handling....
|
# cthulhu lets say, special compat handling....
|
||||||
self.getDynamicApiManager().registerAPI('EmitRegionChanged', emitRegionChanged)
|
self.getDynamicApiManager().registerAPI('EmitRegionChanged', emitRegionChanged)
|
||||||
self.getDynamicApiManager().registerAPI('LoadUserSettings', loadUserSettings)
|
self.getDynamicApiManager().registerAPI('LoadUserSettings', loadUserSettings)
|
||||||
|
self.getDynamicApiManager().registerAPI('APIHelper', self.APIHelper)
|
||||||
|
|
||||||
cthulhuApp = Cthulhu()
|
cthulhuApp = Cthulhu()
|
||||||
|
|
||||||
|
@ -193,7 +193,7 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
|||||||
# ***** Key Bindings treeview initialization *****
|
# ***** Key Bindings treeview initialization *****
|
||||||
|
|
||||||
self.keyBindView = self.get_widget("keyBindingsTreeview")
|
self.keyBindView = self.get_widget("keyBindingsTreeview")
|
||||||
|
|
||||||
if self.keyBindView.get_columns():
|
if self.keyBindView.get_columns():
|
||||||
for column in self.keyBindView.get_columns():
|
for column in self.keyBindView.get_columns():
|
||||||
self.keyBindView.remove_column(column)
|
self.keyBindView.remove_column(column)
|
||||||
@ -337,7 +337,7 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
|||||||
column.set_resizable(True)
|
column.set_resizable(True)
|
||||||
column.set_sort_column_id(EDITABLE)
|
column.set_sort_column_id(EDITABLE)
|
||||||
self.keyBindView.append_column(column)
|
self.keyBindView.append_column(column)
|
||||||
|
|
||||||
# Populates the treeview with all the keybindings:
|
# Populates the treeview with all the keybindings:
|
||||||
#
|
#
|
||||||
self._populateKeyBindings()
|
self._populateKeyBindings()
|
||||||
@ -582,7 +582,7 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
|||||||
self.get_widget("rateScale").set_value(rate)
|
self.get_widget("rateScale").set_value(rate)
|
||||||
else:
|
else:
|
||||||
self.get_widget("rateScale").set_value(50.0)
|
self.get_widget("rateScale").set_value(50.0)
|
||||||
|
|
||||||
pitch = self._getPitchForVoiceType(voiceType)
|
pitch = self._getPitchForVoiceType(voiceType)
|
||||||
if pitch is not None:
|
if pitch is not None:
|
||||||
self.get_widget("pitchScale").set_value(pitch)
|
self.get_widget("pitchScale").set_value(pitch)
|
||||||
@ -1150,7 +1150,7 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
|||||||
grid = self.get_widget('flashMessageDurationGrid')
|
grid = self.get_widget('flashMessageDurationGrid')
|
||||||
grid.set_sensitive(not checkbox.get_active())
|
grid.set_sensitive(not checkbox.get_active())
|
||||||
self.prefsDict["flashIsPersistent"] = checkbox.get_active()
|
self.prefsDict["flashIsPersistent"] = checkbox.get_active()
|
||||||
|
|
||||||
def textAttributeSpokenToggled(self, cell, path, model):
|
def textAttributeSpokenToggled(self, cell, path, model):
|
||||||
"""The user has toggled the state of one of the text attribute
|
"""The user has toggled the state of one of the text attribute
|
||||||
checkboxes to be spoken. Update our model to reflect this, then
|
checkboxes to be spoken. Update our model to reflect this, then
|
||||||
@ -1596,7 +1596,7 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
|||||||
elif dateFormat == messages.DATE_FORMAT_ABBREVIATED_YMD:
|
elif dateFormat == messages.DATE_FORMAT_ABBREVIATED_YMD:
|
||||||
indexdate = DATE_FORMAT_ABBREVIATED_YMD
|
indexdate = DATE_FORMAT_ABBREVIATED_YMD
|
||||||
combobox2.set_active (indexdate)
|
combobox2.set_active (indexdate)
|
||||||
|
|
||||||
combobox3 = self.get_widget("timeFormatCombo")
|
combobox3 = self.get_widget("timeFormatCombo")
|
||||||
self.populateComboBox(combobox3,
|
self.populateComboBox(combobox3,
|
||||||
[sdtime(messages.TIME_FORMAT_LOCALE, ltime()),
|
[sdtime(messages.TIME_FORMAT_LOCALE, ltime()),
|
||||||
@ -1757,7 +1757,7 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
|||||||
prefs["enableEchoByWord"])
|
prefs["enableEchoByWord"])
|
||||||
self.get_widget("enableEchoBySentenceCheckButton").set_active( \
|
self.get_widget("enableEchoBySentenceCheckButton").set_active( \
|
||||||
prefs["enableEchoBySentence"])
|
prefs["enableEchoBySentence"])
|
||||||
|
|
||||||
# Text attributes pane.
|
# Text attributes pane.
|
||||||
#
|
#
|
||||||
self._createTextAttributesTreeView()
|
self._createTextAttributesTreeView()
|
||||||
@ -1785,7 +1785,7 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
|||||||
self.get_widget("generalDesktopButton").set_active(True)
|
self.get_widget("generalDesktopButton").set_active(True)
|
||||||
else:
|
else:
|
||||||
self.get_widget("generalLaptopButton").set_active(True)
|
self.get_widget("generalLaptopButton").set_active(True)
|
||||||
|
|
||||||
combobox = self.get_widget("sayAllStyle")
|
combobox = self.get_widget("sayAllStyle")
|
||||||
self.populateComboBox(combobox, [guilabels.SAY_ALL_STYLE_LINE,
|
self.populateComboBox(combobox, [guilabels.SAY_ALL_STYLE_LINE,
|
||||||
guilabels.SAY_ALL_STYLE_SENTENCE])
|
guilabels.SAY_ALL_STYLE_SENTENCE])
|
||||||
@ -2748,7 +2748,7 @@ class CthulhuSetupGUI(cthulhu_gtkbuilder.GtkBuilderWrapper):
|
|||||||
elif dateFormatCombo == DATE_FORMAT_ABBREVIATED_YMD:
|
elif dateFormatCombo == DATE_FORMAT_ABBREVIATED_YMD:
|
||||||
newFormat = messages.DATE_FORMAT_ABBREVIATED_YMD
|
newFormat = messages.DATE_FORMAT_ABBREVIATED_YMD
|
||||||
self.prefsDict["presentDateFormat"] = newFormat
|
self.prefsDict["presentDateFormat"] = newFormat
|
||||||
|
|
||||||
def timeFormatChanged(self, widget):
|
def timeFormatChanged(self, widget):
|
||||||
"""Signal handler for the "changed" signal for the timeFormat
|
"""Signal handler for the "changed" signal for the timeFormat
|
||||||
GtkComboBox widget. Set the 'timeFormat' preference to the
|
GtkComboBox widget. Set the 'timeFormat' preference to the
|
||||||
|
@ -66,7 +66,7 @@ class DynamicApiManager():
|
|||||||
def getAPI(self, key, application = '', fallback = True):
|
def getAPI(self, key, application = '', fallback = True):
|
||||||
# get dynamic API
|
# get dynamic API
|
||||||
api = None
|
api = None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
api = self.api[application][key]
|
api = self.api[application][key]
|
||||||
return api
|
return api
|
||||||
@ -83,5 +83,5 @@ class DynamicApiManager():
|
|||||||
api = self.api[application]['']
|
api = self.api[application]['']
|
||||||
except:
|
except:
|
||||||
print('API Key: "{}/{}" not found,'.format(application, key))
|
print('API Key: "{}/{}" not found,'.format(application, key))
|
||||||
|
|
||||||
return api
|
return api
|
||||||
|
@ -44,7 +44,7 @@ CTHULHU_SHIFT_MODIFIER_MASK = keybindings.CTHULHU_SHIFT_MODIFIER_MASK
|
|||||||
CTHULHU_CTRL_MODIFIER_MASK = keybindings.CTHULHU_CTRL_MODIFIER_MASK
|
CTHULHU_CTRL_MODIFIER_MASK = keybindings.CTHULHU_CTRL_MODIFIER_MASK
|
||||||
|
|
||||||
keymap = (
|
keymap = (
|
||||||
|
|
||||||
("9", defaultModifierMask, CTHULHU_MODIFIER_MASK,
|
("9", defaultModifierMask, CTHULHU_MODIFIER_MASK,
|
||||||
"routePointerToItemHandler"),
|
"routePointerToItemHandler"),
|
||||||
|
|
||||||
|
@ -19,6 +19,12 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
# Fallback if pluggy is not available
|
# Fallback if pluggy is not available
|
||||||
def cthulhu_hookimpl(func=None, **kwargs):
|
def cthulhu_hookimpl(func=None, **kwargs):
|
||||||
|
"""Fallback decorator when pluggy is not available.
|
||||||
|
|
||||||
|
This is a no-op decorator that returns the original function.
|
||||||
|
It allows the code to continue working without pluggy, though
|
||||||
|
plugins will be disabled.
|
||||||
|
"""
|
||||||
if func is None:
|
if func is None:
|
||||||
return lambda f: f
|
return lambda f: f
|
||||||
return func
|
return func
|
||||||
|
@ -270,6 +270,9 @@ class PluginSystemManager:
|
|||||||
plugin_instance = plugin_class()
|
plugin_instance = plugin_class()
|
||||||
pluginInfo.instance = plugin_instance
|
pluginInfo.instance = plugin_instance
|
||||||
|
|
||||||
|
# Ensure plugins have a reference to the app
|
||||||
|
plugin_instance.app = self.getApp()
|
||||||
|
|
||||||
if hasattr(plugin_instance, 'set_app'):
|
if hasattr(plugin_instance, 'set_app'):
|
||||||
plugin_instance.set_app(self.getApp())
|
plugin_instance.set_app(self.getApp())
|
||||||
|
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
[Plugin]
|
|
||||||
Module=ByeCthulhu
|
|
||||||
Loader=python3
|
|
||||||
Name=Stop announcement for cthulhu
|
|
||||||
Description=Test plugin for cthulhu
|
|
||||||
Authors=Chrys chrys@linux-a11y.org
|
|
@ -1,52 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
#
|
|
||||||
# Copyright (c) 2024 Stormux
|
|
||||||
# Copyright (c) 2010-2012 The Orca Team
|
|
||||||
# Copyright (c) 2012 Igalia, S.L.
|
|
||||||
# Copyright (c) 2005-2010 Sun Microsystems Inc.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# This library is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
# Lesser General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Lesser General Public
|
|
||||||
# License along with this library; if not, write to the
|
|
||||||
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
|
||||||
# Boston MA 02110-1301 USA.
|
|
||||||
#
|
|
||||||
# Fork of Orca Screen Reader (GNOME)
|
|
||||||
# Original source: https://gitlab.gnome.org/GNOME/orca
|
|
||||||
|
|
||||||
from cthulhu import plugin
|
|
||||||
|
|
||||||
import gi
|
|
||||||
gi.require_version('Peas', '1.0')
|
|
||||||
from gi.repository import GObject
|
|
||||||
from gi.repository import Peas
|
|
||||||
import time
|
|
||||||
|
|
||||||
|
|
||||||
class ByeCthulhu(GObject.Object, Peas.Activatable, plugin.Plugin):
|
|
||||||
#__gtype_name__ = 'ByeCthulhu'
|
|
||||||
|
|
||||||
object = GObject.Property(type=GObject.Object)
|
|
||||||
def __init__(self):
|
|
||||||
plugin.Plugin.__init__(self)
|
|
||||||
def do_activate(self):
|
|
||||||
API = self.object
|
|
||||||
self.connectSignal("stop-application-completed", self.process)
|
|
||||||
def do_deactivate(self):
|
|
||||||
API = self.object
|
|
||||||
def do_update_state(self):
|
|
||||||
API = self.object
|
|
||||||
def process(self, app):
|
|
||||||
messages = app.getDynamicApiManager().getAPI('Messages')
|
|
||||||
activeScript = app.getDynamicApiManager().getAPI('CthulhuState').activeScript
|
|
||||||
activeScript.presentationInterrupt()
|
|
||||||
activeScript.presentMessage(messages.STOP_CTHULHU, resetStyles=False)
|
|
@ -1,7 +1,7 @@
|
|||||||
cthulhu_python_PYTHON = \
|
cthulhu_python_PYTHON = \
|
||||||
__init__.py \
|
__init__.py \
|
||||||
ByeCthulhu.plugin \
|
plugin.info \
|
||||||
ByeCthulhu.py
|
plugin.py
|
||||||
|
|
||||||
cthulhu_pythondir=$(pkgpythondir)/plugins/ByeCthulhu
|
cthulhu_pythondir=$(pkgpythondir)/plugins/ByeCthulhu
|
||||||
|
|
||||||
|
8
src/cthulhu/plugins/ByeCthulhu/plugin.info
Normal file
8
src/cthulhu/plugins/ByeCthulhu/plugin.info
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
name = Bye Cthulhu
|
||||||
|
version = 1.0.0
|
||||||
|
description = Says goodbye when Cthulhu is shutting down
|
||||||
|
authors = Stormux <storm_dragon@stormux.org>
|
||||||
|
website = https://stormux.org
|
||||||
|
copyright = Copyright 2025
|
||||||
|
builtin = false
|
||||||
|
hidden = false
|
74
src/cthulhu/plugins/ByeCthulhu/plugin.py
Normal file
74
src/cthulhu/plugins/ByeCthulhu/plugin.py
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
#!/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.
|
||||||
|
|
||||||
|
"""Bye Cthulhu plugin for Cthulhu."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
from cthulhu.plugin import Plugin, cthulhu_hookimpl
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class ByeCthulhu(Plugin):
|
||||||
|
"""Plugin that speaks a goodbye message when Cthulhu is shutting down."""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""Initialize the plugin."""
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
logger.info("ByeCthulhu plugin initialized")
|
||||||
|
self._signal_handler_id = None
|
||||||
|
|
||||||
|
@cthulhu_hookimpl
|
||||||
|
def activate(self, plugin=None):
|
||||||
|
"""Activate the plugin."""
|
||||||
|
# Skip if this activation call isn't for us
|
||||||
|
if plugin is not None and plugin is not self:
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info("Activating ByeCthulhu plugin")
|
||||||
|
try:
|
||||||
|
# Connect to the stop-application-completed signal
|
||||||
|
signal_manager = self.app.getSignalManager()
|
||||||
|
self._signal_handler_id = signal_manager.connectSignal(
|
||||||
|
"stop-application-completed",
|
||||||
|
self.process
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error activating ByeCthulhu plugin: {e}")
|
||||||
|
|
||||||
|
@cthulhu_hookimpl
|
||||||
|
def deactivate(self, plugin=None):
|
||||||
|
"""Deactivate the plugin."""
|
||||||
|
# Skip if this deactivation call isn't for us
|
||||||
|
if plugin is not None and plugin is not self:
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info("Deactivating ByeCthulhu plugin")
|
||||||
|
try:
|
||||||
|
# Disconnect signal if we have an ID
|
||||||
|
if self._signal_handler_id is not None:
|
||||||
|
signal_manager = self.app.getSignalManager()
|
||||||
|
signal_manager.disconnectSignal(
|
||||||
|
"stop-application-completed",
|
||||||
|
self._signal_handler_id
|
||||||
|
)
|
||||||
|
self._signal_handler_id = None
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error deactivating ByeCthulhu plugin: {e}")
|
||||||
|
|
||||||
|
def process(self, app):
|
||||||
|
"""Process the stop-application-completed signal."""
|
||||||
|
try:
|
||||||
|
messages = app.getDynamicApiManager().getAPI('Messages')
|
||||||
|
state = app.getDynamicApiManager().getAPI('CthulhuState')
|
||||||
|
if state.activeScript:
|
||||||
|
state.activeScript.presentationInterrupt()
|
||||||
|
state.activeScript.presentMessage(messages.STOP_CTHULHU, resetStyles=False)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in ByeCthulhu process: {e}")
|
@ -1,6 +0,0 @@
|
|||||||
[Plugin]
|
|
||||||
Module=DisplayVersion
|
|
||||||
Loader=python3
|
|
||||||
Name=Display Version
|
|
||||||
Description=Announce the current version of Cthulhu
|
|
||||||
Authors=Storm Dragon <storm_dragon@stormux.org>
|
|
@ -1,61 +0,0 @@
|
|||||||
#!/usr/bin/env python3
|
|
||||||
#
|
|
||||||
# Copyright (c) 2024 Stormux
|
|
||||||
# Copyright (c) 2010-2012 The Orca Team
|
|
||||||
# Copyright (c) 2012 Igalia, S.L.
|
|
||||||
# Copyright (c) 2005-2010 Sun Microsystems Inc.
|
|
||||||
#
|
|
||||||
# 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.
|
|
||||||
#
|
|
||||||
# This library is distributed in the hope that it will be useful,
|
|
||||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
||||||
# Lesser General Public License for more details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Lesser General Public
|
|
||||||
# License along with this library; if not, write to the
|
|
||||||
# Free Software Foundation, Inc., Franklin Street, Fifth Floor,
|
|
||||||
# Boston MA 02110-1301 USA.
|
|
||||||
#
|
|
||||||
# Fork of Orca Screen Reader (GNOME)
|
|
||||||
# Original source: https://gitlab.gnome.org/GNOME/orca
|
|
||||||
|
|
||||||
from cthulhu import plugin
|
|
||||||
|
|
||||||
import gi
|
|
||||||
gi.require_version('Peas', '1.0')
|
|
||||||
from gi.repository import GObject
|
|
||||||
from gi.repository import Peas
|
|
||||||
from cthulhu import cthulhuVersion
|
|
||||||
|
|
||||||
class DisplayVersion(GObject.Object, Peas.Activatable, plugin.Plugin):
|
|
||||||
__gtype_name__ = 'displayversion'
|
|
||||||
|
|
||||||
object = GObject.Property(type=GObject.Object)
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
plugin.Plugin.__init__(self)
|
|
||||||
self._api = None
|
|
||||||
|
|
||||||
def do_activate(self):
|
|
||||||
self._api = self.object
|
|
||||||
self.registerGestureByString(
|
|
||||||
self.speakText,
|
|
||||||
_(f'Cthulhu screen reader version {cthulhuVersion.version}-{cthulhuVersion.codeName}'),
|
|
||||||
'kb:cthulhu+shift+v'
|
|
||||||
)
|
|
||||||
|
|
||||||
def do_deactivate(self):
|
|
||||||
self._api = None
|
|
||||||
|
|
||||||
def speakText(self, script=None, inputEvent=None):
|
|
||||||
if not self._api:
|
|
||||||
return False
|
|
||||||
self._api.app.getDynamicApiManager().getAPI('CthulhuState').activeScript.presentMessage(
|
|
||||||
f'Cthulhu screen reader version {cthulhuVersion.version}-{cthulhuVersion.codeName}',
|
|
||||||
resetStyles=False
|
|
||||||
)
|
|
||||||
return True
|
|
@ -1,7 +1,7 @@
|
|||||||
cthulhu_python_PYTHON = \
|
cthulhu_python_PYTHON = \
|
||||||
__init__.py \
|
__init__.py \
|
||||||
DisplayVersion.plugin \
|
plugin.info \
|
||||||
DisplayVersion.py
|
plugin.py
|
||||||
|
|
||||||
cthulhu_pythondir=$(pkgpythondir)/plugins/DisplayVersion
|
cthulhu_pythondir=$(pkgpythondir)/plugins/DisplayVersion
|
||||||
|
|
||||||
|
8
src/cthulhu/plugins/DisplayVersion/plugin.info
Normal file
8
src/cthulhu/plugins/DisplayVersion/plugin.info
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
name = Display Version
|
||||||
|
version = 1.0.0
|
||||||
|
description = Announces the Cthulhu version with Cthulhu+Shift+V
|
||||||
|
authors = Stormux <storm_dragon@stormux.org>
|
||||||
|
website = https://stormux.org
|
||||||
|
copyright = Copyright 2025
|
||||||
|
builtin = false
|
||||||
|
hidden = false
|
68
src/cthulhu/plugins/DisplayVersion/plugin.py
Normal file
68
src/cthulhu/plugins/DisplayVersion/plugin.py
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
#!/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.
|
||||||
|
|
||||||
|
"""Display Version plugin for Cthulhu."""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
from cthulhu.plugin import Plugin, cthulhu_hookimpl
|
||||||
|
from cthulhu import cthulhuVersion
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class DisplayVersion(Plugin):
|
||||||
|
"""Plugin that announces the current Cthulhu version."""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""Initialize the plugin."""
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
logger.info("DisplayVersion plugin initialized")
|
||||||
|
self._kb_binding = None
|
||||||
|
|
||||||
|
@cthulhu_hookimpl
|
||||||
|
def activate(self, plugin=None):
|
||||||
|
"""Activate the plugin."""
|
||||||
|
# Skip if this activation call isn't for us
|
||||||
|
if plugin is not None and plugin is not self:
|
||||||
|
return
|
||||||
|
|
||||||
|
try:
|
||||||
|
logger.info("Activating DisplayVersion plugin")
|
||||||
|
|
||||||
|
# Register keyboard shortcut
|
||||||
|
self._kb_binding = self.registerGestureByString(
|
||||||
|
self.speakText,
|
||||||
|
f'Cthulhu screen reader version {cthulhuVersion.version}-{cthulhuVersion.codeName}',
|
||||||
|
'kb:cthulhu+shift+v'
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error activating DisplayVersion plugin: {e}")
|
||||||
|
|
||||||
|
@cthulhu_hookimpl
|
||||||
|
def deactivate(self, plugin=None):
|
||||||
|
"""Deactivate the plugin."""
|
||||||
|
# Skip if this deactivation call isn't for us
|
||||||
|
if plugin is not None and plugin is not self:
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info("Deactivating DisplayVersion plugin")
|
||||||
|
|
||||||
|
def speakText(self, script=None, inputEvent=None):
|
||||||
|
"""Speak the Cthulhu version when shortcut is pressed."""
|
||||||
|
try:
|
||||||
|
if self.app:
|
||||||
|
state = self.app.getDynamicApiManager().getAPI('CthulhuState')
|
||||||
|
if state.activeScript:
|
||||||
|
state.activeScript.presentMessage(
|
||||||
|
f'Cthulhu screen reader version {cthulhuVersion.version}-{cthulhuVersion.codeName}',
|
||||||
|
resetStyles=False
|
||||||
|
)
|
||||||
|
return True
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in DisplayVersion speakText: {e}")
|
||||||
|
return False
|
33
src/cthulhu/plugins/hello_world/README.md
Normal file
33
src/cthulhu/plugins/hello_world/README.md
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# Hello World Plugin for Cthulhu
|
||||||
|
|
||||||
|
This is a simple test plugin for the Cthulhu screen reader that demonstrates how to use the pluggy-based plugin system.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Registers a keyboard shortcut (Cthulhu+z) that speaks "hello world" when pressed
|
||||||
|
- Demonstrates how to use the Plugin base class
|
||||||
|
- Shows how to use cthulhu_hookimpl for hook implementation
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
The plugin uses the following key features:
|
||||||
|
- `pluggy` for hook specification and implementation
|
||||||
|
- The `Plugin` base class from cthulhu.plugin
|
||||||
|
- The `cthulhu_hookimpl` decorator to mark functions as plugin hooks
|
||||||
|
- The `registerGestureByString` method to register keyboard shortcuts
|
||||||
|
|
||||||
|
## Structure
|
||||||
|
|
||||||
|
- `plugin.py`: The main plugin implementation
|
||||||
|
- `plugin.info`: Metadata about the plugin
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
Requires the pluggy package to be installed:
|
||||||
|
```
|
||||||
|
pip install pluggy
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The plugin will be automatically loaded when Cthulhu starts if it's listed in the activePlugins setting.
|
@ -113,7 +113,7 @@ class ResourceContext():
|
|||||||
self.settings[profile][application] = {}
|
self.settings[profile][application] = {}
|
||||||
# add entry
|
# add entry
|
||||||
self.settings[profile][application][sub_setting_name] = entry
|
self.settings[profile][application][sub_setting_name] = entry
|
||||||
|
|
||||||
print('add', 'settings', self.getName(), profile, application, entry.getResourceText())
|
print('add', 'settings', self.getName(), profile, application, entry.getResourceText())
|
||||||
|
|
||||||
|
|
||||||
@ -214,7 +214,7 @@ class ResourceContext():
|
|||||||
self.unregisterAllAPI()
|
self.unregisterAllAPI()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
|
|
||||||
def unregisterAllAPI(self):
|
def unregisterAllAPI(self):
|
||||||
dynamicApiManager = self.app.getDynamicApiManager()
|
dynamicApiManager = self.app.getDynamicApiManager()
|
||||||
for application, value in self.getAPIs().copy().items():
|
for application, value in self.getAPIs().copy().items():
|
||||||
@ -226,7 +226,7 @@ class ResourceContext():
|
|||||||
print('unregister api ', self.getName(), entry.getEntryType(), entry.getResourceText())
|
print('unregister api ', self.getName(), entry.getEntryType(), entry.getResourceText())
|
||||||
def unregisterAllGestures(self):
|
def unregisterAllGestures(self):
|
||||||
APIHelper = self.app.getAPIHelper()
|
APIHelper = self.app.getAPIHelper()
|
||||||
|
|
||||||
for profile, profileValue in self.getGestures().copy().items():
|
for profile, profileValue in self.getGestures().copy().items():
|
||||||
for application, applicationValue in profileValue.copy().items():
|
for application, applicationValue in profileValue.copy().items():
|
||||||
for gesture, entry in applicationValue.copy().items():
|
for gesture, entry in applicationValue.copy().items():
|
||||||
@ -272,12 +272,12 @@ class ResourceManager():
|
|||||||
def removeResourceContext(self, contextName):
|
def removeResourceContext(self, contextName):
|
||||||
if not contextName:
|
if not contextName:
|
||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.resourceContextDict[contextName].unregisterAllResources()
|
self.resourceContextDict[contextName].unregisterAllResources()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# temp
|
# temp
|
||||||
try:
|
try:
|
||||||
print('_________', 'summery', self.resourceContextDict[contextName].getName(), '_________')
|
print('_________', 'summery', self.resourceContextDict[contextName].getName(), '_________')
|
||||||
@ -302,7 +302,7 @@ class ResourceManager():
|
|||||||
return self.resourceContextDict[contextName]
|
return self.resourceContextDict[contextName]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def addAPI(self, application, api, contextName = None):
|
def addAPI(self, application, api, contextName = None):
|
||||||
if not contextName:
|
if not contextName:
|
||||||
return
|
return
|
||||||
|
@ -3558,10 +3558,10 @@ class Utilities:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def unicodeValueString(character):
|
def unicodeValueString(character):
|
||||||
""" Returns a four hex digit representation of the given character
|
""" Returns a four hex digit representation of the given character
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
- The character to return representation
|
- The character to return representation
|
||||||
|
|
||||||
Returns a string representaition of the given character unicode vlue
|
Returns a string representaition of the given character unicode vlue
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ class SpeechServer(speechserver.SpeechServer):
|
|||||||
# See the parent class for documentation.
|
# See the parent class for documentation.
|
||||||
|
|
||||||
_active_servers = {}
|
_active_servers = {}
|
||||||
|
|
||||||
DEFAULT_SERVER_ID = 'default'
|
DEFAULT_SERVER_ID = 'default'
|
||||||
_SERVER_NAMES = {DEFAULT_SERVER_ID: guilabels.DEFAULT_SYNTHESIZER}
|
_SERVER_NAMES = {DEFAULT_SERVER_ID: guilabels.DEFAULT_SYNTHESIZER}
|
||||||
|
|
||||||
@ -93,7 +93,7 @@ class SpeechServer(speechserver.SpeechServer):
|
|||||||
|
|
||||||
Attempt to create the server if it doesn't exist yet. Returns None
|
Attempt to create the server if it doesn't exist yet. Returns None
|
||||||
when it is not possible to create the server.
|
when it is not possible to create the server.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if serverId not in cls._active_servers:
|
if serverId not in cls._active_servers:
|
||||||
cls(serverId)
|
cls(serverId)
|
||||||
@ -781,16 +781,16 @@ class SpeechServer(speechserver.SpeechServer):
|
|||||||
def reset(self, text=None, acss=None):
|
def reset(self, text=None, acss=None):
|
||||||
self._client.close()
|
self._client.close()
|
||||||
self._init()
|
self._init()
|
||||||
|
|
||||||
def list_output_modules(self):
|
def list_output_modules(self):
|
||||||
"""Return names of available output modules as a tuple of strings.
|
"""Return names of available output modules as a tuple of strings.
|
||||||
|
|
||||||
This method is not a part of Cthulhu speech API, but is used internally
|
This method is not a part of Cthulhu speech API, but is used internally
|
||||||
by the Speech Dispatcher backend.
|
by the Speech Dispatcher backend.
|
||||||
|
|
||||||
The returned tuple can be empty if the information can not be
|
The returned tuple can be empty if the information can not be
|
||||||
obtained (e.g. with an older Speech Dispatcher version).
|
obtained (e.g. with an older Speech Dispatcher version).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
return self._send_command(self._client.list_output_modules)
|
return self._send_command(self._client.list_output_modules)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user