Restructured plugin system and examples

This commit is contained in:
itdominator 2022-07-06 23:19:41 -05:00
parent 111c535876
commit 8f64066049
11 changed files with 213 additions and 90 deletions

58
plugins/README.md Normal file
View File

@ -0,0 +1,58 @@
### Note
Copy the example and rename it to your desired name. Plugins define a ui target slot with the 'ui_target' permissions 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
```
class Manifest:
path: str = os.path.dirname(os.path.realpath(__file__))
name: str = "Example Plugin"
author: str = "John Doe"
version: str = "0.0.1"
support: str = ""
permissions: {} = {
'ui_target': "plugin_control_list",
'pass_fm_events': "true"
}
```
### Permissions
```
permissions: {} = {
'ui_target': "plugin_control_list",
'ui_target_id': "<some other Gtk Glade ID>" # Only needed if using "other" in "ui_target". See below for predefined "ui_target" options...
'pass_fm_events': "true" # If empty or undefined will be ignored.
}
```
UI Targets:
<ul>
<li>main_Window</li>
<li>main_menu_bar</li>
<li>path_menu_bar</li>
<li>plugin_control_list</li>
<li>window_(1-4)</li>
<li>context_menu</li>
<li>other</li>
</ul>
### Methods
```
# Must define and return a widget if "ui_target" is defined.
def get_ui_element(self):
button = Gtk.Button(label=self.name)
button.connect("button-release-event", self._do_download)
return button
# Must define in plugin if "pass_fm_events" is set to "true" string.
def set_fm_event_system(self, fm_event_system):
self._fm_event_system = fm_event_system
# Must define regardless if needed. Can just pass if plugin does stuff in its __init__
def run(self):
self._module_event_observer()
```

View File

@ -1,2 +0,0 @@
### 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.

View File

@ -24,42 +24,41 @@ def daemon_threaded(fn):
class Plugin: class Manifest:
def __init__(self, builder, event_system): path: str = os.path.dirname(os.path.realpath(__file__))
self.SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__)) name: str = "Example Plugin"
self._plugin_name = "Example Plugin" author: str = "John Doe"
self._plugin_author = "John Doe" version: str = "0.0.1"
self._plugin_version = "0.0.1" support: str = ""
permissions: {} = {
'ui_target': "plugin_control_list",
'pass_fm_events': "true"
self._builder = builder }
class Plugin(Manifest):
def __init__(self):
self._event_system = event_system self._event_system = event_system
self._event_sleep_time = .5 self._event_sleep_time = .5
self._event_message = None self._event_message = None
button = Gtk.Button(label=self._plugin_name)
def get_ui_element(self):
button = Gtk.Button(label=self.name)
button.connect("button-release-event", self.send_message) button.connect("button-release-event", self.send_message)
return button
plugin_list = self._builder.get_object("plugin_socket") def set_fm_event_system(self, fm_event_system):
plugin_list.add(button) self.event_system = fm_event_system
plugin_list.show_all()
def run(self):
def get_plugin_name(self): self._module_event_observer()
return self._plugin_name
def get_plugin_author(self):
return self._plugin_author
def get_plugin_version(self):
return self._plugin_version
def get_socket_id(self):
return self._socket_id
def send_message(self, widget=None, eve=None): def send_message(self, widget=None, eve=None):
message = "Hello, World!" message = "Hello, World!"
self._event_system.push_gui_event([self._plugin_name, "display_message", ("warning", message, None)]) self._event_system.push_gui_event([self.name, "display_message", ("warning", message, None)])
@daemon_threaded @daemon_threaded
@ -69,7 +68,7 @@ class Plugin:
event = self._event_system.read_module_event() event = self._event_system.read_module_event()
if event: if event:
try: try:
if event[0] is self._plugin_name: if event[0] is self.name:
target_id, method_target, data = self._event_system.consume_module_event() target_id, method_target, data = self._event_system.consume_module_event()
if not method_target: if not method_target:

View File

@ -24,36 +24,36 @@ def daemon_threaded(fn):
class Plugin: class Manifest:
def __init__(self, fm_builder, fm_event_system): path: str = os.path.dirname(os.path.realpath(__file__))
self.SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__)) name: str = "Youtube Download"
self._plugin_name = "Youtube Download" author: str = "ITDominator"
self._plugin_author = "ITDominator" version: str = "0.0.1"
self._plugin_version = "0.0.1" support: str = ""
permissions: {} = {
'ui_target': "plugin_control_list",
'pass_fm_events': "true"
self._fm_builder = fm_builder }
self._fm_event_system = fm_event_system
class Plugin(Manifest):
def __init__(self):
self._fm_event_system = None
self._event_sleep_time = .5 self._event_sleep_time = .5
self._fm_event_message = None self._fm_event_message = None
self._module_event_observer()
button = Gtk.Button(label=self._plugin_name) def get_ui_element(self):
button = Gtk.Button(label=self.name)
button.connect("button-release-event", self._do_download) button.connect("button-release-event", self._do_download)
return button
plugin_list = self._fm_builder.get_object("plugin_socket") def set_fm_event_system(self, fm_event_system):
plugin_list.add(button) self._fm_event_system = fm_event_system
plugin_list.show_all()
def run(self):
def get_plugin_name(self): self._module_event_observer()
return self._plugin_name
def get_plugin_author(self):
return self._plugin_author
def get_plugin_version(self):
return self._plugin_version
@daemon_threaded @daemon_threaded
@ -63,7 +63,7 @@ class Plugin:
event = self._fm_event_system.read_module_event() event = self._fm_event_system.read_module_event()
if event: if event:
try: try:
if event[0] is self._plugin_name: if event[0] is self.name:
target_id, method_target, data = self._fm_event_system.consume_module_event() target_id, method_target, data = self._fm_event_system.consume_module_event()
if not method_target: if not method_target:
@ -77,10 +77,10 @@ class Plugin:
@threaded @threaded
def _do_download(self, widget=None, eve=None): def _do_download(self, widget=None, eve=None):
self._fm_event_system.push_gui_event([self._plugin_name, "get_current_state", ()]) self._fm_event_system.push_gui_event([self.name, "get_current_state", ()])
while not self._fm_event_message: while not self._fm_event_message:
pass pass
state = self._fm_event_message state = self._fm_event_message
subprocess.Popen([f'{self.SCRIPT_PTH}/download.sh' , state.tab.get_current_directory()]) subprocess.Popen([f'{self.path}/download.sh' , state.tab.get_current_directory()])
self._fm_event_message = None self._fm_event_message = None

View File

@ -0,0 +1,18 @@
#!/bin/bash
# . CONFIG.sh
# set -o xtrace ## To debug scripts
# set -o errexit ## To exit on error
# set -o errunset ## To exit if a variable is referenced but not set
function main() {
SCRIPTPATH="$( cd "$(dirname "")" >/dev/null 2>&1 ; pwd -P )"
cd "${SCRIPTPATH}"
echo "Working Dir: " $(pwd)
source '/home/abaddon/Portable_Apps/py-venvs/gtk-apps-venv/venv/bin/activate'
python -m pudb $(pwd)/solarfm/__main__.py; bash
}
main "$@";

View File

@ -30,7 +30,7 @@ class Application(EventSystem):
message = f"FILE|{args.new_tab}" message = f"FILE|{args.new_tab}"
event_system.send_ipc_message(message) event_system.send_ipc_message(message)
raise Exception("IPC Server Exists: Will send path(s) to it and close...") raise Exception("IPC Server Exists: Will send path(s) to it and close...\nNote: If no fm exists, remove /tmp/solarfm-ipc.sock")
settings = Settings() settings = Settings()

View File

@ -97,16 +97,16 @@ class ShowHideMixin:
def show_plugins_popup(self, widget=None, eve=None): def show_plugins_popup(self, widget=None, eve=None):
self.builder.get_object("plugin_list").popup() self.builder.get_object("plugin_controls").popup()
def hide_plugins_popup(self, widget=None, eve=None): def hide_plugins_popup(self, widget=None, eve=None):
self.builder.get_object("plugin_list").hide() self.builder.get_object("plugin_controls").hide()
def show_context_menu(self, widget=None, eve=None): def show_context_menu(self, widget=None, eve=None):
self.builder.get_object("context_menu").run() self.builder.get_object("context_menu_popup").run()
def hide_context_menu(self, widget=None, eve=None): def hide_context_menu(self, widget=None, eve=None):
self.builder.get_object("context_menu").hide() self.builder.get_object("context_menu_popup").hide()
def show_new_file_menu(self, widget=None, eve=None): def show_new_file_menu(self, widget=None, eve=None):

View File

@ -149,10 +149,7 @@ class WidgetFileActionMixin:
def archive_files(self, archiver_dialogue): def archive_files(self, archiver_dialogue):
state = self.get_current_state() state = self.get_current_state()
_paths = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True) paths = [shlex.quote(p) for p in self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True)]
paths = []
for p in _paths:
paths.append(shlex.quote(p))
save_target = archiver_dialogue.get_filename(); save_target = archiver_dialogue.get_filename();
sItr, eItr = self.arc_command_buffer.get_bounds() sItr, eItr = self.arc_command_buffer.get_bounds()
@ -175,6 +172,7 @@ class WidgetFileActionMixin:
rename_input.set_text(entry) rename_input.set_text(entry)
self.show_edit_file_menu(rename_input) self.show_edit_file_menu(rename_input)
if self.skip_edit: if self.skip_edit:
self.skip_edit = False self.skip_edit = False
continue continue

View File

@ -44,7 +44,6 @@ class KeyboardSignalsMixin:
if "alt" in keyname: if "alt" in keyname:
self.alt_down = False self.alt_down = False
mapping = self.keybindings.lookup(event) mapping = self.keybindings.lookup(event)
if mapping: if mapping:
getattr(self, mapping)() getattr(self, mapping)()

View File

@ -11,13 +11,16 @@ from gi.repository import Gtk, Gio
class Plugin: class Plugin:
path: str = None
name: str = None name: str = None
author: str = None author: str = None
version: str = None version: str = None
module: str = None suppoert: str = None
permissions:{} = None
reference: type = None reference: type = None
class Plugins: class Plugins:
"""Plugins controller""" """Plugins controller"""
@ -44,7 +47,6 @@ class Plugins:
Gio.FileMonitorEvent.MOVED_OUT]: Gio.FileMonitorEvent.MOVED_OUT]:
self.reload_plugins(file) self.reload_plugins(file)
# @threaded
def load_plugins(self, file: str = None) -> None: def load_plugins(self, file: str = None) -> None:
print(f"Loading plugins...") print(f"Loading plugins...")
parent_path = os.getcwd() parent_path = os.getcwd()
@ -53,23 +55,11 @@ class Plugins:
try: try:
path = join(self._plugins_path, file) path = join(self._plugins_path, file)
if isdir(path): if isdir(path):
os.chdir(path) module = self.load_plugin_module(path, file)
plugin = self.collect_info(module, path)
loading_data = self.parse_permissions(plugin)
sys.path.insert(0, path) self.execute_plugin(module, plugin, loading_data)
spec = importlib.util.spec_from_file_location(file, join(path, "__main__.py"))
app = importlib.util.module_from_spec(spec)
spec.loader.exec_module(app)
plugin_reference = app.Plugin(self._builder, event_system)
plugin = Plugin()
plugin.name = plugin_reference.get_plugin_name()
plugin.author = plugin_reference.get_plugin_author()
plugin.version = plugin_reference.get_plugin_version()
plugin.module = path
plugin.reference = plugin_reference
self._plugin_collection.append(plugin)
except Exception as e: except Exception as e:
print("Malformed plugin! Not loading!") print("Malformed plugin! Not loading!")
traceback.print_exc() traceback.print_exc()
@ -77,5 +67,68 @@ class Plugins:
os.chdir(parent_path) os.chdir(parent_path)
def load_plugin_module(self, path, file):
os.chdir(path)
sys.path.insert(0, path)
spec = importlib.util.spec_from_file_location(file, join(path, "plugin.py"))
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def collect_info(self, module, path) -> Plugin:
plugin = Plugin()
plugin.path = module.Manifest.path
plugin.name = module.Manifest.name
plugin.author = module.Manifest.author
plugin.version = module.Manifest.version
plugin.support = module.Manifest.support
plugin.permissions = module.Manifest.permissions
return plugin
def parse_permissions(self, plugin):
loading_data = {}
permissions = plugin.permissions
keys = permissions.keys()
if "ui_target" in keys:
if permissions["ui_target"] in [
"none", "other", "main_Window", "main_menu_bar", "path_menu_bar", "plugin_control_list",
"context_menu", "window_1", "window_2", "window_3", "window_4"
]:
if permissions["ui_target"] == "other":
if "ui_target_id" in keys:
loading_data["ui_target"] = self._builder.get_object(permissions["ui_target_id"])
if loading_data["ui_target"] == None:
raise Exception('Invalid "ui_target_id" given in permissions. Must have one if setting "ui_target" to "other"...')
else:
raise Exception('Invalid "ui_target_id" given in permissions. Must have one if setting "ui_target" to "other"...')
else:
loading_data["ui_target"] = self._builder.get_object(permissions["ui_target"])
else:
raise Exception('Unknown "ui_target" given in permissions.')
if "pass_fm_events" in keys:
if permissions["pass_fm_events"] in ["true"]:
loading_data["pass_fm_events"] = True
return loading_data
def execute_plugin(self, module: type, plugin: Plugin, loading_data: []):
plugin.reference = module.Plugin()
keys = loading_data.keys()
if "ui_target" in keys:
loading_data["ui_target"].add(plugin.reference.get_ui_element())
loading_data["ui_target"].show_all()
if "pass_fm_events" in keys:
plugin.reference.set_fm_event_system(event_system)
plugin.reference.run()
self._plugin_collection.append(plugin)
def reload_plugins(self, file: str = None) -> None: def reload_plugins(self, file: str = None) -> None:
print(f"Reloading plugins... stub.") print(f"Reloading plugins... stub.")

View File

@ -66,7 +66,7 @@ class Settings:
def create_window(self) -> None: def create_window(self) -> None:
# Get window and connect signals # Get window and connect signals
self._main_window = self._builder.get_object("Main_Window") self._main_window = self._builder.get_object("main_window")
self._set_window_data() self._set_window_data()
def _set_window_data(self) -> None: def _set_window_data(self) -> None:
@ -93,7 +93,7 @@ class Settings:
cr.set_operator(cairo.OPERATOR_OVER) cr.set_operator(cairo.OPERATOR_OVER)
def get_monitor_data(self) -> list: def get_monitor_data(self) -> list:
screen = self._builder.get_object("Main_Window").get_screen() screen = self._builder.get_object("main_window").get_screen()
monitors = [] monitors = []
for m in range(screen.get_n_monitors()): for m in range(screen.get_n_monitors()):
monitors.append(screen.get_monitor_geometry(m)) monitors.append(screen.get_monitor_geometry(m))