Restructured plugin system and examples
This commit is contained in:
parent
111c535876
commit
8f64066049
|
@ -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()
|
||||
|
||||
```
|
|
@ -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.
|
|
@ -24,42 +24,41 @@ def daemon_threaded(fn):
|
|||
|
||||
|
||||
|
||||
class Plugin:
|
||||
def __init__(self, builder, event_system):
|
||||
self.SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__))
|
||||
self._plugin_name = "Example Plugin"
|
||||
self._plugin_author = "John Doe"
|
||||
self._plugin_version = "0.0.1"
|
||||
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"
|
||||
|
||||
self._builder = builder
|
||||
}
|
||||
|
||||
|
||||
class Plugin(Manifest):
|
||||
def __init__(self):
|
||||
self._event_system = event_system
|
||||
self._event_sleep_time = .5
|
||||
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)
|
||||
return button
|
||||
|
||||
plugin_list = self._builder.get_object("plugin_socket")
|
||||
plugin_list.add(button)
|
||||
plugin_list.show_all()
|
||||
def set_fm_event_system(self, fm_event_system):
|
||||
self.event_system = fm_event_system
|
||||
|
||||
|
||||
def get_plugin_name(self):
|
||||
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 run(self):
|
||||
self._module_event_observer()
|
||||
|
||||
|
||||
def send_message(self, widget=None, eve=None):
|
||||
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
|
||||
|
@ -69,7 +68,7 @@ class Plugin:
|
|||
event = self._event_system.read_module_event()
|
||||
if event:
|
||||
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()
|
||||
|
||||
if not method_target:
|
|
@ -24,36 +24,36 @@ def daemon_threaded(fn):
|
|||
|
||||
|
||||
|
||||
class Plugin:
|
||||
def __init__(self, fm_builder, fm_event_system):
|
||||
self.SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__))
|
||||
self._plugin_name = "Youtube Download"
|
||||
self._plugin_author = "ITDominator"
|
||||
self._plugin_version = "0.0.1"
|
||||
class Manifest:
|
||||
path: str = os.path.dirname(os.path.realpath(__file__))
|
||||
name: str = "Youtube Download"
|
||||
author: str = "ITDominator"
|
||||
version: str = "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._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)
|
||||
return button
|
||||
|
||||
plugin_list = self._fm_builder.get_object("plugin_socket")
|
||||
plugin_list.add(button)
|
||||
plugin_list.show_all()
|
||||
def set_fm_event_system(self, fm_event_system):
|
||||
self._fm_event_system = fm_event_system
|
||||
|
||||
|
||||
def get_plugin_name(self):
|
||||
return self._plugin_name
|
||||
|
||||
def get_plugin_author(self):
|
||||
return self._plugin_author
|
||||
|
||||
def get_plugin_version(self):
|
||||
return self._plugin_version
|
||||
def run(self):
|
||||
self._module_event_observer()
|
||||
|
||||
|
||||
@daemon_threaded
|
||||
|
@ -63,7 +63,7 @@ class Plugin:
|
|||
event = self._fm_event_system.read_module_event()
|
||||
if event:
|
||||
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()
|
||||
|
||||
if not method_target:
|
||||
|
@ -77,10 +77,10 @@ class Plugin:
|
|||
|
||||
@threaded
|
||||
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:
|
||||
pass
|
||||
|
||||
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
|
|
@ -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 "$@";
|
|
@ -30,7 +30,7 @@ class Application(EventSystem):
|
|||
message = f"FILE|{args.new_tab}"
|
||||
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()
|
||||
|
|
|
@ -97,16 +97,16 @@ class ShowHideMixin:
|
|||
|
||||
|
||||
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):
|
||||
self.builder.get_object("plugin_list").hide()
|
||||
self.builder.get_object("plugin_controls").hide()
|
||||
|
||||
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):
|
||||
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):
|
||||
|
|
|
@ -149,10 +149,7 @@ class WidgetFileActionMixin:
|
|||
|
||||
def archive_files(self, archiver_dialogue):
|
||||
state = self.get_current_state()
|
||||
_paths = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True)
|
||||
paths = []
|
||||
for p in _paths:
|
||||
paths.append(shlex.quote(p))
|
||||
paths = [shlex.quote(p) for p in self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True)]
|
||||
|
||||
save_target = archiver_dialogue.get_filename();
|
||||
sItr, eItr = self.arc_command_buffer.get_bounds()
|
||||
|
@ -175,6 +172,7 @@ class WidgetFileActionMixin:
|
|||
rename_input.set_text(entry)
|
||||
|
||||
self.show_edit_file_menu(rename_input)
|
||||
|
||||
if self.skip_edit:
|
||||
self.skip_edit = False
|
||||
continue
|
||||
|
|
|
@ -44,7 +44,6 @@ class KeyboardSignalsMixin:
|
|||
if "alt" in keyname:
|
||||
self.alt_down = False
|
||||
|
||||
|
||||
mapping = self.keybindings.lookup(event)
|
||||
if mapping:
|
||||
getattr(self, mapping)()
|
||||
|
|
|
@ -11,13 +11,16 @@ from gi.repository import Gtk, Gio
|
|||
|
||||
|
||||
class Plugin:
|
||||
path: str = None
|
||||
name: str = None
|
||||
author: str = None
|
||||
version: str = None
|
||||
module: str = None
|
||||
suppoert: str = None
|
||||
permissions:{} = None
|
||||
reference: type = None
|
||||
|
||||
|
||||
|
||||
class Plugins:
|
||||
"""Plugins controller"""
|
||||
|
||||
|
@ -44,7 +47,6 @@ class Plugins:
|
|||
Gio.FileMonitorEvent.MOVED_OUT]:
|
||||
self.reload_plugins(file)
|
||||
|
||||
# @threaded
|
||||
def load_plugins(self, file: str = None) -> None:
|
||||
print(f"Loading plugins...")
|
||||
parent_path = os.getcwd()
|
||||
|
@ -53,23 +55,11 @@ class Plugins:
|
|||
try:
|
||||
path = join(self._plugins_path, file)
|
||||
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)
|
||||
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)
|
||||
self.execute_plugin(module, plugin, loading_data)
|
||||
except Exception as e:
|
||||
print("Malformed plugin! Not loading!")
|
||||
traceback.print_exc()
|
||||
|
@ -77,5 +67,68 @@ class Plugins:
|
|||
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:
|
||||
print(f"Reloading plugins... stub.")
|
||||
|
|
|
@ -66,7 +66,7 @@ class Settings:
|
|||
|
||||
def create_window(self) -> None:
|
||||
# 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()
|
||||
|
||||
def _set_window_data(self) -> None:
|
||||
|
@ -93,7 +93,7 @@ class Settings:
|
|||
cr.set_operator(cairo.OPERATOR_OVER)
|
||||
|
||||
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 = []
|
||||
for m in range(screen.get_n_monitors()):
|
||||
monitors.append(screen.get_monitor_geometry(m))
|
||||
|
|
Loading…
Reference in New Issue