extending plugins to load pre or post app start

This commit is contained in:
itdominator 2024-06-29 21:24:57 -05:00
parent cc5966dab2
commit 62debf9ece
10 changed files with 117 additions and 40 deletions

View File

@ -1,2 +1,31 @@
### Note ### Note
Copy the example and rename it to your desired name. The Main class and passed in arguments are required. You don't necessarily need to use the passed in socket_id or event_system. Copy the example and rename it to your desired name. Plugins define a ui target slot with the 'ui_target' requests data but don't have to if not directly interacted with.
Plugins must have a run method defined; though, you do not need to necessarily do anything within it. The run method implies that the passed in event system or other data is ready for the plugin to use.
### Manifest Example (All are required even if empty.)
```
class Manifest:
name: str = "Example Plugin"
author: str = "John Doe"
version: str = "0.0.1"
support: str = ""
requests: {} = {
'pass_ui_objects': ["plugin_control_list"],
'pass_events': "true",
'bind_keys': []
}
pre_launch: bool = False
```
### Requests
```
requests: {} = {
'pass_events': "true", # If empty or not present will be ignored.
"pass_ui_objects": [""], # Request reference to a UI component. Will be passed back as array to plugin.
'bind_keys': [f"{name}||send_message:<Control>f"],
f"{name}||do_save:<Control>s"] # Bind keys with method and key pare using list. Must pass "name" like shown with delimiter to its right.
}
```

View File

@ -1,5 +1,7 @@
PyGObject PyGObject==3.40.1
pygobject-stubs --no-cache-dir --config-settings=config=Gtk3,Gdk3,Soup2 pygobject-stubs --no-cache-dir --config-settings=config=Gtk3,Gdk3,Soup2
pyxdg setproctitle==1.2.2
setproctitle pyxdg==0.27
sqlmodel psutil==5.8.0
pycryptodome==3.20.0
sqlmodel==0.0.19

View File

@ -28,7 +28,10 @@ class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerData):
self._load_controllers() self._load_controllers()
if args.no_plugins == "false": if args.no_plugins == "false":
self.plugins.launch_plugins() self.plugins_controller.pre_launch_plugins()
if args.no_plugins == "false":
self.plugins_controller.post_launch_plugins()
for file in settings_manager.get_starting_files(): for file in settings_manager.get_starting_files():
event_system.emit("post-file-to-ipc", file) event_system.emit("post-file-to-ipc", file)

View File

@ -15,17 +15,18 @@ class BaseControllerData:
''' BaseControllerData contains most of the state of the app at ay given time. It also has some support methods. ''' ''' BaseControllerData contains most of the state of the app at ay given time. It also has some support methods. '''
def setup_controller_data(self) -> None: def setup_controller_data(self) -> None:
self.window = settings_manager.get_main_window() self.window = settings_manager.get_main_window()
self.builder = BuilderWrapper() self.builder = BuilderWrapper()
self.plugins_controller = PluginsController()
self.base_container = None self.base_container = None
self.was_midified_key = False self.was_midified_key = False
self.ctrl_down = False self.ctrl_down = False
self.shift_down = False self.shift_down = False
self.alt_down = False self.alt_down = False
self._load_glade_file() self._load_glade_file()
self.plugins = PluginsController()
def collect_files_dirs(self, args, unknownargs): def collect_files_dirs(self, args, unknownargs):
files = [] files = []
@ -96,4 +97,4 @@ class BaseControllerData:
proc = subprocess.Popen(command, stdin = subprocess.PIPE) proc = subprocess.Popen(command, stdin = subprocess.PIPE)
proc.stdin.write(data.encode(encoding)) proc.stdin.write(data.encode(encoding))
proc.stdin.close() proc.stdin.close()
retcode = proc.wait() retcode = proc.wait()

View File

@ -135,4 +135,4 @@ class Window(Gtk.ApplicationWindow):
Gtk.main_quit() Gtk.main_quit()
def main(self): def main(self):
Gtk.main() Gtk.main()

View File

@ -193,4 +193,4 @@ class SettingsManager(StartCheckMixin, Singleton):
def save_settings(self): def save_settings(self):
with open(self._CONFIG_FILE, 'w') as outfile: with open(self._CONFIG_FILE, 'w') as outfile:
json.dump(self.settings.as_dict(), outfile, separators=(',', ':'), indent=4) json.dump(self.settings.as_dict(), outfile, separators=(',', ':'), indent=4)

View File

@ -15,13 +15,14 @@ class ManifestProcessor(Exception):
class Plugin: class Plugin:
path: str = None path: str = None
name: str = None name: str = None
author: str = None author: str = None
version: str = None version: str = None
support: str = None support: str = None
requests:{} = None requests:{} = None
reference: type = None reference: type = None
pre_launch: bool = False
class ManifestProcessor: class ManifestProcessor:
@ -46,23 +47,25 @@ class ManifestProcessor:
plugin.support = self._manifest["support"] plugin.support = self._manifest["support"]
plugin.requests = self._manifest["requests"] plugin.requests = self._manifest["requests"]
if "pre_launch" in self._manifest.keys():
plugin.pre_launch = True if self._manifest["pre_launch"] == "true" else False
return plugin return plugin
def get_loading_data(self): def get_loading_data(self):
loading_data = {} loading_data = {}
requests = self._plugin.requests requests = self._plugin.requests
keys = requests.keys()
if "pass_events" in keys: if "pass_events" in requests:
if requests["pass_events"] in ["true"]: if requests["pass_events"] in ["true"]:
loading_data["pass_events"] = True loading_data["pass_events"] = True
if "bind_keys" in keys: if "pass_ui_objects" in requests:
if isinstance(requests["bind_keys"], list):
loading_data["bind_keys"] = requests["bind_keys"]
if "pass_ui_objects" in keys:
if isinstance(requests["pass_ui_objects"], list): if isinstance(requests["pass_ui_objects"], list):
loading_data["pass_ui_objects"] = [ self._builder.get_object(obj) for obj in requests["pass_ui_objects"] ] loading_data["pass_ui_objects"] = [ self._builder.get_object(obj) for obj in requests["pass_ui_objects"] ]
if "bind_keys" in requests:
if isinstance(requests["bind_keys"], list):
loading_data["bind_keys"] = requests["bind_keys"]
return self._plugin, loading_data return self._plugin, loading_data

View File

@ -10,6 +10,7 @@ from os.path import isdir
import gi import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
from gi.repository import GLib
from gi.repository import Gio from gi.repository import Gio
# Application imports # Application imports
@ -35,11 +36,23 @@ class PluginsController:
self._plugins_dir_watcher = None self._plugins_dir_watcher = None
self._plugin_collection = [] self._plugin_collection = []
self._plugin_manifests = {}
self._load_manifests()
def launch_plugins(self) -> None: def _load_manifests(self):
logger.info(f"Loading manifests...")
for path, folder in [[join(self._plugins_path, item), item] if os.path.isdir(join(self._plugins_path, item)) else None for item in os.listdir(self._plugins_path)]:
manifest = ManifestProcessor(path, self._builder)
self._plugin_manifests[path] = {
"path": path,
"folder": folder,
"manifest": manifest
}
self._set_plugins_watcher() self._set_plugins_watcher()
self.load_plugins()
def _set_plugins_watcher(self) -> None: def _set_plugins_watcher(self) -> None:
self._plugins_dir_watcher = Gio.File.new_for_path(self._plugins_path) \ self._plugins_dir_watcher = Gio.File.new_for_path(self._plugins_path) \
@ -52,21 +65,47 @@ class PluginsController:
Gio.FileMonitorEvent.MOVED_OUT]: Gio.FileMonitorEvent.MOVED_OUT]:
self.reload_plugins(file) self.reload_plugins(file)
def load_plugins(self, file: str = None) -> None: def pre_launch_plugins(self) -> None:
logger.info(f"Loading plugins...") logger.info(f"Loading pre-launch plugins...")
plugin_manifests: {} = {}
for key in self._plugin_manifests:
target_manifest = self._plugin_manifests[key]["manifest"]
if target_manifest.is_pre_launch():
plugin_manifests[key] = self._plugin_manifests[key]
self._load_plugins(plugin_manifests, is_pre_launch = True)
def post_launch_plugins(self) -> None:
logger.info(f"Loading post-launch plugins...")
plugin_manifests: {} = {}
for key in self._plugin_manifests:
target_manifest = self._plugin_manifests[key]["manifest"]
if not target_manifest.is_pre_launch():
plugin_manifests[key] = self._plugin_manifests[key]
self._load_plugins(plugin_manifests)
def _load_plugins(self, plugin_manifests: {} = {}, is_pre_launch: bool = False) -> None:
parent_path = os.getcwd() parent_path = os.getcwd()
for path, folder in [[join(self._plugins_path, item), item] if os.path.isdir(join(self._plugins_path, item)) else None for item in os.listdir(self._plugins_path)]: for key in plugin_manifests:
try: target_manifest = plugin_manifests[key]
target = join(path, "plugin.py") path, folder, manifest = target_manifest["path"], target_manifest["folder"], target_manifest["manifest"]
manifest = ManifestProcessor(path, self._builder)
try:
target = join(path, "plugin.py")
if not os.path.exists(target): if not os.path.exists(target):
raise InvalidPluginException("Invalid Plugin Structure: Plugin doesn't have 'plugin.py'. Aboarting load...") raise InvalidPluginException("Invalid Plugin Structure: Plugin doesn't have 'plugin.py'. Aboarting load...")
plugin, loading_data = manifest.get_loading_data() plugin, loading_data = manifest.get_loading_data()
module = self.load_plugin_module(path, folder, target) module = self.load_plugin_module(path, folder, target)
self.execute_plugin(module, plugin, loading_data)
if is_pre_launch:
self.execute_plugin(module, plugin, loading_data)
else:
GLib.idle_add(self.execute_plugin, *(module, plugin, loading_data))
except Exception as e: except Exception as e:
logger.info(f"Malformed Plugin: Not loading -->: '{folder}' !") logger.info(f"Malformed Plugin: Not loading -->: '{folder}' !")
logger.debug("Trace: ", traceback.print_exc()) logger.debug("Trace: ", traceback.print_exc())

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB