Convert simple plugin plugin to new plugin format. Hmm, gotta get in a couple more... plugin plugin plugin! lol
This commit is contained in:
		@@ -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
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
cthulhu_python_PYTHON = \
 | 
			
		||||
        __init__.py \
 | 
			
		||||
        SimplePluginSystem.plugin \
 | 
			
		||||
        SimplePluginSystem.py
 | 
			
		||||
        plugin.info \
 | 
			
		||||
        plugin.py
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user