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
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
pyxdg
setproctitle
sqlmodel
setproctitle==1.2.2
pyxdg==0.27
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()
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():
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. '''
def setup_controller_data(self) -> None:
self.window = settings_manager.get_main_window()
self.builder = BuilderWrapper()
self.window = settings_manager.get_main_window()
self.builder = BuilderWrapper()
self.plugins_controller = PluginsController()
self.base_container = None
self.was_midified_key = False
self.ctrl_down = False
self.shift_down = False
self.alt_down = False
self.base_container = None
self.was_midified_key = False
self.ctrl_down = False
self.shift_down = False
self.alt_down = False
self._load_glade_file()
self.plugins = PluginsController()
def collect_files_dirs(self, args, unknownargs):
files = []
@ -96,4 +97,4 @@ class BaseControllerData:
proc = subprocess.Popen(command, stdin = subprocess.PIPE)
proc.stdin.write(data.encode(encoding))
proc.stdin.close()
retcode = proc.wait()
retcode = proc.wait()

View File

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

View File

@ -193,4 +193,4 @@ class SettingsManager(StartCheckMixin, Singleton):
def save_settings(self):
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:
path: str = None
name: str = None
author: str = None
version: str = None
support: str = None
requests:{} = None
reference: type = None
path: str = None
name: str = None
author: str = None
version: str = None
support: str = None
requests:{} = None
reference: type = None
pre_launch: bool = False
class ManifestProcessor:
@ -46,23 +47,25 @@ class ManifestProcessor:
plugin.support = self._manifest["support"]
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
def get_loading_data(self):
loading_data = {}
requests = self._plugin.requests
keys = requests.keys()
if "pass_events" in keys:
if "pass_events" in requests:
if requests["pass_events"] in ["true"]:
loading_data["pass_events"] = True
if "bind_keys" in keys:
if isinstance(requests["bind_keys"], list):
loading_data["bind_keys"] = requests["bind_keys"]
if "pass_ui_objects" in keys:
if "pass_ui_objects" in requests:
if isinstance(requests["pass_ui_objects"], list):
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

View File

@ -10,6 +10,7 @@ from os.path import isdir
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from gi.repository import GLib
from gi.repository import Gio
# Application imports
@ -35,11 +36,23 @@ class PluginsController:
self._plugins_dir_watcher = None
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.load_plugins()
def _set_plugins_watcher(self) -> None:
self._plugins_dir_watcher = Gio.File.new_for_path(self._plugins_path) \
@ -52,21 +65,47 @@ class PluginsController:
Gio.FileMonitorEvent.MOVED_OUT]:
self.reload_plugins(file)
def load_plugins(self, file: str = None) -> None:
logger.info(f"Loading plugins...")
def pre_launch_plugins(self) -> None:
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()
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)]:
try:
target = join(path, "plugin.py")
manifest = ManifestProcessor(path, self._builder)
for key in plugin_manifests:
target_manifest = plugin_manifests[key]
path, folder, manifest = target_manifest["path"], target_manifest["folder"], target_manifest["manifest"]
try:
target = join(path, "plugin.py")
if not os.path.exists(target):
raise InvalidPluginException("Invalid Plugin Structure: Plugin doesn't have 'plugin.py'. Aboarting load...")
plugin, loading_data = manifest.get_loading_data()
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:
logger.info(f"Malformed Plugin: Not loading -->: '{folder}' !")
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