From 8f64066049f71dbec3a5b59b88d3383880bc34ef Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Wed, 6 Jul 2022 23:19:41 -0500 Subject: [PATCH] Restructured plugin system and examples --- plugins/README.md | 58 ++++++++++++ plugins/README.txt | 2 - plugins/template/{__main__.py => plugin.py} | 49 +++++----- .../{__main__.py => plugin.py} | 50 +++++------ .../solarfm-0.0.1/SolarFM/debugger.sh | 18 ++++ .../solarfm-0.0.1/SolarFM/solarfm/app.py | 2 +- .../solarfm/core/mixins/show_hide_mixin.py | 8 +- .../mixins/ui/widget_file_action_mixin.py | 22 +++-- .../core/signals/keyboard_signals_mixin.py | 1 - .../SolarFM/solarfm/plugins/plugins.py | 89 +++++++++++++++---- .../SolarFM/solarfm/utils/settings.py | 4 +- 11 files changed, 213 insertions(+), 90 deletions(-) create mode 100644 plugins/README.md delete mode 100644 plugins/README.txt rename plugins/template/{__main__.py => plugin.py} (62%) rename plugins/youtube_download/{__main__.py => plugin.py} (62%) create mode 100755 src/versions/solarfm-0.0.1/SolarFM/debugger.sh diff --git a/plugins/README.md b/plugins/README.md new file mode 100644 index 0000000..6c3ef9e --- /dev/null +++ b/plugins/README.md @@ -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': "" # 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: + + +### 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() + +``` diff --git a/plugins/README.txt b/plugins/README.txt deleted file mode 100644 index 4173ddd..0000000 --- a/plugins/README.txt +++ /dev/null @@ -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. diff --git a/plugins/template/__main__.py b/plugins/template/plugin.py similarity index 62% rename from plugins/template/__main__.py rename to plugins/template/plugin.py index 110ce10..f991579 100644 --- a/plugins/template/__main__.py +++ b/plugins/template/plugin.py @@ -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: diff --git a/plugins/youtube_download/__main__.py b/plugins/youtube_download/plugin.py similarity index 62% rename from plugins/youtube_download/__main__.py rename to plugins/youtube_download/plugin.py index 06d7e06..1407471 100644 --- a/plugins/youtube_download/__main__.py +++ b/plugins/youtube_download/plugin.py @@ -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 diff --git a/src/versions/solarfm-0.0.1/SolarFM/debugger.sh b/src/versions/solarfm-0.0.1/SolarFM/debugger.sh new file mode 100755 index 0000000..db15691 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/debugger.sh @@ -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 "$@"; diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py index bedf202..bc414b5 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py @@ -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() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/show_hide_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/show_hide_mixin.py index 687a47a..cf77ba0 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/show_hide_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/show_hide_mixin.py @@ -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): diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/widget_file_action_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/widget_file_action_mixin.py index 84005de..9fc5a83 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/widget_file_action_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/widget_file_action_mixin.py @@ -63,15 +63,15 @@ class WidgetFileActionMixin: if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED, Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN, Gio.FileMonitorEvent.MOVED_OUT]: - if debug: - self.logger.debug(eve_type) + if debug: + self.logger.debug(eve_type) - if eve_type in [Gio.FileMonitorEvent.MOVED_IN, Gio.FileMonitorEvent.MOVED_OUT]: - self.update_on_soft_lock_end(data[0]) - elif data[0] in self.soft_update_lock.keys(): - self.soft_update_lock[data[0]]["last_update_time"] = time.time() - else: - self.soft_lock_countdown(data[0]) + if eve_type in [Gio.FileMonitorEvent.MOVED_IN, Gio.FileMonitorEvent.MOVED_OUT]: + self.update_on_soft_lock_end(data[0]) + elif data[0] in self.soft_update_lock.keys(): + self.soft_update_lock[data[0]]["last_update_time"] = time.time() + else: + self.soft_lock_countdown(data[0]) @threaded def soft_lock_countdown(self, tab_widget): @@ -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 diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/keyboard_signals_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/keyboard_signals_mixin.py index bc2f762..4cb0108 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/keyboard_signals_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/keyboard_signals_mixin.py @@ -44,7 +44,6 @@ class KeyboardSignalsMixin: if "alt" in keyname: self.alt_down = False - mapping = self.keybindings.lookup(event) if mapping: getattr(self, mapping)() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py index 92cf234..44c5e63 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py @@ -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.") diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/settings.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/settings.py index 80bbc01..3826e3d 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/settings.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/settings.py @@ -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))