Convert simple plugin plugin to new plugin format. Hmm, gotta get in a couple more... plugin plugin plugin! lol
This commit is contained in:
parent
c712bea421
commit
35a83327ac
@ -132,7 +132,6 @@ src/cthulhu/plugins/Clipboard/Makefile
|
|||||||
src/cthulhu/plugins/DisplayVersion/Makefile
|
src/cthulhu/plugins/DisplayVersion/Makefile
|
||||||
src/cthulhu/plugins/hello_world/Makefile
|
src/cthulhu/plugins/hello_world/Makefile
|
||||||
src/cthulhu/plugins/self_voice/Makefile
|
src/cthulhu/plugins/self_voice/Makefile
|
||||||
src/cthulhu/plugins/Time/Makefile
|
|
||||||
src/cthulhu/plugins/SimplePluginSystem/Makefile
|
src/cthulhu/plugins/SimplePluginSystem/Makefile
|
||||||
src/cthulhu/backends/Makefile
|
src/cthulhu/backends/Makefile
|
||||||
src/cthulhu/cthulhu_bin.py
|
src/cthulhu/cthulhu_bin.py
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
SUBDIRS = Clipboard DisplayVersion hello_world self_voice Time ByeCthulhu HelloCthulhu PluginManager SimplePluginSystem
|
SUBDIRS = Clipboard DisplayVersion hello_world self_voice ByeCthulhu HelloCthulhu PluginManager SimplePluginSystem
|
||||||
|
|
||||||
cthulhu_pythondir=$(pkgpythondir)/plugins
|
cthulhu_pythondir=$(pkgpythondir)/plugins
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
cthulhu_python_PYTHON = \
|
cthulhu_python_PYTHON = \
|
||||||
__init__.py \
|
__init__.py \
|
||||||
SimplePluginSystem.plugin \
|
plugin.info \
|
||||||
SimplePluginSystem.py
|
plugin.py
|
||||||
|
|
||||||
cthulhu_pythondir=$(pkgpythondir)/plugins/SimplePluginSystem
|
cthulhu_pythondir=$(pkgpythondir)/plugins/SimplePluginSystem
|
||||||
|
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
[Plugin]
|
|
||||||
Module=SimplePluginSystem
|
|
||||||
Loader=python3
|
|
||||||
Name=Simple Plugin System
|
|
||||||
Description=Simple plugin system implementation for Cthulhu
|
|
||||||
Authors=Chrys <chrys@linux-a11y.org>;Storm Dragon <storm_dragon@stormux.org>
|
|
||||||
Copyright=Copyright Â2024 Chrys, Storm Dragon
|
|
||||||
Website=https://git.stormux.org/storm/cthulhu
|
|
||||||
Version=1.0
|
|
||||||
Builtin=true
|
|
||||||
|
|
@ -1,291 +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
|
|
||||||
|
|
||||||
from gi.repository import GObject, Peas
|
|
||||||
import glob
|
|
||||||
import os
|
|
||||||
import importlib.util
|
|
||||||
import random
|
|
||||||
import string
|
|
||||||
import _thread
|
|
||||||
from subprocess import Popen, PIPE
|
|
||||||
|
|
||||||
settings = None
|
|
||||||
speech = None
|
|
||||||
braille = None
|
|
||||||
input_event = None
|
|
||||||
|
|
||||||
def outputMessage( Message):
|
|
||||||
if (settings.enableSpeech):
|
|
||||||
speech.speak(Message)
|
|
||||||
if (settings.enableBraille):
|
|
||||||
braille.displayMessage(Message)
|
|
||||||
|
|
||||||
class SimplePluginSystem(GObject.Object, Peas.Activatable, plugin.Plugin):
|
|
||||||
__gtype_name__ = 'SimplePluginSystem'
|
|
||||||
object = GObject.Property(type=GObject.Object)
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
plugin.Plugin.__init__(self)
|
|
||||||
self.plugin_list = []
|
|
||||||
self.loaded = False
|
|
||||||
self.plugin_repo = os.path.expanduser('~') + "/.local/share/cthulhu/simple-plugins-enabled/"
|
|
||||||
|
|
||||||
def do_activate(self):
|
|
||||||
API = self.object
|
|
||||||
global settings
|
|
||||||
global speech
|
|
||||||
global braille
|
|
||||||
global input_event
|
|
||||||
settings = API.app.getDynamicApiManager().getAPI('Settings')
|
|
||||||
speech = API.app.getDynamicApiManager().getAPI('Speech')
|
|
||||||
braille = API.app.getDynamicApiManager().getAPI('Braille')
|
|
||||||
input_event = API.app.getDynamicApiManager().getAPI('InputEvent')
|
|
||||||
"""Required method for plugins"""
|
|
||||||
if not self.loaded:
|
|
||||||
self.load_plugins()
|
|
||||||
|
|
||||||
def do_deactivate(self):
|
|
||||||
"""Required method for plugins"""
|
|
||||||
# Remove all registered keybindings
|
|
||||||
for plugin in self.plugin_list:
|
|
||||||
self.unregisterShortcut(plugin['function'], plugin['shortcut'])
|
|
||||||
self.loaded = False
|
|
||||||
self.plugin_list = []
|
|
||||||
|
|
||||||
def SetupShortcutAndHandle(self, currPluginSetting):
|
|
||||||
shortcut = ''
|
|
||||||
# just the modifier
|
|
||||||
if not currPluginSetting['shiftkey'] and not currPluginSetting['ctrlkey'] and not currPluginSetting['altkey']:
|
|
||||||
shortcut = 'kb:cthulhu+' + currPluginSetting['key']
|
|
||||||
# cthulhu + alt
|
|
||||||
if not currPluginSetting['shiftkey'] and not currPluginSetting['ctrlkey'] and currPluginSetting['altkey']:
|
|
||||||
shortcut = 'kb:cthulhu+alt+' + currPluginSetting['key']
|
|
||||||
# cthulhu + CTRL
|
|
||||||
if not currPluginSetting['shiftkey'] and currPluginSetting['ctrlkey'] and not currPluginSetting['altkey']:
|
|
||||||
shortcut = 'kb:cthulhu+control+' + currPluginSetting['key']
|
|
||||||
# cthulhu + alt + CTRL
|
|
||||||
if not currPluginSetting['shiftkey'] and currPluginSetting['ctrlkey'] and currPluginSetting['altkey']:
|
|
||||||
shortcut = 'kb:cthulhu+alt+control+ ' + currPluginSetting['key']
|
|
||||||
# cthulhu + shift
|
|
||||||
if currPluginSetting['shiftkey'] and not currPluginSetting['ctrlkey'] and not currPluginSetting['altkey']:
|
|
||||||
shortcut = 'kb:cthulhu+shift+' + currPluginSetting['key']
|
|
||||||
# alt + shift
|
|
||||||
if currPluginSetting['shiftkey'] and not currPluginSetting['ctrlkey'] and currPluginSetting['altkey']:
|
|
||||||
shortcut = 'kb:alt+shift+' + currPluginSetting['key']
|
|
||||||
if shortcut != '':
|
|
||||||
print(shortcut)
|
|
||||||
currPluginSetting['shortcut'] = shortcut
|
|
||||||
self.registerGestureByString(currPluginSetting['function'], _(currPluginSetting['pluginname']), shortcut)
|
|
||||||
return currPluginSetting
|
|
||||||
|
|
||||||
def id_generator(self, size=7, chars=string.ascii_letters):
|
|
||||||
return ''.join(random.choice(chars) for _ in range(size))
|
|
||||||
|
|
||||||
def initSettings(self):
|
|
||||||
currPluginSetting={
|
|
||||||
'pluginname':'',
|
|
||||||
'functionname':'',
|
|
||||||
'key':'',
|
|
||||||
'shiftkey':False,
|
|
||||||
'ctrlkey':False,
|
|
||||||
'altkey':False,
|
|
||||||
'startnotify':False,
|
|
||||||
'stopnotify':False,
|
|
||||||
'blockcall':False,
|
|
||||||
'error':False,
|
|
||||||
'exec': False,
|
|
||||||
'parameters':'',
|
|
||||||
'function':None,
|
|
||||||
'inputeventhandler':None,
|
|
||||||
'valid':False,
|
|
||||||
'supressoutput':False,
|
|
||||||
'shortcut': ''
|
|
||||||
}
|
|
||||||
return currPluginSetting
|
|
||||||
|
|
||||||
def getPluginSettings(self, filepath, currPluginSetting):
|
|
||||||
try:
|
|
||||||
currPluginSetting['file'] = filepath
|
|
||||||
fileName, fileExtension = os.path.splitext(filepath)
|
|
||||||
if (fileExtension and (fileExtension != '')): #if there is an extension
|
|
||||||
currPluginSetting['loadable'] = (fileExtension.lower() == '.py') # only python is loadable
|
|
||||||
filename = os.path.basename(filepath) #filename
|
|
||||||
filename = os.path.splitext(filename)[0] #remove extension if we have one
|
|
||||||
#remove pluginname seperated by __-__
|
|
||||||
filenamehelper = filename.split('__-__')
|
|
||||||
filename = filenamehelper[len(filenamehelper) - 1 ]
|
|
||||||
currPluginSetting['permission'] = os.access(filepath, os.X_OK )
|
|
||||||
currPluginSetting['pluginname'] = 'NoNameAvailable'
|
|
||||||
if len(filenamehelper) == 2:
|
|
||||||
currPluginSetting['pluginname'] = filenamehelper[0]
|
|
||||||
#now get shortcuts seperated by __+__
|
|
||||||
filenamehelper = filename.split('__+__')
|
|
||||||
if len([y for y in filenamehelper if 'parameters_' in y.lower()]) == 1 and\
|
|
||||||
len([y for y in filenamehelper if 'parameters_' in y.lower()][0]) > 11:
|
|
||||||
currPluginSetting['parameters'] = [y for y in filenamehelper if 'parameters_' in y.lower()][0][11:]
|
|
||||||
if len([y for y in filenamehelper if 'key_' in y.lower()]) == 1 and\
|
|
||||||
len([y for y in filenamehelper if 'key_' in y.lower()][0]) > 4 :
|
|
||||||
currPluginSetting['key'] = [y for y in filenamehelper if 'key_' in y.lower()][0][4]
|
|
||||||
if currPluginSetting['key'] == '':
|
|
||||||
settcurrPluginSetting = 'shift' in map(str.lower, filenamehelper)
|
|
||||||
currPluginSetting['ctrlkey'] = 'control' in map(str.lower, filenamehelper)
|
|
||||||
currPluginSetting['altkey'] = 'alt' in map(str.lower, filenamehelper)
|
|
||||||
currPluginSetting['startnotify'] = 'startnotify' in map(str.lower, filenamehelper)
|
|
||||||
currPluginSetting['stopnotify'] = 'stopnotify' in map(str.lower, filenamehelper)
|
|
||||||
currPluginSetting['blockcall'] = 'blockcall' in map(str.lower, filenamehelper)
|
|
||||||
currPluginSetting['error'] = 'error' in map(str.lower, filenamehelper)
|
|
||||||
currPluginSetting['supressoutput'] = 'supressoutput' in map(str.lower, filenamehelper)
|
|
||||||
currPluginSetting['exec'] = 'exec' in map(str.lower, filenamehelper)
|
|
||||||
currPluginSetting['loadmodule'] = 'loadmodule' in map(str.lower, filenamehelper)
|
|
||||||
currPluginSetting = self.readSettingsFromPlugin(currPluginSetting)
|
|
||||||
if not currPluginSetting['loadmodule']:
|
|
||||||
if not currPluginSetting['permission']: #subprocessing only works with exec permission
|
|
||||||
return self.initSettings()
|
|
||||||
if currPluginSetting['loadmodule'] and not currPluginSetting['loadable']: #sorry.. its not loadable only .py is loadable
|
|
||||||
return self.initSettings()
|
|
||||||
if (len(currPluginSetting['key']) > 1): #no shortcut
|
|
||||||
if not currPluginSetting['exec']: # and no exec -> the plugin make no sense because it isnt hooked anywhere
|
|
||||||
return self.initSettings() #so not load it (sets valid = False)
|
|
||||||
else:
|
|
||||||
currPluginSetting['key'] = '' #there is a strange key, but exec? ignore the key..
|
|
||||||
currPluginSetting['valid'] = True # we could load everything
|
|
||||||
return currPluginSetting
|
|
||||||
except:
|
|
||||||
return self.initSettings()
|
|
||||||
|
|
||||||
def readSettingsFromPlugin(self, currPluginSetting):
|
|
||||||
if not os.access(currPluginSetting['file'], os.R_OK ):
|
|
||||||
return currPluginSetting
|
|
||||||
fileName, fileExtension = os.path.splitext(currPluginSetting['file'])
|
|
||||||
if (fileExtension and (fileExtension != '')): #if there is an extension
|
|
||||||
if (fileExtension.lower() != '.py') and \
|
|
||||||
(fileExtension.lower() != '.sh'):
|
|
||||||
return currPluginSetting
|
|
||||||
else:
|
|
||||||
return currPluginSetting
|
|
||||||
|
|
||||||
with open(currPluginSetting['file'], "r") as pluginFile:
|
|
||||||
for line in pluginFile:
|
|
||||||
currPluginSetting['shiftkey'] = ('sopsproperty:shift' in line.lower().replace(" ", "")) or currPluginSetting['shiftkey']
|
|
||||||
currPluginSetting['ctrlkey'] = ('sopsproperty:control' in line.lower().replace(" ", "")) or currPluginSetting['ctrlkey']
|
|
||||||
currPluginSetting['altkey'] = ('sopsproperty:alt' in line.lower().replace(" ", "")) or currPluginSetting['altkey']
|
|
||||||
currPluginSetting['startnotify'] = ('sopsproperty:startnotify' in line.lower().replace(" ", "")) or currPluginSetting['startnotify']
|
|
||||||
currPluginSetting['stopnotify'] = ('sopsproperty:stopnotify' in line.lower().replace(" ", "")) or currPluginSetting['stopnotify']
|
|
||||||
currPluginSetting['blockcall'] = ('sopsproperty:blockcall' in line.lower().replace(" ", "")) or currPluginSetting['blockcall']
|
|
||||||
currPluginSetting['error'] = ('sopsproperty:error' in line.lower().replace(" ", "")) or currPluginSetting['error']
|
|
||||||
currPluginSetting['supressoutput'] = ('sopsproperty:supressoutput' in line.lower().replace(" ", "")) or currPluginSetting['supressoutput']
|
|
||||||
currPluginSetting['exec'] = ('sopsproperty:exec' in line.lower().replace(" ", "")) or currPluginSetting['exec']
|
|
||||||
currPluginSetting['loadmodule'] = ('sopsproperty:loadmodule' in line.lower().replace(" ", "")) or currPluginSetting['loadmodule']
|
|
||||||
return currPluginSetting
|
|
||||||
|
|
||||||
def buildPluginSubprocess(self, currPluginSetting):
|
|
||||||
currplugin = "\'\"" + currPluginSetting['file'] + "\" " + currPluginSetting['parameters'] + "\'"
|
|
||||||
pluginname = currPluginSetting['pluginname']
|
|
||||||
if currPluginSetting['blockcall']:
|
|
||||||
pluginname = "blocking " + pluginname
|
|
||||||
fun_body = "global " + currPluginSetting['functionname']+"\n"
|
|
||||||
fun_body += "def " + currPluginSetting['functionname'] + "(script=None, inputEvent=None):\n"
|
|
||||||
if currPluginSetting['startnotify']:
|
|
||||||
fun_body +=" outputMessage('start " + pluginname + "')\n"
|
|
||||||
fun_body +=" p = Popen(" + currplugin + ", stdout=PIPE, stderr=PIPE, shell=True)\n"
|
|
||||||
fun_body +=" stdout, stderr = p.communicate()\n"
|
|
||||||
fun_body +=" message = ''\n"
|
|
||||||
fun_body +=" if not " + str(currPluginSetting['supressoutput']) + " and stdout:\n"
|
|
||||||
fun_body +=" message += str(stdout, \"utf-8\")\n"
|
|
||||||
fun_body +=" if " + str(currPluginSetting['error']) + " and stderr:\n"
|
|
||||||
fun_body +=" message += ' error: ' + str(stderr, \"utf-8\")\n"
|
|
||||||
fun_body +=" outputMessage( message)\n"
|
|
||||||
if currPluginSetting['stopnotify']:
|
|
||||||
fun_body +=" outputMessage('finish " + pluginname + "')\n"
|
|
||||||
fun_body +=" return True\n\n"
|
|
||||||
fun_body += "global " + currPluginSetting['functionname']+"T\n"
|
|
||||||
fun_body +="def " + currPluginSetting['functionname'] + "T(script=None, inputEvent=None):\n"
|
|
||||||
fun_body +=" _thread.start_new_thread("+ currPluginSetting['functionname'] + ",(script, inputEvent))\n\n"
|
|
||||||
return fun_body
|
|
||||||
|
|
||||||
def buildPluginExec(self, currPluginSetting):
|
|
||||||
pluginname = currPluginSetting['pluginname']
|
|
||||||
if currPluginSetting['blockcall']:
|
|
||||||
pluginname = "blocking " + pluginname
|
|
||||||
fun_body = "global " + currPluginSetting['functionname']+"\n"
|
|
||||||
fun_body += "def " + currPluginSetting['functionname'] + "(script=None, inputEvent=None):\n"
|
|
||||||
if currPluginSetting['startnotify']:
|
|
||||||
fun_body +=" outputMessage('start " + pluginname + "')\n"
|
|
||||||
fun_body += " try:\n"
|
|
||||||
fun_body += " spec = importlib.util.spec_from_file_location(\"" + currPluginSetting['functionname'] + "\",\""+ currPluginSetting['file']+"\")\n"
|
|
||||||
fun_body += " "+currPluginSetting['functionname'] + "Module = importlib.util.module_from_spec(spec)\n"
|
|
||||||
fun_body += " spec.loader.exec_module(" + currPluginSetting['functionname'] + "Module)\n"
|
|
||||||
fun_body += " except:\n"
|
|
||||||
fun_body += " pass\n"
|
|
||||||
if currPluginSetting['error']:
|
|
||||||
fun_body += " outputMessage(\"Error while executing " + pluginname + "\")\n"
|
|
||||||
if currPluginSetting['stopnotify']:
|
|
||||||
fun_body +=" outputMessage('finish " + pluginname + "')\n"
|
|
||||||
fun_body += " return True\n\n"
|
|
||||||
fun_body += "global " + currPluginSetting['functionname']+"T\n"
|
|
||||||
fun_body +="def " + currPluginSetting['functionname'] + "T(script=None, inputEvent=None):\n"
|
|
||||||
fun_body +=" _thread.start_new_thread("+ currPluginSetting['functionname'] + ",(script, inputEvent))\n\n"
|
|
||||||
return fun_body
|
|
||||||
|
|
||||||
def getFunctionName(self, currPluginSetting):
|
|
||||||
currPluginSetting['functionname'] = ''
|
|
||||||
while currPluginSetting['functionname'] == '' or currPluginSetting['functionname'] + 'T' in globals() or currPluginSetting['functionname'] in globals():
|
|
||||||
currPluginSetting['functionname'] = self.id_generator()
|
|
||||||
return currPluginSetting
|
|
||||||
|
|
||||||
def load_plugins(self):
|
|
||||||
if not self.loaded:
|
|
||||||
self.plugin_list = glob.glob(self.plugin_repo+'*')
|
|
||||||
for currplugin in self.plugin_list:
|
|
||||||
currPluginSetting = self.initSettings()
|
|
||||||
currPluginSetting = self.getPluginSettings(currplugin, currPluginSetting)
|
|
||||||
|
|
||||||
if not currPluginSetting['valid']:
|
|
||||||
continue
|
|
||||||
|
|
||||||
currPluginSetting = self.getFunctionName(currPluginSetting)
|
|
||||||
|
|
||||||
if currPluginSetting['loadmodule']:
|
|
||||||
exec(self.buildPluginExec(currPluginSetting)) # load as python module
|
|
||||||
else:
|
|
||||||
exec(self.buildPluginSubprocess(currPluginSetting)) # run as subprocess
|
|
||||||
|
|
||||||
if currPluginSetting['blockcall']:
|
|
||||||
currPluginSetting['function'] = globals()[currPluginSetting['functionname']] # non threaded
|
|
||||||
else:
|
|
||||||
currPluginSetting['function'] = globals()[currPluginSetting['functionname']+"T"] # T = Threaded
|
|
||||||
|
|
||||||
|
|
||||||
if currPluginSetting['exec']: # exec on load if we want
|
|
||||||
currPluginSetting['function']()
|
|
||||||
|
|
||||||
if not currPluginSetting['key'] == '':
|
|
||||||
currPluginSetting = self.SetupShortcutAndHandle(currPluginSetting)
|
|
||||||
print(currPluginSetting)
|
|
||||||
self.plugin_list.append(currPluginSetting) # store in a list
|
|
||||||
self.loaded = True
|
|
9
src/cthulhu/plugins/SimplePluginSystem/plugin.info
Normal file
9
src/cthulhu/plugins/SimplePluginSystem/plugin.info
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
[Plugin]
|
||||||
|
Name = Simple Plugin System
|
||||||
|
Module = SimplePluginSystem
|
||||||
|
Description = Simple plugin system implementation for Cthulhu
|
||||||
|
Authors = Storm Dragon <storm_dragon@stormux.org>
|
||||||
|
Copyright = Copyright (c) 2025 Stormux
|
||||||
|
Website = https://git.stormux.org/storm/cthulhu
|
||||||
|
Version = 1.0
|
||||||
|
Category = System
|
343
src/cthulhu/plugins/SimplePluginSystem/plugin.py
Normal file
343
src/cthulhu/plugins/SimplePluginSystem/plugin.py
Normal file
@ -0,0 +1,343 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
#
|
||||||
|
# Copyright (c) 2024 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.
|
||||||
|
#
|
||||||
|
# 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.
|
||||||
|
#
|
||||||
|
|
||||||
|
"""Simple Plugin System for Cthulhu."""
|
||||||
|
|
||||||
|
import glob
|
||||||
|
import os
|
||||||
|
import importlib.util
|
||||||
|
import random
|
||||||
|
import string
|
||||||
|
import _thread
|
||||||
|
import logging
|
||||||
|
from subprocess import Popen, PIPE
|
||||||
|
|
||||||
|
from cthulhu.plugin import Plugin, cthulhu_hookimpl
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Global variables for API access
|
||||||
|
settings = None
|
||||||
|
speech = None
|
||||||
|
braille = None
|
||||||
|
input_event = None
|
||||||
|
|
||||||
|
def outputMessage(Message):
|
||||||
|
"""Output a message via speech and/or braille depending on settings."""
|
||||||
|
if (settings.enableSpeech):
|
||||||
|
speech.speak(Message)
|
||||||
|
if (settings.enableBraille):
|
||||||
|
braille.displayMessage(Message)
|
||||||
|
|
||||||
|
class SimplePluginSystem(Plugin):
|
||||||
|
"""Simple plugin system implementation for Cthulhu.
|
||||||
|
|
||||||
|
This plugin allows loading and managing simple script-based plugins
|
||||||
|
from a designated directory.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
"""Initialize the plugin system."""
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
logger.info("SimplePluginSystem plugin initialized")
|
||||||
|
self.plugin_list = []
|
||||||
|
self.loaded = False
|
||||||
|
self.plugin_repo = os.path.expanduser('~') + "/.local/share/cthulhu/simple-plugins-enabled/"
|
||||||
|
self._signal_handler_id = None
|
||||||
|
|
||||||
|
@cthulhu_hookimpl
|
||||||
|
def activate(self, plugin=None):
|
||||||
|
"""Activate the plugin system."""
|
||||||
|
# Skip if this activation call isn't for us
|
||||||
|
if plugin is not None and plugin is not self:
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info("Activating SimplePluginSystem plugin")
|
||||||
|
try:
|
||||||
|
global settings
|
||||||
|
global speech
|
||||||
|
global braille
|
||||||
|
global input_event
|
||||||
|
|
||||||
|
settings = self.app.getDynamicApiManager().getAPI('Settings')
|
||||||
|
speech = self.app.getDynamicApiManager().getAPI('Speech')
|
||||||
|
braille = self.app.getDynamicApiManager().getAPI('Braille')
|
||||||
|
input_event = self.app.getDynamicApiManager().getAPI('InputEvent')
|
||||||
|
|
||||||
|
if not self.loaded:
|
||||||
|
self.load_plugins()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error activating SimplePluginSystem plugin: {e}")
|
||||||
|
|
||||||
|
@cthulhu_hookimpl
|
||||||
|
def deactivate(self, plugin=None):
|
||||||
|
"""Deactivate the plugin system."""
|
||||||
|
# Skip if this deactivation call isn't for us
|
||||||
|
if plugin is not None and plugin is not self:
|
||||||
|
return
|
||||||
|
|
||||||
|
logger.info("Deactivating SimplePluginSystem plugin")
|
||||||
|
try:
|
||||||
|
# Remove all registered keybindings
|
||||||
|
for plugin in self.plugin_list:
|
||||||
|
self.unregisterShortcut(plugin['function'], plugin['shortcut'])
|
||||||
|
self.loaded = False
|
||||||
|
self.plugin_list = []
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error deactivating SimplePluginSystem plugin: {e}")
|
||||||
|
|
||||||
|
def SetupShortcutAndHandle(self, currPluginSetting):
|
||||||
|
"""Set up keyboard shortcuts for a plugin."""
|
||||||
|
shortcut = ''
|
||||||
|
# just the modifier
|
||||||
|
if not currPluginSetting['shiftkey'] and not currPluginSetting['ctrlkey'] and not currPluginSetting['altkey']:
|
||||||
|
shortcut = 'kb:cthulhu+' + currPluginSetting['key']
|
||||||
|
# cthulhu + alt
|
||||||
|
if not currPluginSetting['shiftkey'] and not currPluginSetting['ctrlkey'] and currPluginSetting['altkey']:
|
||||||
|
shortcut = 'kb:cthulhu+alt+' + currPluginSetting['key']
|
||||||
|
# cthulhu + CTRL
|
||||||
|
if not currPluginSetting['shiftkey'] and currPluginSetting['ctrlkey'] and not currPluginSetting['altkey']:
|
||||||
|
shortcut = 'kb:cthulhu+control+' + currPluginSetting['key']
|
||||||
|
# cthulhu + alt + CTRL
|
||||||
|
if not currPluginSetting['shiftkey'] and currPluginSetting['ctrlkey'] and currPluginSetting['altkey']:
|
||||||
|
shortcut = 'kb:cthulhu+alt+control+ ' + currPluginSetting['key']
|
||||||
|
# cthulhu + shift
|
||||||
|
if currPluginSetting['shiftkey'] and not currPluginSetting['ctrlkey'] and not currPluginSetting['altkey']:
|
||||||
|
shortcut = 'kb:cthulhu+shift+' + currPluginSetting['key']
|
||||||
|
# alt + shift
|
||||||
|
if currPluginSetting['shiftkey'] and not currPluginSetting['ctrlkey'] and currPluginSetting['altkey']:
|
||||||
|
shortcut = 'kb:alt+shift+' + currPluginSetting['key']
|
||||||
|
if shortcut != '':
|
||||||
|
logger.debug(f"Registering shortcut: {shortcut}")
|
||||||
|
currPluginSetting['shortcut'] = shortcut
|
||||||
|
self.registerGestureByString(currPluginSetting['function'], _(currPluginSetting['pluginname']), shortcut)
|
||||||
|
return currPluginSetting
|
||||||
|
|
||||||
|
def id_generator(self, size=7, chars=string.ascii_letters):
|
||||||
|
"""Generate a random ID string."""
|
||||||
|
return ''.join(random.choice(chars) for _ in range(size))
|
||||||
|
|
||||||
|
def initSettings(self):
|
||||||
|
"""Initialize default settings for a plugin."""
|
||||||
|
currPluginSetting = {
|
||||||
|
'pluginname': '',
|
||||||
|
'functionname': '',
|
||||||
|
'key': '',
|
||||||
|
'shiftkey': False,
|
||||||
|
'ctrlkey': False,
|
||||||
|
'altkey': False,
|
||||||
|
'startnotify': False,
|
||||||
|
'stopnotify': False,
|
||||||
|
'blockcall': False,
|
||||||
|
'error': False,
|
||||||
|
'exec': False,
|
||||||
|
'parameters': '',
|
||||||
|
'function': None,
|
||||||
|
'inputeventhandler': None,
|
||||||
|
'valid': False,
|
||||||
|
'supressoutput': False,
|
||||||
|
'shortcut': ''
|
||||||
|
}
|
||||||
|
return currPluginSetting
|
||||||
|
|
||||||
|
def getPluginSettings(self, filepath, currPluginSetting):
|
||||||
|
"""Parse plugin settings from filename and content."""
|
||||||
|
try:
|
||||||
|
currPluginSetting['file'] = filepath
|
||||||
|
fileName, fileExtension = os.path.splitext(filepath)
|
||||||
|
if (fileExtension and (fileExtension != '')): # if there is an extension
|
||||||
|
currPluginSetting['loadable'] = (fileExtension.lower() == '.py') # only python is loadable
|
||||||
|
filename = os.path.basename(filepath) # filename
|
||||||
|
filename = os.path.splitext(filename)[0] # remove extension if we have one
|
||||||
|
# remove pluginname seperated by __-__
|
||||||
|
filenamehelper = filename.split('__-__')
|
||||||
|
filename = filenamehelper[len(filenamehelper) - 1]
|
||||||
|
currPluginSetting['permission'] = os.access(filepath, os.X_OK)
|
||||||
|
currPluginSetting['pluginname'] = 'NoNameAvailable'
|
||||||
|
if len(filenamehelper) == 2:
|
||||||
|
currPluginSetting['pluginname'] = filenamehelper[0]
|
||||||
|
# now get shortcuts seperated by __+__
|
||||||
|
filenamehelper = filename.split('__+__')
|
||||||
|
if len([y for y in filenamehelper if 'parameters_' in y.lower()]) == 1 and\
|
||||||
|
len([y for y in filenamehelper if 'parameters_' in y.lower()][0]) > 11:
|
||||||
|
currPluginSetting['parameters'] = [y for y in filenamehelper if 'parameters_' in y.lower()][0][11:]
|
||||||
|
if len([y for y in filenamehelper if 'key_' in y.lower()]) == 1 and\
|
||||||
|
len([y for y in filenamehelper if 'key_' in y.lower()][0]) > 4:
|
||||||
|
currPluginSetting['key'] = [y for y in filenamehelper if 'key_' in y.lower()][0][4]
|
||||||
|
if currPluginSetting['key'] == '':
|
||||||
|
settcurrPluginSetting = 'shift' in map(str.lower, filenamehelper)
|
||||||
|
currPluginSetting['ctrlkey'] = 'control' in map(str.lower, filenamehelper)
|
||||||
|
currPluginSetting['altkey'] = 'alt' in map(str.lower, filenamehelper)
|
||||||
|
currPluginSetting['startnotify'] = 'startnotify' in map(str.lower, filenamehelper)
|
||||||
|
currPluginSetting['stopnotify'] = 'stopnotify' in map(str.lower, filenamehelper)
|
||||||
|
currPluginSetting['blockcall'] = 'blockcall' in map(str.lower, filenamehelper)
|
||||||
|
currPluginSetting['error'] = 'error' in map(str.lower, filenamehelper)
|
||||||
|
currPluginSetting['supressoutput'] = 'supressoutput' in map(str.lower, filenamehelper)
|
||||||
|
currPluginSetting['exec'] = 'exec' in map(str.lower, filenamehelper)
|
||||||
|
currPluginSetting['loadmodule'] = 'loadmodule' in map(str.lower, filenamehelper)
|
||||||
|
currPluginSetting = self.readSettingsFromPlugin(currPluginSetting)
|
||||||
|
if not currPluginSetting['loadmodule']:
|
||||||
|
if not currPluginSetting['permission']: # subprocessing only works with exec permission
|
||||||
|
return self.initSettings()
|
||||||
|
if currPluginSetting['loadmodule'] and not currPluginSetting['loadable']: # sorry.. its not loadable only .py is loadable
|
||||||
|
return self.initSettings()
|
||||||
|
if (len(currPluginSetting['key']) > 1): # no shortcut
|
||||||
|
if not currPluginSetting['exec']: # and no exec -> the plugin make no sense because it isnt hooked anywhere
|
||||||
|
return self.initSettings() # so not load it (sets valid = False)
|
||||||
|
else:
|
||||||
|
currPluginSetting['key'] = '' # there is a strange key, but exec? ignore the key..
|
||||||
|
currPluginSetting['valid'] = True # we could load everything
|
||||||
|
return currPluginSetting
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error getting plugin settings: {e}")
|
||||||
|
return self.initSettings()
|
||||||
|
|
||||||
|
def readSettingsFromPlugin(self, currPluginSetting):
|
||||||
|
"""Read settings from plugin file content."""
|
||||||
|
if not os.access(currPluginSetting['file'], os.R_OK):
|
||||||
|
return currPluginSetting
|
||||||
|
fileName, fileExtension = os.path.splitext(currPluginSetting['file'])
|
||||||
|
if (fileExtension and (fileExtension != '')): # if there is an extension
|
||||||
|
if (fileExtension.lower() != '.py') and \
|
||||||
|
(fileExtension.lower() != '.sh'):
|
||||||
|
return currPluginSetting
|
||||||
|
else:
|
||||||
|
return currPluginSetting
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(currPluginSetting['file'], "r") as pluginFile:
|
||||||
|
for line in pluginFile:
|
||||||
|
currPluginSetting['shiftkey'] = ('sopsproperty:shift' in line.lower().replace(" ", "")) or currPluginSetting['shiftkey']
|
||||||
|
currPluginSetting['ctrlkey'] = ('sopsproperty:control' in line.lower().replace(" ", "")) or currPluginSetting['ctrlkey']
|
||||||
|
currPluginSetting['altkey'] = ('sopsproperty:alt' in line.lower().replace(" ", "")) or currPluginSetting['altkey']
|
||||||
|
currPluginSetting['startnotify'] = ('sopsproperty:startnotify' in line.lower().replace(" ", "")) or currPluginSetting['startnotify']
|
||||||
|
currPluginSetting['stopnotify'] = ('sopsproperty:stopnotify' in line.lower().replace(" ", "")) or currPluginSetting['stopnotify']
|
||||||
|
currPluginSetting['blockcall'] = ('sopsproperty:blockcall' in line.lower().replace(" ", "")) or currPluginSetting['blockcall']
|
||||||
|
currPluginSetting['error'] = ('sopsproperty:error' in line.lower().replace(" ", "")) or currPluginSetting['error']
|
||||||
|
currPluginSetting['supressoutput'] = ('sopsproperty:supressoutput' in line.lower().replace(" ", "")) or currPluginSetting['supressoutput']
|
||||||
|
currPluginSetting['exec'] = ('sopsproperty:exec' in line.lower().replace(" ", "")) or currPluginSetting['exec']
|
||||||
|
currPluginSetting['loadmodule'] = ('sopsproperty:loadmodule' in line.lower().replace(" ", "")) or currPluginSetting['loadmodule']
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error reading plugin file: {e}")
|
||||||
|
|
||||||
|
return currPluginSetting
|
||||||
|
|
||||||
|
def buildPluginSubprocess(self, currPluginSetting):
|
||||||
|
"""Build a function to execute a plugin as a subprocess."""
|
||||||
|
currplugin = "\'\"" + currPluginSetting['file'] + "\" " + currPluginSetting['parameters'] + "\'"
|
||||||
|
pluginname = currPluginSetting['pluginname']
|
||||||
|
if currPluginSetting['blockcall']:
|
||||||
|
pluginname = "blocking " + pluginname
|
||||||
|
fun_body = "global " + currPluginSetting['functionname'] + "\n"
|
||||||
|
fun_body += "def " + currPluginSetting['functionname'] + "(script=None, inputEvent=None):\n"
|
||||||
|
if currPluginSetting['startnotify']:
|
||||||
|
fun_body += " outputMessage('start " + pluginname + "')\n"
|
||||||
|
fun_body += " p = Popen(" + currplugin + ", stdout=PIPE, stderr=PIPE, shell=True)\n"
|
||||||
|
fun_body += " stdout, stderr = p.communicate()\n"
|
||||||
|
fun_body += " message = ''\n"
|
||||||
|
fun_body += " if not " + str(currPluginSetting['supressoutput']) + " and stdout:\n"
|
||||||
|
fun_body += " message += str(stdout, \"utf-8\")\n"
|
||||||
|
fun_body += " if " + str(currPluginSetting['error']) + " and stderr:\n"
|
||||||
|
fun_body += " message += ' error: ' + str(stderr, \"utf-8\")\n"
|
||||||
|
fun_body += " outputMessage(message)\n"
|
||||||
|
if currPluginSetting['stopnotify']:
|
||||||
|
fun_body += " outputMessage('finish " + pluginname + "')\n"
|
||||||
|
fun_body += " return True\n\n"
|
||||||
|
fun_body += "global " + currPluginSetting['functionname'] + "T\n"
|
||||||
|
fun_body += "def " + currPluginSetting['functionname'] + "T(script=None, inputEvent=None):\n"
|
||||||
|
fun_body += " _thread.start_new_thread(" + currPluginSetting['functionname'] + ",(script, inputEvent))\n\n"
|
||||||
|
return fun_body
|
||||||
|
|
||||||
|
def buildPluginExec(self, currPluginSetting):
|
||||||
|
"""Build a function to execute a plugin as a Python module."""
|
||||||
|
pluginname = currPluginSetting['pluginname']
|
||||||
|
if currPluginSetting['blockcall']:
|
||||||
|
pluginname = "blocking " + pluginname
|
||||||
|
fun_body = "global " + currPluginSetting['functionname'] + "\n"
|
||||||
|
fun_body += "def " + currPluginSetting['functionname'] + "(script=None, inputEvent=None):\n"
|
||||||
|
if currPluginSetting['startnotify']:
|
||||||
|
fun_body += " outputMessage('start " + pluginname + "')\n"
|
||||||
|
fun_body += " try:\n"
|
||||||
|
fun_body += " spec = importlib.util.spec_from_file_location(\"" + currPluginSetting['functionname'] + "\",\"" + currPluginSetting['file'] + "\")\n"
|
||||||
|
fun_body += " " + currPluginSetting['functionname'] + "Module = importlib.util.module_from_spec(spec)\n"
|
||||||
|
fun_body += " spec.loader.exec_module(" + currPluginSetting['functionname'] + "Module)\n"
|
||||||
|
fun_body += " except Exception as e:\n"
|
||||||
|
fun_body += " logger.error(f\"Error executing plugin {pluginname}: {e}\")\n"
|
||||||
|
if currPluginSetting['error']:
|
||||||
|
fun_body += " outputMessage(\"Error while executing " + pluginname + "\")\n"
|
||||||
|
if currPluginSetting['stopnotify']:
|
||||||
|
fun_body += " outputMessage('finish " + pluginname + "')\n"
|
||||||
|
fun_body += " return True\n\n"
|
||||||
|
fun_body += "global " + currPluginSetting['functionname'] + "T\n"
|
||||||
|
fun_body += "def " + currPluginSetting['functionname'] + "T(script=None, inputEvent=None):\n"
|
||||||
|
fun_body += " _thread.start_new_thread(" + currPluginSetting['functionname'] + ",(script, inputEvent))\n\n"
|
||||||
|
return fun_body
|
||||||
|
|
||||||
|
def getFunctionName(self, currPluginSetting):
|
||||||
|
"""Generate a unique function name for a plugin."""
|
||||||
|
currPluginSetting['functionname'] = ''
|
||||||
|
while currPluginSetting['functionname'] == '' or currPluginSetting['functionname'] + 'T' in globals() or currPluginSetting['functionname'] in globals():
|
||||||
|
currPluginSetting['functionname'] = self.id_generator()
|
||||||
|
return currPluginSetting
|
||||||
|
|
||||||
|
def load_plugins(self):
|
||||||
|
"""Load and setup all plugins in the plugin repository."""
|
||||||
|
if not self.loaded:
|
||||||
|
try:
|
||||||
|
logger.info(f"Loading plugins from {self.plugin_repo}")
|
||||||
|
self.plugin_list = glob.glob(self.plugin_repo + '*')
|
||||||
|
|
||||||
|
for currplugin in self.plugin_list:
|
||||||
|
try:
|
||||||
|
currPluginSetting = self.initSettings()
|
||||||
|
currPluginSetting = self.getPluginSettings(currplugin, currPluginSetting)
|
||||||
|
|
||||||
|
if not currPluginSetting['valid']:
|
||||||
|
logger.debug(f"Skipping invalid plugin: {currplugin}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
currPluginSetting = self.getFunctionName(currPluginSetting)
|
||||||
|
|
||||||
|
if currPluginSetting['loadmodule']:
|
||||||
|
exec(self.buildPluginExec(currPluginSetting)) # load as python module
|
||||||
|
else:
|
||||||
|
exec(self.buildPluginSubprocess(currPluginSetting)) # run as subprocess
|
||||||
|
|
||||||
|
if currPluginSetting['blockcall']:
|
||||||
|
currPluginSetting['function'] = globals()[currPluginSetting['functionname']] # non threaded
|
||||||
|
else:
|
||||||
|
currPluginSetting['function'] = globals()[currPluginSetting['functionname'] + "T"] # T = Threaded
|
||||||
|
|
||||||
|
if currPluginSetting['exec']: # exec on load if we want
|
||||||
|
currPluginSetting['function']()
|
||||||
|
|
||||||
|
if not currPluginSetting['key'] == '':
|
||||||
|
currPluginSetting = self.SetupShortcutAndHandle(currPluginSetting)
|
||||||
|
|
||||||
|
logger.debug(f"Loaded plugin: {currPluginSetting['pluginname']}")
|
||||||
|
self.plugin_list.append(currPluginSetting) # store in a list
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error loading plugin {currplugin}: {e}")
|
||||||
|
|
||||||
|
self.loaded = True
|
||||||
|
logger.info(f"Loaded {len(self.plugin_list)} plugins")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Error in load_plugins: {e}")
|
@ -1,7 +0,0 @@
|
|||||||
cthulhu_python_PYTHON = \
|
|
||||||
__init__.py \
|
|
||||||
Time.plugin \
|
|
||||||
Time.py
|
|
||||||
|
|
||||||
cthulhu_pythondir=$(pkgpythondir)/plugins/Time
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
|||||||
[Plugin]
|
|
||||||
Module=Time
|
|
||||||
Loader=python3
|
|
||||||
Name=Time
|
|
||||||
Description=Present current time
|
|
||||||
Authors=Chrys chrys@linux-a11y.org
|
|
@ -1,60 +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
|
|
||||||
|
|
||||||
import gi, time
|
|
||||||
gi.require_version('Peas', '1.0')
|
|
||||||
from gi.repository import GObject
|
|
||||||
from gi.repository import Peas
|
|
||||||
|
|
||||||
from cthulhu import plugin
|
|
||||||
|
|
||||||
class Time(GObject.Object, Peas.Activatable, plugin.Plugin):
|
|
||||||
#__gtype_name__ = 'Time'
|
|
||||||
|
|
||||||
object = GObject.Property(type=GObject.Object)
|
|
||||||
def __init__(self):
|
|
||||||
plugin.Plugin.__init__(self)
|
|
||||||
def do_activate(self):
|
|
||||||
API = self.object
|
|
||||||
self.connectSignal("setup-inputeventhandlers-completed", self.setupCompatBinding)
|
|
||||||
def setupCompatBinding(self, app):
|
|
||||||
cmdnames = app.getDynamicApiManager().getAPI('Cmdnames')
|
|
||||||
inputEventHandlers = app.getDynamicApiManager().getAPI('inputEventHandlers')
|
|
||||||
inputEventHandlers['presentTimeHandler'] = app.getAPIHelper().createInputEventHandler(self.presentTime, cmdnames.PRESENT_CURRENT_TIME)
|
|
||||||
def do_deactivate(self):
|
|
||||||
API = self.object
|
|
||||||
inputEventHandlers = API.app.getDynamicApiManager().getAPI('inputEventHandlers')
|
|
||||||
del inputEventHandlers['presentTimeHandler']
|
|
||||||
def do_update_state(self):
|
|
||||||
API = self.object
|
|
||||||
def presentTime(self, script=None, inputEvent=None):
|
|
||||||
""" Presents the current time. """
|
|
||||||
API = self.object
|
|
||||||
settings_manager = API.app.getDynamicApiManager().getAPI('SettingsManager')
|
|
||||||
_settingsManager = settings_manager.getManager()
|
|
||||||
timeFormat = _settingsManager.getSetting('presentTimeFormat')
|
|
||||||
message = time.strftime(timeFormat, time.localtime())
|
|
||||||
API.app.getDynamicApiManager().getAPI('CthulhuState').activeScript.presentMessage(message, resetStyles=False)
|
|
||||||
return True
|
|
@ -1,25 +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
|
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user