Compare commits

...

11 Commits

Author SHA1 Message Date
Storm Dragon
0cadb12c00 Merge branch 'sps' of git.stormux.org:storm/cthulhu into sps 2024-10-23 19:16:07 -04:00
Storm Dragon
2f6fde6896 Removed add SimplePluginSystem as it is now done. 2024-10-23 19:15:45 -04:00
Chrys
89467b6988 remove unused variable 2024-10-24 00:58:11 +02:00
Chrys
17d2773344 make shortcuts work, migrate to plugin api 2024-10-24 00:55:22 +02:00
Chrys
0abe30791d fix all syntax issues in SOPS 2024-10-24 00:08:25 +02:00
Chrys
087dcc3ab2 add SimplePlugin to settings.py 2024-10-23 19:06:12 +02:00
Chrys
4eaa4fae86 add SimplePlugin to configure.ac 2024-10-23 19:04:22 +02:00
Chrys
ba0169b016 add SimplePlugin to make 2024-10-23 19:03:06 +02:00
Storm Dragon
7605e7d60f Found some stuff I missed on the initial attempt to get this working. 2024-10-23 09:37:42 -04:00
Storm Dragon
9562c08130 fixed author lines, I think. 2024-10-23 08:16:37 -04:00
Storm Dragon
8a9bbefeac Initial attempt at chaning SOPS into a plugin for Cthulhu. 2024-10-23 07:58:44 -04:00
8 changed files with 287 additions and 3 deletions

1
TODO
View File

@ -1,4 +1,3 @@
- Add Simple Cthulhu Plugin System as native code for Cthulhu
- Merge in sleep mode.
- Add in ocrdesktop code so that Cthulhu has native ocr support.
- Get rid of build systems. My hope is git clone and run so long as requirements are satisfied.

View File

@ -130,6 +130,7 @@ src/cthulhu/plugins/Date/Makefile
src/cthulhu/plugins/Time/Makefile
src/cthulhu/plugins/MouseReview/Makefile
src/cthulhu/plugins/ClassicPreferences/Makefile
src/cthulhu/plugins/SimplePluginSystem/Makefile
src/cthulhu/backends/Makefile
src/cthulhu/cthulhu_bin.py
src/cthulhu/cthulhu_i18n.py

View File

@ -1,4 +1,4 @@
SUBDIRS = Clipboard HelloWorld SelfVoice Time MouseReview Date ByeCthulhu HelloCthulhu PluginManager CapsLockHack ClassicPreferences
SUBDIRS = Clipboard HelloWorld SelfVoice Time MouseReview Date ByeCthulhu HelloCthulhu PluginManager CapsLockHack ClassicPreferences SimplePluginSystem
cthulhu_pythondir=$(pkgpythondir)/plugins

View File

@ -0,0 +1,7 @@
cthulhu_python_PYTHON = \
__init__.py \
SimplePluginSystem.plugin \
SimplePluginSystem.py
cthulhu_pythondir=$(pkgpythondir)/plugins/SimplePluginSystem

View File

@ -0,0 +1,11 @@
[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

View File

@ -0,0 +1,266 @@
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

View File

@ -407,4 +407,4 @@ presentChatRoomLast = False
presentLiveRegionFromInactiveTab = False
# Plugins
activePlugins = ['Clipboard', 'MouseReview', 'Date', 'ByeCthulhu', 'Time', 'HelloCthulhu', 'HelloWorld', 'SelfVoice', 'PluginManager', 'ClassicPreferences']
activePlugins = ['Clipboard', 'MouseReview', 'Date', 'ByeCthulhu', 'Time', 'HelloCthulhu', 'HelloWorld', 'SelfVoice', 'PluginManager', 'ClassicPreferences', 'SimplePluginSystem']