From a7fbc6eadb9731e0ba81c0d70b088231ec680641 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sat, 24 Sep 2022 15:40:21 -0500 Subject: [PATCH 01/30] Updated .gitignore, renamed plugins file --- .gitignore | 2 +- .../solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py | 4 ++-- .../solarfm/plugins/{plugins.py => plugins_controller.py} | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/{plugins.py => plugins_controller.py} (98%) diff --git a/.gitignore b/.gitignore index a7059ec..cc28be0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +docs/ .idea/ *.zip @@ -140,4 +141,3 @@ dmypy.json # Cython debug symbols cython_debug/ - diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py index 6181d1f..fbd5e89 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py @@ -9,7 +9,7 @@ from gi.repository import GLib # Application imports from trasher.xdgtrash import XDGTrash from shellfm.windows.controller import WindowController -from plugins.plugins import Plugins +from plugins.plugins_controller import PluginsController @dataclass(slots=True) @@ -36,7 +36,7 @@ class Controller_Data: self.trashman = XDGTrash() self.fm_controller = WindowController() - self.plugins = Plugins(_settings) + self.plugins = PluginsController(_settings) self.fm_controller_data = self.fm_controller.get_state_from_file() self.trashman.regenerate() 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_controller.py similarity index 98% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins_controller.py index e787202..1ef2001 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins_controller.py @@ -17,8 +17,8 @@ class InvalidPluginException(Exception): ... -class Plugins: - """Plugins controller""" +class PluginsController: + """PluginsController controller""" def __init__(self, settings: type): path = os.path.dirname(os.path.realpath(__file__)) From ded86b81ec4845d769de29d718d5d998bab9f989 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Thu, 29 Sep 2022 17:22:33 -0500 Subject: [PATCH 02/30] Event system logic rework --- plugins/favorites/plugin.py | 22 ++----- plugins/file_properties/plugin.py | 7 +-- plugins/movie_tv_info/plugin.py | 16 ++--- plugins/searcher/plugin.py | 7 +-- plugins/template/plugin.py | 4 +- plugins/vod_thumbnailer/plugin.py | 32 +++++----- plugins/youtube_download/plugin.py | 17 ++--- .../SolarFM/solarfm/__builtins__.py | 18 +----- .../SolarFM/solarfm/core/controller.py | 38 +++-------- .../SolarFM/solarfm/core/controller_data.py | 2 + .../SolarFM/solarfm/plugins/plugin_base.py | 31 ++------- .../solarfm/plugins/plugins_controller.py | 1 + .../solarfm/utils/endpoint_registry.py | 22 +++++++ .../SolarFM/solarfm/utils/event_system.py | 63 +++++-------------- .../SolarFM/solarfm/utils/ipc_server.py | 7 ++- 15 files changed, 102 insertions(+), 185 deletions(-) create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/endpoint_registry.py diff --git a/plugins/favorites/plugin.py b/plugins/favorites/plugin.py index e586de7..aca3f63 100644 --- a/plugins/favorites/plugin.py +++ b/plugins/favorites/plugin.py @@ -38,7 +38,6 @@ class Plugin(PluginBase): self._favorites_dialog = None self._favorites_store = None self._favorites = None - self._state = None self._selected = None @@ -48,8 +47,6 @@ class Plugin(PluginBase): return button def run(self): - self._module_event_observer() - self._builder = Gtk.Builder() self._builder.add_from_file(self._GLADE_FILE) @@ -81,19 +78,15 @@ class Plugin(PluginBase): @threaded def _get_state(self, widget=None, eve=None): - self._event_system.push_gui_event([self.name, "get_current_state", ()]) - self.wait_for_fm_message() + self._event_system.post_event("get_current_state", None) - self._state = self._event_message - self._event_message = None @threaded def _set_current_dir_lbl(self, widget=None, eve=None): - self.wait_for_state() - self._current_dir_lbl.set_label(f"Current Directory:\n{self._state.tab.get_current_directory()}") + self._current_dir_lbl.set_label(f"Current Directory:\n{self._fm_state.tab.get_current_directory()}") def _add_to_favorite(self, state): - current_directory = self._state.tab.get_current_directory() + current_directory = self._fm_state.tab.get_current_directory() self._favorites_store.append([current_directory]) self._favorites.append(current_directory) self._save_favorites() @@ -111,11 +104,10 @@ class Plugin(PluginBase): def _set_selected_path(self, widget=None, eve=None): path = self._favorites_store.get_value(self._selected, 0) self._ui_objects[0].set_text(path) - - + self._set_current_dir_lbl() def _show_favorites_menu(self, widget=None, eve=None): - self._state = None + self._fm_state = None self._get_state() self._set_current_dir_lbl() self._favorites_dialog.run() @@ -127,7 +119,3 @@ class Plugin(PluginBase): selected = user_data.get_selected()[1] if selected: self._selected = selected - - def wait_for_state(self): - while not self._state: - pass diff --git a/plugins/file_properties/plugin.py b/plugins/file_properties/plugin.py index 29472ed..5177943 100644 --- a/plugins/file_properties/plugin.py +++ b/plugins/file_properties/plugin.py @@ -89,8 +89,6 @@ class Plugin(PluginBase): return button def run(self): - self._module_event_observer() - self._builder = Gtk.Builder() self._builder.add_from_file(self._GLADE_FILE) @@ -109,10 +107,9 @@ class Plugin(PluginBase): @threaded def _show_properties_page(self, widget=None, eve=None): - self._event_system.push_gui_event([self.name, "get_current_state", ()]) - self.wait_for_fm_message() + event_system.post_event("get_current_state", None) - state = self._event_message + state = self._fm_state self._event_message = None GLib.idle_add(self._process_changes, (state)) diff --git a/plugins/movie_tv_info/plugin.py b/plugins/movie_tv_info/plugin.py index 3a4a1c5..64df1c6 100644 --- a/plugins/movie_tv_info/plugin.py +++ b/plugins/movie_tv_info/plugin.py @@ -39,7 +39,6 @@ class Plugin(PluginBase): self._dialog = None self._thumbnail_preview_img = None self._tmdb = scraper.get_tmdb_scraper() - self._state = None self._overview = None self._file_name = None self._file_location = None @@ -53,8 +52,6 @@ class Plugin(PluginBase): return button def run(self): - self._module_event_observer() - self._builder = Gtk.Builder() self._builder.add_from_file(self._GLADE_FILE) @@ -80,19 +77,18 @@ class Plugin(PluginBase): @threaded def _show_info_page(self, widget=None, eve=None): - self._event_system.push_gui_event([self.name, "get_current_state", ()]) - self.wait_for_fm_message() + self._event_system.post_event("get_current_state", None) - state = self._event_message + state = self._fm_state self._event_message = None GLib.idle_add(self._process_changes, (state)) def _process_changes(self, state): - self._state = None + self._fm_state = None if len(state.selected_files) == 1: - self._state = state + self._fm_state = state self._set_ui_data() response = self._thumbnailer_dialog.run() if response in [Gtk.ResponseType.CLOSE, Gtk.ResponseType.DELETE_EVENT]: @@ -111,8 +107,8 @@ class Plugin(PluginBase): print(video_data["videos"]) if not keys in ("", None) and "videos" in keys else ... def get_video_data(self): - uri = self._state.selected_files[0] - path = self._state.tab.get_current_directory() + uri = self._fm_state.selected_files[0] + path = self._fm_state.tab.get_current_directory() parts = uri.split("/") _title = parts[ len(parts) - 1 ] diff --git a/plugins/searcher/plugin.py b/plugins/searcher/plugin.py index 3ace6ba..9328a90 100644 --- a/plugins/searcher/plugin.py +++ b/plugins/searcher/plugin.py @@ -98,8 +98,6 @@ class Plugin(PluginBase): return button def run(self): - self._module_event_observer() - self._builder = Gtk.Builder() self._builder.add_from_file(self._GLADE_FILE) @@ -127,10 +125,9 @@ class Plugin(PluginBase): @daemon_threaded def _show_grep_list_page(self, widget=None, eve=None): - self._event_system.push_gui_event([self.name, "get_current_state", ()]) - self.wait_for_fm_message() + self._event_system.post_event("get_current_state", None) - state = self._event_message + state = self._fm_state self._event_message = None GLib.idle_add(self._process_queries, (state)) diff --git a/plugins/template/plugin.py b/plugins/template/plugin.py index 08c2b72..b99308a 100644 --- a/plugins/template/plugin.py +++ b/plugins/template/plugin.py @@ -39,8 +39,8 @@ class Plugin(PluginBase): return button 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.name, "display_message", ("warning", message, None)]) + event_system.post_event("display_message", ("warning", message, None)) diff --git a/plugins/vod_thumbnailer/plugin.py b/plugins/vod_thumbnailer/plugin.py index 4a8abad..3d6f164 100644 --- a/plugins/vod_thumbnailer/plugin.py +++ b/plugins/vod_thumbnailer/plugin.py @@ -42,7 +42,6 @@ class Plugin(PluginBase): self._file_name = None self._file_location = None self._file_hash = None - self._state = None def get_ui_element(self): @@ -51,8 +50,6 @@ class Plugin(PluginBase): return button def run(self): - self._module_event_observer() - self._builder = Gtk.Builder() self._builder.add_from_file(self._GLADE_FILE) @@ -78,20 +75,19 @@ class Plugin(PluginBase): @threaded def _show_thumbnailer_page(self, widget=None, eve=None): - self._event_system.push_gui_event([self.name, "get_current_state", ()]) - self.wait_for_fm_message() + self._event_system.post_event("get_current_state", None) - state = self._event_message + state = self._fm_state self._event_message = None GLib.idle_add(self._process_changes, (state)) def _process_changes(self, state): - self._state = None + self._fm_state = None if len(state.selected_files) == 1: if state.selected_files[0].lower().endswith(state.tab.fvideos): - self._state = state + self._fm_state = state self._set_ui_data() response = self._thumbnailer_dialog.run() if response in [Gtk.ResponseType.CLOSE, Gtk.ResponseType.DELETE_EVENT]: @@ -103,32 +99,32 @@ class Plugin(PluginBase): file = self._file_name.get_text() dir = self._file_location.get_text() file_hash = self._file_hash.get_text() - hash_img_pth = f"{self._state.tab.ABS_THUMBS_PTH}/{file_hash}.jpg" + hash_img_pth = f"{self._fm_state.tab.ABS_THUMBS_PTH}/{file_hash}.jpg" try: os.remove(hash_img_pth) if os.path.isfile(hash_img_pth) else ... - self._state.tab.create_thumbnail(dir, file, f"{scrub_percent}%") + self._fm_state.tab.create_thumbnail(dir, file, f"{scrub_percent}%") preview_pixbuf = GdkPixbuf.Pixbuf.new_from_file(hash_img_pth) self._thumbnail_preview_img.set_from_pixbuf(preview_pixbuf) - img_pixbuf = self._state.tab.create_scaled_image(hash_img_pth) - tree_pth = self._state.icon_grid.get_selected_items()[0] - itr = self._state.store.get_iter(tree_pth) - pixbuff = self._state.store.get(itr, 0)[0] - self._state.store.set(itr, 0, img_pixbuf) + img_pixbuf = self._fm_state.tab.create_scaled_image(hash_img_pth) + tree_pth = self._fm_state.icon_grid.get_selected_items()[0] + itr = self._fm_state.store.get_iter(tree_pth) + pixbuff = self._fm_state.store.get(itr, 0)[0] + self._fm_state.store.set(itr, 0, img_pixbuf) except Exception as e: print(repr(e)) print("Couldn't regenerate thumbnail!") def _set_ui_data(self): - uri = self._state.selected_files[0] - path = self._state.tab.get_current_directory() + uri = self._fm_state.selected_files[0] + path = self._fm_state.tab.get_current_directory() parts = uri.split("/") file_hash = hashlib.sha256(str.encode(uri)).hexdigest() - hash_img_pth = f"{self._state.tab.ABS_THUMBS_PTH}/{file_hash}.jpg" + hash_img_pth = f"{self._fm_state.tab.ABS_THUMBS_PTH}/{file_hash}.jpg" preview_pixbuf = GdkPixbuf.Pixbuf.new_from_file(hash_img_pth) self._thumbnail_preview_img.set_from_pixbuf(preview_pixbuf) diff --git a/plugins/youtube_download/plugin.py b/plugins/youtube_download/plugin.py index d2d4919..5ebf3b1 100644 --- a/plugins/youtube_download/plugin.py +++ b/plugins/youtube_download/plugin.py @@ -40,14 +40,15 @@ class Plugin(PluginBase): return button def run(self): - self._module_event_observer() + ... + def _do_download(self, widget=None, eve=None): + self._event_system.post_event("get_current_state", None) + + dir = self._fm_state.tab.get_current_directory() + self._download(dir) + @threaded - def _do_download(self, widget=None, eve=None): - self._event_system.push_gui_event([self.name, "get_current_state", ()]) - self.wait_for_fm_message() - - state = self._event_message - subprocess.Popen([f'{self.path}/download.sh' , state.tab.get_current_directory()]) - self._event_message = None + def _download(self, dir): + subprocess.Popen([f'{self.path}/download.sh', dir]) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py index d9a77d8..428f16f 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py @@ -5,6 +5,7 @@ import builtins, threading # Application imports from utils.event_system import EventSystem +from utils.endpoint_registry import EndpointRegistry @@ -24,23 +25,6 @@ def daemon_threaded_wrapper(fn): -class EndpointRegistry(): - def __init__(self): - self._endpoints = {} - - def register(self, rule, **options): - def decorator(f): - self._endpoints[rule] = f - return f - - return decorator - - def get_endpoints(self): - return self._endpoints - - - - # NOTE: Just reminding myself we can add to builtins two different ways... # __builtins__.update({"event_system": Builtins()}) builtins.app_name = "SolarFM" diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py index e844325..8d99d05 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py @@ -28,52 +28,32 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi if debug: self.window.set_interactive_debugging(True) + if not trace_debug: - self.gui_event_observer() + self._subscribe_to_events() if unknownargs: for arg in unknownargs: if os.path.isdir(arg): message = f"FILE|{arg}" - event_system.send_ipc_message(message) + event_system.post_event("post_file_to_ipc", message) if args.new_tab and os.path.isdir(args.new_tab): message = f"FILE|{args.new_tab}" - event_system.send_ipc_message(message) + event_system.post_event("post_file_to_ipc", message) + def _subscribe_to_events(self): + event_system.subscribe("handle_file_from_ipc", self.handle_file_from_ipc) + event_system.subscribe("get_current_state", self.get_current_state) + event_system.subscribe("display_message", self.display_message) + def tear_down(self, widget=None, eve=None): self.fm_controller.save_state() time.sleep(event_sleep_time) Gtk.main_quit() - @daemon_threaded - def gui_event_observer(self): - while True: - time.sleep(event_sleep_time) - event = event_system.consume_gui_event() - if event: - try: - sender_id, method_target, parameters = event - if sender_id: - method = getattr(self.__class__, "handle_gui_event_and_return_message") - GLib.idle_add(method, *(self, sender_id, method_target, parameters)) - else: - method = getattr(self.__class__, method_target) - GLib.idle_add(method, *(self, *parameters,)) - except Exception as e: - print(repr(e)) - - def handle_gui_event_and_return_message(self, sender, method_target, parameters): - method = getattr(self.__class__, f"{method_target}") - data = method(*(self, *parameters)) - event_system.push_module_event([sender, None, data]) - - def handle_plugin_key_event(self, sender, method_target, parameters=()): - event_system.push_module_event([sender, method_target, parameters]) - - def save_load_session(self, action="save_session"): wid, tid = self.fm_controller.get_active_wid_and_tid() tab = self.get_fm_window(wid).get_tab_by_id(tid) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py index fbd5e89..04a6656 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py @@ -154,6 +154,8 @@ class Controller_Data: # if self.to_cut_files: # state.to_cut_files = self.format_to_uris(state.store, state.wid, state.tid, self.to_cut_files, True) + event_system.post_event("update_state_info_plugins", state) + return state diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugin_base.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugin_base.py index 8e33157..effd377 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugin_base.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugin_base.py @@ -13,10 +13,9 @@ class PluginBase: self._builder = None self._ui_objects = None - + self._fm_state = None self._event_system = None - self._event_sleep_time = .5 - self._event_message = None + def set_fm_event_system(self, fm_event_system): self._event_system = fm_event_system @@ -24,32 +23,14 @@ class PluginBase: def set_ui_object_collection(self, ui_objects): self._ui_objects = ui_objects - def wait_for_fm_message(self): - while not self._event_message: - pass def clear_children(self, widget: type) -> None: ''' Clear children of a gtk widget. ''' for child in widget.get_children(): widget.remove(child) - @daemon_threaded - def _module_event_observer(self): - while True: - time.sleep(self._event_sleep_time) - event = self._event_system.read_module_event() - if event: - try: - if event[0] == self.name: - target_id, method_target, data = self._event_system.consume_module_event() + def subscribe_to_events(self): + self._event_system.subscribe("update_state_info_plugins", self._update_fm_state_info) - if not method_target: - self._event_message = data - else: - method = getattr(self.__class__, f"{method_target}") - if data: - data = method(*(self, *data)) - else: - method(*(self,)) - except Exception as e: - print(repr(e)) + def _update_fm_state_info(self, state): + self._fm_state = state diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins_controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins_controller.py index 1ef2001..11f9dda 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins_controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins_controller.py @@ -93,6 +93,7 @@ class PluginsController: if "pass_fm_events" in keys: plugin.reference.set_fm_event_system(event_system) + plugin.reference.subscribe_to_events() if "bind_keys" in keys: self._keybindings.append_bindings( loading_data["bind_keys"] ) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/endpoint_registry.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/endpoint_registry.py new file mode 100644 index 0000000..15ffa9e --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/endpoint_registry.py @@ -0,0 +1,22 @@ +# Python imports + +# Lib imports + +# Application imports + + + + +class EndpointRegistry(): + def __init__(self): + self._endpoints = {} + + def register(self, rule, **options): + def decorator(f): + self._endpoints[rule] = f + return f + + return decorator + + def get_endpoints(self): + return self._endpoints diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/event_system.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/event_system.py index f3ea280..53aa64e 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/event_system.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/event_system.py @@ -1,4 +1,5 @@ # Python imports +from collections import defaultdict # Lib imports @@ -7,57 +8,23 @@ -class EventSystemPushException(Exception): - ... - - class EventSystem: - """ Inheret IPCServerMixin. Create an pub/sub systems. """ + """ Create event system. """ def __init__(self): - # NOTE: The format used is list of ['who', target, (data,)] Where: - # who is the sender or target ID and is used for context and control flow, - # method_target is the method to call, - # data is the method parameters OR message data to give - # Where data may be any kind of data - self._gui_events = [] - self._module_events = [] + self.subscribers = defaultdict(list) - # Makeshift "events" system FIFO - def _pop_gui_event(self) -> None: - if len(self._gui_events) > 0: - return self._gui_events.pop(0) - return None + def subscribe(self, event_type, fn): + self.subscribers[event_type].append(fn) - def _pop_module_event(self) -> None: - if len(self._module_events) > 0: - return self._module_events.pop(0) - return None - - - def push_gui_event(self, event: list) -> None: - if len(event) == 3: - self._gui_events.append(event) - return None - - raise EventSystemPushException("Invald event format! Please do: ['sender_id': str, method_target: method, (data,): any]") - - def push_module_event(self, event: list) -> None: - if len(event) == 3: - self._module_events.append(event) - return None - - raise EventSystemPushException("Invald event format! Please do: ['target_id': str, method_target: method, (data,): any]") - - def read_gui_event(self) -> list: - return self._gui_events[0] if self._gui_events else None - - def read_module_event(self) -> list: - return self._module_events[0] if self._module_events else None - - def consume_gui_event(self) -> list: - return self._pop_gui_event() - - def consume_module_event(self) -> list: - return self._pop_module_event() + def post_event(self, event_type, data): + if event_type in self.subscribers: + for fn in self.subscribers[event_type]: + if data: + if hasattr(data, '__iter__'): + fn(*data) + else: + fn(data) + else: + fn() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py index 1ddf584..c73dc0a 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py @@ -29,6 +29,11 @@ class IPCServer: elif conn_type == "local_network_unsecured": self._ipc_authkey = None + self._subscribe_to_events() + + def _subscribe_to_events(self): + event_system.subscribe("post_file_to_ipc", self.send_ipc_message) + @daemon_threaded def create_ipc_listener(self) -> None: @@ -60,7 +65,7 @@ class IPCServer: if "FILE|" in msg: file = msg.split("FILE|")[1].strip() if file: - event_system.push_gui_event([None, "handle_file_from_ipc", (file,)]) + event_system.post_event("handle_file_from_ipc", file) conn.close() break From bdd532060a364d701ed02401fa12cfd2ff42a76c Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Thu, 29 Sep 2022 17:32:35 -0500 Subject: [PATCH 03/30] Dgata arg check --- .../solarfm-0.0.1/SolarFM/solarfm/utils/event_system.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/event_system.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/event_system.py index 53aa64e..10b2765 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/event_system.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/event_system.py @@ -22,7 +22,7 @@ class EventSystem: if event_type in self.subscribers: for fn in self.subscribers[event_type]: if data: - if hasattr(data, '__iter__'): + if hasattr(data, '__iter__') and not type(data) is str: fn(*data) else: fn(data) From d3e42b3ae09ff596d97d317dc0dd19e41081d30e Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Fri, 30 Sep 2022 23:30:38 -0500 Subject: [PATCH 04/30] Added PIL support for images like webp; changed emit name --- plugins/favorites/plugin.py | 2 +- plugins/file_properties/plugin.py | 2 +- plugins/movie_tv_info/plugin.py | 4 +-- plugins/searcher/plugin.py | 2 +- plugins/template/plugin.py | 2 +- plugins/vod_thumbnailer/plugin.py | 2 +- plugins/youtube_download/plugin.py | 2 +- .../SolarFM/solarfm/core/controller.py | 4 +-- .../SolarFM/solarfm/core/controller_data.py | 2 +- .../shellfm/windows/tabs/icons/icon.py | 28 +++++++++++++++++-- .../SolarFM/solarfm/utils/event_system.py | 2 +- .../SolarFM/solarfm/utils/ipc_server.py | 2 +- 12 files changed, 38 insertions(+), 16 deletions(-) diff --git a/plugins/favorites/plugin.py b/plugins/favorites/plugin.py index aca3f63..3a68f05 100644 --- a/plugins/favorites/plugin.py +++ b/plugins/favorites/plugin.py @@ -78,7 +78,7 @@ class Plugin(PluginBase): @threaded def _get_state(self, widget=None, eve=None): - self._event_system.post_event("get_current_state", None) + self._event_system.emit("get_current_state @threaded diff --git a/plugins/file_properties/plugin.py b/plugins/file_properties/plugin.py index 5177943..2cb6262 100644 --- a/plugins/file_properties/plugin.py +++ b/plugins/file_properties/plugin.py @@ -107,7 +107,7 @@ class Plugin(PluginBase): @threaded def _show_properties_page(self, widget=None, eve=None): - event_system.post_event("get_current_state", None) + event_system.emit("get_current_state state = self._fm_state self._event_message = None diff --git a/plugins/movie_tv_info/plugin.py b/plugins/movie_tv_info/plugin.py index 64df1c6..f7f9c3c 100644 --- a/plugins/movie_tv_info/plugin.py +++ b/plugins/movie_tv_info/plugin.py @@ -77,7 +77,7 @@ class Plugin(PluginBase): @threaded def _show_info_page(self, widget=None, eve=None): - self._event_system.post_event("get_current_state", None) + self._event_system.emit("get_current_state") state = self._fm_state self._event_message = None @@ -111,6 +111,7 @@ class Plugin(PluginBase): path = self._fm_state.tab.get_current_directory() parts = uri.split("/") _title = parts[ len(parts) - 1 ] + trailer = None try: title = _title.split("(")[0].strip() @@ -136,7 +137,6 @@ class Plugin(PluginBase): raise Exception("No key found. Defering to none...") except Exception as e: print("No trailer found...") - trailer = None except Exception as e: print(repr(e)) diff --git a/plugins/searcher/plugin.py b/plugins/searcher/plugin.py index 9328a90..cc50574 100644 --- a/plugins/searcher/plugin.py +++ b/plugins/searcher/plugin.py @@ -125,7 +125,7 @@ class Plugin(PluginBase): @daemon_threaded def _show_grep_list_page(self, widget=None, eve=None): - self._event_system.post_event("get_current_state", None) + self._event_system.emit("get_current_state state = self._fm_state self._event_message = None diff --git a/plugins/template/plugin.py b/plugins/template/plugin.py index b99308a..dff5d40 100644 --- a/plugins/template/plugin.py +++ b/plugins/template/plugin.py @@ -43,4 +43,4 @@ class Plugin(PluginBase): def send_message(self, widget=None, eve=None): message = "Hello, World!" - event_system.post_event("display_message", ("warning", message, None)) + event_system.emit("display_message", ("warning", message, None)) diff --git a/plugins/vod_thumbnailer/plugin.py b/plugins/vod_thumbnailer/plugin.py index 3d6f164..c74f551 100644 --- a/plugins/vod_thumbnailer/plugin.py +++ b/plugins/vod_thumbnailer/plugin.py @@ -75,7 +75,7 @@ class Plugin(PluginBase): @threaded def _show_thumbnailer_page(self, widget=None, eve=None): - self._event_system.post_event("get_current_state", None) + self._event_system.emit("get_current_state state = self._fm_state self._event_message = None diff --git a/plugins/youtube_download/plugin.py b/plugins/youtube_download/plugin.py index 5ebf3b1..da13c75 100644 --- a/plugins/youtube_download/plugin.py +++ b/plugins/youtube_download/plugin.py @@ -44,7 +44,7 @@ class Plugin(PluginBase): def _do_download(self, widget=None, eve=None): - self._event_system.post_event("get_current_state", None) + self._event_system.emit("get_current_state dir = self._fm_state.tab.get_current_directory() self._download(dir) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py index 8d99d05..7a9c930 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py @@ -36,11 +36,11 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi for arg in unknownargs: if os.path.isdir(arg): message = f"FILE|{arg}" - event_system.post_event("post_file_to_ipc", message) + event_system.emit("post_file_to_ipc", message) if args.new_tab and os.path.isdir(args.new_tab): message = f"FILE|{args.new_tab}" - event_system.post_event("post_file_to_ipc", message) + event_system.emit("post_file_to_ipc", message) def _subscribe_to_events(self): diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py index 04a6656..ea44a63 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py @@ -154,7 +154,7 @@ class Controller_Data: # if self.to_cut_files: # state.to_cut_files = self.format_to_uris(state.store, state.wid, state.tid, self.to_cut_files, True) - event_system.post_event("update_state_info_plugins", state) + event_system.emit("update_state_info_plugins", state) return state diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/icon.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/icon.py index 3a18857..94a9b2e 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/icon.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/icon.py @@ -2,10 +2,17 @@ import os, subprocess, threading, hashlib from os.path import isfile -# Gtk imports +# Lib imports import gi gi.require_version('GdkPixbuf', '2.0') -from gi.repository import GdkPixbuf +from gi.repository import GdkPixbuf, GLib + + +try: + from PIL import Image as PImage +except Exception as e: + PImage = None + # Application imports from .mixins.desktopiconmixin import DesktopIconMixin @@ -67,12 +74,27 @@ class Icon(DesktopIconMixin, VideoIconMixin): .get_static_image() \ .scale_simple(wxh[0], wxh[1], GdkPixbuf.InterpType.BILINEAR) else: - return GdkPixbuf.Pixbuf.new_from_file_at_scale(path, wxh[0], wxh[1], True) + if PImage and path.lower().endswith(".webp"): + return self.image2pixbuf(path, wxh) + else: + return GdkPixbuf.Pixbuf.new_from_file_at_scale(path, wxh[0], wxh[1], True) except Exception as e: print("Image Scaling Issue:") print( repr(e) ) return None + def image2pixbuf(self, path, wxh): + """Convert Pillow image to GdkPixbuf""" + im = PImage.open(path) + data = im.tobytes() + data = GLib.Bytes.new(data) + w, h = im.size + + pixbuf = GdkPixbuf.Pixbuf.new_from_bytes(data, GdkPixbuf.Colorspace.RGB, + False, 8, w, h, w * 3) + + return pixbuf.scale_simple(wxh[0], wxh[1], 2) # BILINEAR = 2 + def create_from_file(self, path): try: return GdkPixbuf.Pixbuf.new_from_file(path) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/event_system.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/event_system.py index 10b2765..25c96fc 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/event_system.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/event_system.py @@ -18,7 +18,7 @@ class EventSystem: def subscribe(self, event_type, fn): self.subscribers[event_type].append(fn) - def post_event(self, event_type, data): + def emit(self, event_type, data = None): if event_type in self.subscribers: for fn in self.subscribers[event_type]: if data: diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py index c73dc0a..f71691b 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py @@ -65,7 +65,7 @@ class IPCServer: if "FILE|" in msg: file = msg.split("FILE|")[1].strip() if file: - event_system.post_event("handle_file_from_ipc", file) + event_system.emit("handle_file_from_ipc", file) conn.close() break From da63e6e44e75d29abcdcbc8b402272b74a010c7f Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sat, 1 Oct 2022 16:04:46 -0500 Subject: [PATCH 05/30] Moved trash logic to plugin structure --- plugins/trasher/__init__.py | 3 + plugins/trasher/__main__.py | 3 + plugins/trasher/manifest.json | 12 + plugins/trasher/plugin.py | 115 +++ .../solarfm => plugins}/trasher/trash.py | 0 plugins/trasher/trasher.glade | 126 ++++ .../solarfm => plugins}/trasher/xdgtrash.py | 0 .../SolarFM/solarfm/core/controller.py | 20 +- .../SolarFM/solarfm/core/controller_data.py | 6 +- .../mixins/ui/widget_file_action_mixin.py | 39 - .../solarfm/core/mixins/ui/window_mixin.py | 7 +- .../SolarFM/solarfm/trasher/__init__.py | 3 - .../usr/share/solarfm/Main_Window.glade | 694 +++++++----------- 13 files changed, 556 insertions(+), 472 deletions(-) create mode 100644 plugins/trasher/__init__.py create mode 100644 plugins/trasher/__main__.py create mode 100644 plugins/trasher/manifest.json create mode 100644 plugins/trasher/plugin.py rename {src/versions/solarfm-0.0.1/SolarFM/solarfm => plugins}/trasher/trash.py (100%) create mode 100644 plugins/trasher/trasher.glade rename {src/versions/solarfm-0.0.1/SolarFM/solarfm => plugins}/trasher/xdgtrash.py (100%) delete mode 100755 src/versions/solarfm-0.0.1/SolarFM/solarfm/trasher/__init__.py diff --git a/plugins/trasher/__init__.py b/plugins/trasher/__init__.py new file mode 100644 index 0000000..d36fa8c --- /dev/null +++ b/plugins/trasher/__init__.py @@ -0,0 +1,3 @@ +""" + Pligin Module +""" diff --git a/plugins/trasher/__main__.py b/plugins/trasher/__main__.py new file mode 100644 index 0000000..a576329 --- /dev/null +++ b/plugins/trasher/__main__.py @@ -0,0 +1,3 @@ +""" + Pligin Package +""" diff --git a/plugins/trasher/manifest.json b/plugins/trasher/manifest.json new file mode 100644 index 0000000..e6f0c35 --- /dev/null +++ b/plugins/trasher/manifest.json @@ -0,0 +1,12 @@ +{ + "manifest": { + "name": "Trasher", + "author": "ITDominator", + "version": "0.0.1", + "support": "", + "requests": { + "ui_target": "context_menu", + "pass_fm_events": "true" + } + } +} diff --git a/plugins/trasher/plugin.py b/plugins/trasher/plugin.py new file mode 100644 index 0000000..cbfe70d --- /dev/null +++ b/plugins/trasher/plugin.py @@ -0,0 +1,115 @@ +# Python imports +import os, threading, subprocess, inspect + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk, GLib, Gio + +# Application imports +from plugins.plugin_base import PluginBase +from .xdgtrash import XDGTrash + + +# NOTE: Threads WILL NOT die with parent's destruction. +def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() + return wrapper + +# NOTE: Threads WILL die with parent's destruction. +def daemon_threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() + return wrapper + + + + +class Plugin(PluginBase): + def __init__(self): + super().__init__() + self.path = os.path.dirname(os.path.realpath(__file__)) + self._GLADE_FILE = f"{self.path}/trasher.glade" + self.name = "Trasher" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus + # where self.name should not be needed for message comms + self.trashman = XDGTrash() + self.trash_files_path = f"{GLib.get_user_data_dir()}/Trash/files" + self.trash_info_path = f"{GLib.get_user_data_dir()}/Trash/info" + + self.trashman.regenerate() + + def get_ui_element(self): + self._builder = Gtk.Builder() + self._builder.add_from_file(self._GLADE_FILE) + + classes = [self] + handlers = {} + for c in classes: + methods = None + try: + methods = inspect.getmembers(c, predicate=inspect.ismethod) + handlers.update(methods) + except Exception as e: + print(repr(e)) + + self._builder.connect_signals(handlers) + + trasher = self._builder.get_object("trasher") + trasher.show_all() + + return trasher + + + def run(self): + self._event_system.subscribe("show_trash_buttons", self._show_trash_buttons) + self._event_system.subscribe("hide_trash_buttons", self._hide_trash_buttons) + + def _show_trash_buttons(self): + self._builder.get_object("restore_from_trash").show() + self._builder.get_object("empty_trash").show() + + def _hide_trash_buttons(self): + self._builder.get_object("restore_from_trash").hide() + self._builder.get_object("empty_trash").hide() + + def delete_files(self, widget = None, eve = None): + self._event_system.emit("get_current_state") + state = self._fm_state + uris = state.selected_files + response = None + + state.warning_alert.format_secondary_text(f"Do you really want to delete the {len(uris)} file(s)?") + for uri in uris: + file = Gio.File.new_for_path(uri) + + if not response: + response = state.warning_alert.run() + state.warning_alert.hide() + if response == Gtk.ResponseType.YES: + type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE) + + if type == Gio.FileType.DIRECTORY: + state.tab.delete_file( file.get_path() ) + else: + file.delete(cancellable=None) + else: + break + + def trash_files(self, widget = None, eve = None, verbocity = False): + self._event_system.emit("get_current_state") + state = self._fm_state + for uri in state.selected_files: + self.trashman.trash(uri, verbocity) + + def restore_trash_files(self, widget = None, eve = None, verbocity = False): + self._event_system.emit("get_current_state") + state = self._fm_state + for uri in state.selected_files: + self.trashman.restore(filename=uri.split("/")[-1], verbose = verbocity) + + def empty_trash(self, widget = None, eve = None, verbocity = False): + self.trashman.empty(verbose = verbocity) + + def go_to_trash(self, widget = None, eve = None, verbocity = False): + self._event_system.emit("go_to_path", self.trash_files_path) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/trasher/trash.py b/plugins/trasher/trash.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/trasher/trash.py rename to plugins/trasher/trash.py diff --git a/plugins/trasher/trasher.glade b/plugins/trasher/trasher.glade new file mode 100644 index 0000000..54837b9 --- /dev/null +++ b/plugins/trasher/trasher.glade @@ -0,0 +1,126 @@ + + + + + + True + False + user-trash + + + True + False + user-trash + + + True + True + 5 + 10 + True + + + True + False + vertical + + + Restore From Trash + restore_from_trash + True + True + True + Restore From Trash... + + + + False + True + 0 + + + + + Empty Trash + empty_trash + True + True + True + Empty Trash... + + + + False + True + 1 + + + + + Trash + trash + True + True + True + Move to Trash... + 20 + trash_img + True + + + + False + True + 2 + + + + + Go To Trash + go_to_trash + True + True + True + Go To Trash... + trash_img2 + True + + + + False + True + 3 + + + + + gtk-delete + delete + True + True + True + Delete... + 20 + True + True + + + + False + True + 4 + + + + + + + True + False + False + Trash + center + + + + diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/trasher/xdgtrash.py b/plugins/trasher/xdgtrash.py similarity index 100% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/trasher/xdgtrash.py rename to plugins/trasher/xdgtrash.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py index 7a9c930..c034bd3 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py @@ -47,6 +47,8 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi event_system.subscribe("handle_file_from_ipc", self.handle_file_from_ipc) event_system.subscribe("get_current_state", self.get_current_state) event_system.subscribe("display_message", self.display_message) + event_system.subscribe("go_to_path", self.go_to_path) + event_system.subscribe("do_hide_context_menu", self.do_hide_context_menu) def tear_down(self, widget=None, eve=None): self.fm_controller.save_state() @@ -100,7 +102,7 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi gc.collect() - def do_action_from_menu_controls(self, widget, event_button): + def do_action_from_menu_controls(self, widget, eve = None): action = widget.get_name() self.hide_context_menu() self.hide_new_file_menu() @@ -124,16 +126,6 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi self.paste_files() if action == "archive": self.show_archiver_dialogue() - if action == "delete": - self.delete_files() - if action == "trash": - self.trash_files() - if action == "go_to_trash": - self.path_entry.set_text(self.trash_files_path) - if action == "restore_from_trash": - self.restore_trash_files() - if action == "empty_trash": - self.empty_trash() if action == "create": self.create_files() if action in ["save_session", "save_session_as", "load_session"]: @@ -169,3 +161,9 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi wid, tid = self.fm_controller.get_active_wid_and_tid() tab = self.get_fm_window(wid).get_tab_by_id(tid) tab.execute([f"{tab.terminal_app}"], start_dir=tab.get_current_directory()) + + def go_to_path(self, path): + self.path_entry.set_text(path) + + def do_hide_context_menu(self): + self.hide_context_menu() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py index ea44a63..3e69f9f 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py @@ -7,7 +7,6 @@ import gi from gi.repository import GLib # Application imports -from trasher.xdgtrash import XDGTrash from shellfm.windows.controller import WindowController from plugins.plugins_controller import PluginsController @@ -22,6 +21,7 @@ class State: selected_files: [] = None to_copy_files: [] = None to_cut_files: [] = None + warning_alert: type = None class Controller_Data: @@ -34,11 +34,9 @@ class Controller_Data: self.logger = self.settings.get_logger() self.keybindings = self.settings.get_keybindings() - self.trashman = XDGTrash() self.fm_controller = WindowController() self.plugins = PluginsController(_settings) self.fm_controller_data = self.fm_controller.get_state_from_file() - self.trashman.regenerate() self.window = self.settings.get_main_window() self.window1 = self.builder.get_object("window_1") @@ -142,6 +140,7 @@ class Controller_Data: state.tab = self.get_fm_window(state.wid).get_tab_by_id(state.tid) state.icon_grid = self.builder.get_object(f"{state.wid}|{state.tid}|icon_grid") state.store = state.icon_grid.get_model() + state.warning_alert = self.warning_alert selected_files = state.icon_grid.get_selected_items() @@ -155,7 +154,6 @@ class Controller_Data: # state.to_cut_files = self.format_to_uris(state.store, state.wid, state.tid, self.to_cut_files, True) event_system.emit("update_state_info_plugins", state) - return state 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 9072990..0058147 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 @@ -208,45 +208,6 @@ class WidgetFileActionMixin: elif self.to_cut_files: self.handle_files(self.to_cut_files, "move", target) - def delete_files(self): - state = self.get_current_state() - uris = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True) - response = None - - self.warning_alert.format_secondary_text(f"Do you really want to delete the {len(uris)} file(s)?") - for uri in uris: - file = Gio.File.new_for_path(uri) - - if not response: - response = self.warning_alert.run() - self.warning_alert.hide() - if response == Gtk.ResponseType.YES: - type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE) - - if type == Gio.FileType.DIRECTORY: - state.tab.delete_file( file.get_path() ) - else: - file.delete(cancellable=None) - else: - break - - - def trash_files(self): - state = self.get_current_state() - uris = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True) - for uri in uris: - self.trashman.trash(uri, False) - - def restore_trash_files(self): - state = self.get_current_state() - uris = self.format_to_uris(state.store, state.wid, state.tid, self.selected_files, True) - for uri in uris: - self.trashman.restore(filename=uri.split("/")[-1], verbose=False) - - def empty_trash(self): - self.trashman.empty(verbose=False) - - def create_files(self): fname_field = self.builder.get_object("new_fname_field") self.show_new_file_menu(fname_field) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py index 1d22380..513182e 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py @@ -88,12 +88,11 @@ class WindowMixin(TabMixin): formatted_mount_free = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::free")) ) formatted_mount_size = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::size")) ) + # NOTE: Hides empty trash and other desired buttons based on context. if self.trash_files_path == current_directory: - self.builder.get_object("restore_from_trash").show() - self.builder.get_object("empty_trash").show() + event_system.emit("show_trash_buttons") else: - self.builder.get_object("restore_from_trash").hide() - self.builder.get_object("empty_trash").hide() + event_system.emit("hide_trash_buttons") # If something selected self.bottom_size_label.set_label(f"{formatted_mount_free} free / {formatted_mount_size}") diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/trasher/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/trasher/__init__.py deleted file mode 100755 index 5c16e50..0000000 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/trasher/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -Trasher module -""" diff --git a/user_config/usr/share/solarfm/Main_Window.glade b/user_config/usr/share/solarfm/Main_Window.glade index 2bf3bd8..2343d0c 100644 --- a/user_config/usr/share/solarfm/Main_Window.glade +++ b/user_config/usr/share/solarfm/Main_Window.glade @@ -668,6 +668,289 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False gtk-edit + + False + False + mouse + splashscreen + True + False + False + static + + + + False + vertical + 2 + + + False + end + + + + + + + + + False + False + 0 + + + + + True + False + vertical + + + True + True + 5 + 5 + True + True + + + True + False + vertical + + + gtk-open + open + True + True + True + Open... + True + + + + False + True + 0 + + + + + Open With + open_with + True + True + True + open_with_img + True + + + + False + True + 1 + + + + + gtk-execute + execute + True + True + True + True + + + + False + True + 2 + + + + + Execute in Terminal + execute_in_terminal + True + True + True + exec_in_term_img + + + + False + True + 3 + + + + + + + True + False + False + Open + center + + + + + False + True + 4 + + + + + True + True + 5 + 5 + True + True + + + True + False + vertical + + + gtk-new + create + True + True + True + New File/Folder... + True + + + + False + True + 0 + + + + + Rename + rename + True + True + True + Rename... + rename_img2 + True + + + + False + True + 1 + + + + + gtk-cut + cut + True + True + True + Cut... + True + True + + + + False + True + 2 + + + + + gtk-copy + copy + True + True + True + Copy... + True + True + + + + False + True + 3 + + + + + gtk-paste + paste + True + True + True + Paste... + True + True + + + + False + True + 4 + + + + + Archive + archive + True + True + True + Archive... + archive_img + True + + + + False + True + 5 + + + + + + + True + False + False + File Actions + center + end + + + + + False + True + 5 + + + + + False + True + 1 + + + + + False dialog @@ -2321,417 +2604,6 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe - - True - False - user-trash - - - True - False - user-trash - - - False - False - mouse - splashscreen - True - False - False - static - - - - False - vertical - 2 - - - False - end - - - - - - - - - False - False - 0 - - - - - True - False - vertical - - - True - True - 5 - 5 - True - True - - - True - False - vertical - - - gtk-open - open - True - True - True - Open... - True - - - - False - True - 0 - - - - - Open With - open_with - True - True - True - open_with_img - True - - - - False - True - 1 - - - - - gtk-execute - execute - True - True - True - True - - - - False - True - 2 - - - - - Execute in Terminal - execute_in_terminal - True - True - True - exec_in_term_img - - - - False - True - 3 - - - - - - - True - False - False - Open - center - - - - - False - True - 4 - - - - - True - True - 5 - 5 - True - True - - - True - False - vertical - - - gtk-new - create - True - True - True - New File/Folder... - True - - - - False - True - 0 - - - - - Rename - rename - True - True - True - Rename... - rename_img2 - True - - - - False - True - 1 - - - - - gtk-cut - cut - True - True - True - Cut... - True - True - - - - False - True - 2 - - - - - gtk-copy - copy - True - True - True - Copy... - True - True - - - - False - True - 3 - - - - - gtk-paste - paste - True - True - True - Paste... - True - True - - - - False - True - 4 - - - - - Archive - archive - True - True - True - Archive... - archive_img - True - - - - False - True - 5 - - - - - - - True - False - False - File Actions - center - end - - - - - False - True - 5 - - - - - True - True - 5 - 10 - True - - - True - False - vertical - - - Restore From Trash - restore_from_trash - True - True - True - Restore From Trash... - - - - False - True - 0 - - - - - Empty Trash - empty_trash - True - True - True - Empty Trash... - - - - False - True - 1 - - - - - Trash - trash - True - True - True - Move to Trash... - 20 - trash_img - True - - - - False - True - 2 - - - - - Go To Trash - go_to_trash - True - True - True - Go To Trash... - trash_img2 - True - - - - False - True - 3 - - - - - gtk-delete - delete - True - True - True - Delete... - 20 - True - True - - - - False - True - 4 - - - - - - - True - False - False - Trash - center - - - - - False - True - 6 - - - - - False - True - 1 - - - - - False False From 72f0236e58b0e07fd1e1761f3304e10ea7177087 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sat, 1 Oct 2022 19:08:05 -0500 Subject: [PATCH 06/30] Added submodule search logic --- .../SolarFM/solarfm/plugins/plugins_controller.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins_controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins_controller.py index 11f9dda..63cccfe 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins_controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins_controller.py @@ -72,13 +72,23 @@ class PluginsController: def load_plugin_module(self, path, folder, target): os.chdir(path) - spec = importlib.util.spec_from_file_location(folder, target, submodule_search_locations=path) + + locations = [] + self.collect_search_locations(path, locations) + + spec = importlib.util.spec_from_file_location(folder, target, submodule_search_locations = locations) module = importlib.util.module_from_spec(spec) sys.modules[folder] = module spec.loader.exec_module(module) return module + def collect_search_locations(self, path, locations): + locations.append(path) + for file in os.listdir(path): + _path = os.path.join(path, file) + if os.path.isdir(_path): + self.collect_search_locations(_path, locations) def execute_plugin(self, module: type, plugin: PluginInfo, loading_data: []): plugin.reference = module.Plugin() From dc9cae6d3838d6809ae1f5eebf1657b1efe52265 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sat, 1 Oct 2022 22:12:14 -0500 Subject: [PATCH 07/30] Fixed keyboard events, updated exception types --- plugins/trasher/manifest.json | 6 ++- .../solarfm-0.0.1/SolarFM/solarfm/app.py | 10 ++--- .../solarfm/core/mixins/ui/window_mixin.py | 13 +++--- .../core/signals/keyboard_signals_mixin.py | 7 +++- .../shellfm/windows/tabs/icons/icon.py | 40 +++++++++++-------- .../usr/share/solarfm/key-bindings.json | 3 -- 6 files changed, 46 insertions(+), 33 deletions(-) diff --git a/plugins/trasher/manifest.json b/plugins/trasher/manifest.json index e6f0c35..0657d65 100644 --- a/plugins/trasher/manifest.json +++ b/plugins/trasher/manifest.json @@ -6,7 +6,11 @@ "support": "", "requests": { "ui_target": "context_menu", - "pass_fm_events": "true" + "pass_fm_events": "true", + "bind_keys": [ + "Trasher||delete_files:Delete", + "Trasher||trash_files:d" + ] } } } 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 cd89d84..a16c779 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py @@ -10,10 +10,10 @@ from utils.settings import Settings from core.controller import Controller -class App_Launch_Exception(Exception): +class AppLaunchException(Exception): ... -class Controller_Start_Exceptio(Exception): +class ControllerStartExceptio(Exception): ... @@ -38,7 +38,7 @@ class Application(IPCServer): message = f"FILE|{args.new_tab}" self.send_ipc_message(message) - raise App_Launch_Exception(f"IPC Server Exists: Will send path(s) to it and close...\nNote: If no fm exists, remove /tmp/{app_name}-ipc.sock") + raise AppLaunchException(f"IPC Server Exists: Will send path(s) to it and close...\nNote: If no fm exists, remove /tmp/{app_name}-ipc.sock") settings = Settings() @@ -46,7 +46,7 @@ class Application(IPCServer): controller = Controller(args, unknownargs, settings) if not controller: - raise Controller_Start_Exceptio("Controller exited and doesn't exist...") + raise ControllerStartExceptio("Controller exited and doesn't exist...") # Gets the methods from the classes and sets to handler. # Then, builder connects to any signals it needs. @@ -57,7 +57,7 @@ class Application(IPCServer): try: methods = inspect.getmembers(c, predicate=inspect.ismethod) handlers.update(methods) - except Exception as e: + except AppLaunchException as e: print(repr(e)) settings.get_builder().connect_signals(handlers) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py index 513182e..2ab6be1 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py @@ -12,6 +12,10 @@ from gi.repository import Gdk, Gio from .tab_mixin import TabMixin +class WindowException(Exception): + ... + + class WindowMixin(TabMixin): """docstring for WindowMixin""" @@ -46,7 +50,7 @@ class WindowMixin(TabMixin): icon_grid.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE)) icon_grid.event(Gdk.Event().new(type=Gdk.EventType.BUTTON_RELEASE)) - except Exception as e: + except WindowException as e: print("\n: The saved session might be missing window data! :\nLocation: ~/.config/solarfm/session.json\nFix: Back it up and delete it to reset.\n") print(repr(e)) else: @@ -107,7 +111,7 @@ class WindowMixin(TabMixin): cancellable=None) file_size = file_info.get_size() combined_size += file_size - except Exception as e: + except WindowException as e: if debug: print(repr(e)) @@ -168,14 +172,13 @@ class WindowMixin(TabMixin): self.set_path_text(wid, tid) self.set_window_title() - if eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 1: # l-click if self.single_click_open: # FIXME: need to find a way to pass the model index self.grid_icon_double_click(icons_grid) elif eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 3: # r-click self.show_context_menu() - except Exception as e: + except WindowException as e: print(repr(e)) self.display_message(self.error_color, f"{repr(e)}") @@ -204,7 +207,7 @@ class WindowMixin(TabMixin): self.update_tab(tab_label, state.tab, state.store, state.wid, state.tid) else: self.open_files() - except Exception as e: + except WindowException as e: traceback.print_exc() self.display_message(self.error_color, f"{repr(e)}") 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 e5f70e8..e4151f8 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 @@ -52,8 +52,8 @@ class KeyboardSignalsMixin: return True except Exception: # Must be plugins scope or we forgot to add method to file manager scope - sender, method_target = mapping.split("||") - self.handle_plugin_key_event(sender, method_target) + sender, eve_type = mapping.split("||") + self.handle_plugin_key_event(sender, eve_type) else: if debug: print(f"on_global_key_release_controller > key > {keyname}") @@ -77,6 +77,9 @@ class KeyboardSignalsMixin: return True + def handle_plugin_key_event(self, sender, eve_type): + event_system.emit(eve_type) + def keyboard_close_tab(self): wid, tid = self.fm_controller.get_active_wid_and_tid() notebook = self.builder.get_object(f"window_{wid}") diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/icon.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/icon.py index 94a9b2e..0d392ac 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/icon.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/icon.py @@ -42,11 +42,14 @@ class Icon(DesktopIconMixin, VideoIconMixin): thumbnl = self.parse_desktop_files(full_path) return thumbnl - except Exception as e: - return None + except Exception: + ... + + return None def create_thumbnail(self, dir, file, scrub_percent = "65%"): full_path = f"{dir}/{file}" + try: file_hash = hashlib.sha256(str.encode(full_path)).hexdigest() hash_img_pth = f"{self.ABS_THUMBS_PTH}/{file_hash}.jpg" @@ -61,27 +64,29 @@ class Icon(DesktopIconMixin, VideoIconMixin): except Exception as e: print("Thumbnail generation issue:") print( repr(e) ) - return GdkPixbuf.Pixbuf.new_from_file(f"{self.DEFAULT_ICONS}/video.png") + + return GdkPixbuf.Pixbuf.new_from_file(f"{self.DEFAULT_ICONS}/video.png") def create_scaled_image(self, path, wxh = None): if not wxh: wxh = self.video_icon_wh - try: - if path.lower().endswith(".gif"): - return GdkPixbuf.PixbufAnimation.new_from_file(path) \ - .get_static_image() \ - .scale_simple(wxh[0], wxh[1], GdkPixbuf.InterpType.BILINEAR) - else: - if PImage and path.lower().endswith(".webp"): + if path: + try: + if path.lower().endswith(".gif"): + return GdkPixbuf.PixbufAnimation.new_from_file(path) \ + .get_static_image() \ + .scale_simple(wxh[0], wxh[1], GdkPixbuf.InterpType.BILINEAR) + elif path.lower().endswith(".webp") and PImage: return self.image2pixbuf(path, wxh) - else: - return GdkPixbuf.Pixbuf.new_from_file_at_scale(path, wxh[0], wxh[1], True) - except Exception as e: - print("Image Scaling Issue:") - print( repr(e) ) - return None + + return GdkPixbuf.Pixbuf.new_from_file_at_scale(path, wxh[0], wxh[1], True) + except Exception as e: + print("Image Scaling Issue:") + print( repr(e) ) + + return None def image2pixbuf(self, path, wxh): """Convert Pillow image to GdkPixbuf""" @@ -101,7 +106,8 @@ class Icon(DesktopIconMixin, VideoIconMixin): except Exception as e: print("Image from file Issue:") print( repr(e) ) - return None + + return None def return_generic_icon(self): return GdkPixbuf.Pixbuf.new_from_file(self.DEFAULT_ICON) diff --git a/user_config/usr/share/solarfm/key-bindings.json b/user_config/usr/share/solarfm/key-bindings.json index bd36c53..f3b10a0 100644 --- a/user_config/usr/share/solarfm/key-bindings.json +++ b/user_config/usr/share/solarfm/key-bindings.json @@ -6,10 +6,7 @@ "open_terminal" : "F4", "refresh_tab" : ["F5", "r"], - "delete_files" : ["Delete", - "d"], "tggl_top_main_menubar" : "h", - "trash_files" : "t", "tear_down" : "q", "go_up" : "Up", "go_home" : "slash", From f48d84a0048336ab595461ed0e53893b566e4164 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Mon, 3 Oct 2022 00:52:50 -0500 Subject: [PATCH 08/30] Reworked the search logic --- plugins/searcher/plugin.py | 189 ++++++++++++++------------- plugins/searcher/search.py | 103 +++++++++++++++ plugins/searcher/search_dialog.glade | 4 +- 3 files changed, 204 insertions(+), 92 deletions(-) create mode 100755 plugins/searcher/search.py diff --git a/plugins/searcher/plugin.py b/plugins/searcher/plugin.py index cc50574..3a2ae7c 100644 --- a/plugins/searcher/plugin.py +++ b/plugins/searcher/plugin.py @@ -1,16 +1,17 @@ # Python imports -import os, multiprocessing, threading, subprocess, inspect, time, json -from multiprocessing import Manager, Process +import os, threading, subprocess, inspect, time, json, base64, shlex, select, signal, pickle # Lib imports import gi gi.require_version('Gtk', '3.0') -from gi.repository import Gtk, GLib, GObject +from gi.repository import Gtk, GLib # Application imports from plugins.plugin_base import PluginBase + + # NOTE: Threads WILL NOT die with parent's destruction. def threaded(fn): def wrapper(*args, **kwargs): @@ -35,19 +36,21 @@ class FilePreviewWidget(Gtk.LinkButton): class GrepPreviewWidget(Gtk.Box): - def __init__(self, path, sub_keys, data): + def __init__(self, _path, sub_keys, data): super(GrepPreviewWidget, self).__init__() self.set_orientation(Gtk.Orientation.VERTICAL) self.line_color = "#e0cc64" - + path = base64.urlsafe_b64decode(_path.encode('utf-8')).decode('utf-8') _label = '/'.join( path.split("/")[-3:] ) title = Gtk.LinkButton.new_with_label(uri=f"file://{path}", label=_label) self.add(title) for key in sub_keys: line_num = key - text = data[key] + text = base64.urlsafe_b64decode(data[key].encode('utf-8')).decode('utf-8') + + box = Gtk.Box() number_label = Gtk.Label() text_view = Gtk.Label(label=text[:-1]) @@ -69,11 +72,7 @@ class GrepPreviewWidget(Gtk.Box): self.show_all() - -manager = Manager() -grep_result_set = manager.dict() -file_result_set = manager.list() - +pause_fifo_update = False class Plugin(PluginBase): def __init__(self): @@ -84,6 +83,9 @@ class Plugin(PluginBase): # where self.name should not be needed for message comms self._GLADE_FILE = f"{self.path}/search_dialog.glade" + self._files_fifo_file = f"/tmp/search_files_fifo" + self._grep_fifo_file = f"/tmp/grep_files_fifo" + self._search_dialog = None self._active_path = None self._file_list = None @@ -116,113 +118,118 @@ class Plugin(PluginBase): self._search_dialog = self._builder.get_object("search_dialog") self._grep_list = self._builder.get_object("grep_list") self._file_list = self._builder.get_object("file_list") + self.fsearch = self._builder.get_object("fsearch") - GObject.signal_new("update-file-ui-signal", self._search_dialog, GObject.SIGNAL_RUN_LAST, GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,)) - self._search_dialog.connect("update-file-ui-signal", self._load_file_ui) - GObject.signal_new("update-grep-ui-signal", self._search_dialog, GObject.SIGNAL_RUN_LAST, GObject.TYPE_PYOBJECT, (GObject.TYPE_PYOBJECT,)) - self._search_dialog.connect("update-grep-ui-signal", self._load_grep_ui) + self._event_system.subscribe("update-file-ui", self._load_file_ui) + self._event_system.subscribe("update-grep-ui", self._load_grep_ui) + + if not os.path.exists(self._files_fifo_file): + os.mkfifo(self._files_fifo_file, 0o777) + if not os.path.exists(self._grep_fifo_file): + os.mkfifo(self._grep_fifo_file, 0o777) + + self.run_files_fifo_thread() + self.run_grep_fifo_thread() @daemon_threaded + def run_files_fifo_thread(self): + with open(self._files_fifo_file) as fifo: + while True: + select.select([fifo],[],[fifo]) + data = fifo.read() + GLib.idle_add(self._load_file_ui, data) + + @daemon_threaded + def run_grep_fifo_thread(self): + with open(self._grep_fifo_file) as fifo: + while True: + select.select([fifo],[],[fifo]) + data = fifo.read() + GLib.idle_add(self._load_grep_ui, data) + + def _show_grep_list_page(self, widget=None, eve=None): - self._event_system.emit("get_current_state + self._event_system.emit("get_current_state") state = self._fm_state self._event_message = None - GLib.idle_add(self._process_queries, (state)) - - def _process_queries(self, state): self._active_path = state.tab.get_current_directory() response = self._search_dialog.run() self._search_dialog.hide() def _run_find_file_query(self, widget=None, eve=None): - if self._list_proc: - self._list_proc.terminate() - self._list_proc = None - time.sleep(.2) - - del file_result_set[:] - self.clear_children(self._file_list) + self._stop_find_file_query() query = widget.get_text() - if query: - self._list_proc = multiprocessing.Process(self._do_list_search(self._active_path, query)) - self._list_proc.start() + if not query in ("", None): + target_dir = shlex.quote( self._fm_state.tab.get_current_directory() ) + # command = [f"{self.path}/search.sh", "-t", "file_search", "-d", f"{target_dir}", "-q", f"{query}"] + command = ["python", f"{self.path}/search.py", "-t", "file_search", "-d", f"{target_dir}", "-q", f"{query}"] - def _do_list_search(self, path, query): - self._file_traverse_path(path, query) - for target, file in file_result_set: - widget = FilePreviewWidget(target, file) - self._search_dialog.emit("update-file-ui-signal", (widget)) + process = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None) - def _load_file_ui(self, parent=None, widget=None): - self._file_list.add(widget) + def _stop_find_file_query(self, widget=None, eve=None): + pause_fifo_update = True - def _file_traverse_path(self, path, query): - try: - for file in os.listdir(path): - target = os.path.join(path, file) - if os.path.isdir(target): - self._file_traverse_path(target, query) - else: - if query.lower() in file.lower(): - file_result_set.append([target, file]) - except Exception as e: - if debug: - print("Couldn't traverse to path. Might be permissions related...") + if self._list_proc: + if self._list_proc.poll(): + self._list_proc.send_signal(signal.SIGKILL) + while self._list_proc.poll(): + pass + + self._list_proc = None + else: + self._list_proc = None + + self.clear_children(self._file_list) + pause_fifo_update = False def _run_grep_query(self, widget=None, eve=None): - if self._grep_proc: - self._grep_proc.terminate() - self._grep_proc = None - time.sleep(.2) - - grep_result_set.clear() - self.clear_children(self._grep_list) + self._stop_grep_query() query = widget.get_text() - if query: - self._grep_proc = multiprocessing.Process(self._do_grep_search(self._active_path, query)) - self._grep_proc.start() + if not query in ("", None): + target_dir = shlex.quote( self._fm_state.tab.get_current_directory() ) + command = ["python", f"{self.path}/search.py", "-t", "grep_search", "-d", f"{target_dir}", "-q", f"{query}"] + process = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None) - def _do_grep_search(self, path, query): - self._grep_traverse_path(path, query) + def _stop_grep_query(self, widget=None, eve=None): + pause_fifo_update = True - keys = grep_result_set.keys() - for key in keys: - sub_keys = grep_result_set[key].keys() - widget = GrepPreviewWidget(key, sub_keys, grep_result_set[key]) - self._search_dialog.emit("update-grep-ui-signal", (widget)) + if self._grep_proc: + if self._grep_proc.poll(): + self._grep_proc.send_signal(signal.SIGKILL) + while self._grep_proc.poll(): + pass - def _load_grep_ui(self, parent=None, widget=None): - self._grep_list.add(widget) + self._grep_proc = None + else: + self._grep_proc = None - def _grep_traverse_path(self, path, query): - try: - for file in os.listdir(path): - target = os.path.join(path, file) - if os.path.isdir(target): - self._grep_traverse_path(target, query) - else: - self._search_for_string(target, query) - except Exception as e: - if debug: - print("Couldn't traverse to path. Might be permissions related...") + self.clear_children(self._grep_list) + pause_fifo_update = False - def _search_for_string(self, file, query): - try: - with open(file, 'r') as fp: - for i, line in enumerate(fp): - if query in line: - if f"{file}" in grep_result_set.keys(): - grep_result_set[f"{file}"][f"{i+1}"] = line - else: - grep_result_set[f"{file}"] = {} - grep_result_set[f"{file}"] = {f"{i+1}": line} - except Exception as e: - if debug: - print("Couldn't read file. Might be binary or other cause...") + + def _load_file_ui(self, data): + if not data in ("", None) and not pause_fifo_update: + jdata = json.loads( data ) + target = jdata[0] + file = jdata[1] + + widget = FilePreviewWidget(target, file) + self._file_list.add(widget) + + def _load_grep_ui(self, data): + if not data in ("", None) and not pause_fifo_update: + jdata = json.loads( data ) + jkeys = jdata.keys() + for key in jkeys: + sub_keys = jdata[key].keys() + grep_result = jdata[key] + + widget = GrepPreviewWidget(key, sub_keys, grep_result) + self._grep_list.add(widget) diff --git a/plugins/searcher/search.py b/plugins/searcher/search.py new file mode 100755 index 0000000..5756dfe --- /dev/null +++ b/plugins/searcher/search.py @@ -0,0 +1,103 @@ +#!/usr/bin/python3 + + +# Python imports +import os, traceback, argparse, time, json, base64 +from setproctitle import setproctitle + +# Lib imports + +# Application imports + + + +_files_fifo_file = f"/tmp/search_files_fifo" +_grep_fifo_file = f"/tmp/grep_files_fifo" + +filter = (".mkv", ".mp4", ".webm", ".avi", ".mov", ".m4v", ".mpg", ".mpeg", ".wmv", ".flv") + \ + (".png", ".jpg", ".jpeg", ".gif", ".ico", ".tga", ".webp") + \ + (".psf", ".mp3", ".ogg", ".flac", ".m4a") + +file_result_set = [] + + +def file_search(fifo, path, query): + try: + for file in os.listdir(path): + target = os.path.join(path, file) + if os.path.isdir(target): + file_search(fifo, target, query) + else: + if query.lower() in file.lower(): + # file_result_set.append([target, file]) + data = json.dumps([target, file]) + fifo.write(data) + time.sleep(0.01) + except Exception as e: + print("Couldn't traverse to path. Might be permissions related...") + traceback.print_exc() + +def _search_for_string(file, query): + try: + b64_file = base64.urlsafe_b64encode(file.encode('utf-8')).decode('utf-8') + grep_result_set = {} + + with open(file, 'r') as fp: + for i, line in enumerate(fp): + if query in line: + b64_line = base64.urlsafe_b64encode(line.encode('utf-8')).decode('utf-8') + + if f"{b64_file}" in grep_result_set.keys(): + grep_result_set[f"{b64_file}"][f"{i+1}"] = b64_line + else: + grep_result_set[f"{b64_file}"] = {} + grep_result_set[f"{b64_file}"] = {f"{i+1}": b64_line} + + # NOTE: Push to fifo here after loop + with open(_grep_fifo_file, 'w') as fifo: + data = json.dumps(grep_result_set) + fifo.write(data) + time.sleep(0.05) + except Exception as e: + print("Couldn't read file. Might be binary or other cause...") + traceback.print_exc() + + +def grep_search(path, query): + try: + for file in os.listdir(path): + target = os.path.join(path, file) + if os.path.isdir(target): + grep_search(target, query) + else: + if not target.lower().endswith(filter): + _search_for_string(target, query) + except Exception as e: + print("Couldn't traverse to path. Might be permissions related...") + traceback.print_exc() + +def search(args): + if args.type == "file_search": + with open(_files_fifo_file, 'w') as fifo: + file_search(fifo, args.dir, args.query) + + if args.type == "grep_search": + grep_search(args.dir, args.query) + + +if __name__ == "__main__": + try: + setproctitle('SolarFM: File Search - Grepy') + + parser = argparse.ArgumentParser() + # Add long and short arguments + parser.add_argument("--type", "-t", default=None, help="Type of search to do.") + parser.add_argument("--dir", "-d", default=None, help="Directory root for search type.") + parser.add_argument("--query", "-q", default=None, help="Query search is working against.") + + # Read arguments (If any...) + args = parser.parse_args() + search(args) + + except Exception as e: + traceback.print_exc() diff --git a/plugins/searcher/search_dialog.glade b/plugins/searcher/search_dialog.glade index 47fc1bc..9fbca4b 100644 --- a/plugins/searcher/search_dialog.glade +++ b/plugins/searcher/search_dialog.glade @@ -74,7 +74,7 @@ False vertical - + True True Query... @@ -83,6 +83,7 @@ False Search for file... + False @@ -149,6 +150,7 @@ False Query string in file... + False From 867c651a046c0c6cb91b4f30956dc54922d54338 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Mon, 3 Oct 2022 20:50:38 -0500 Subject: [PATCH 09/30] Changed search plugin IPC logic --- plugins/searcher/ipc_server.py | 104 +++++++++++++++++++++++++++++++++ plugins/searcher/plugin.py | 36 ++---------- plugins/searcher/search.py | 67 +++++++++++---------- 3 files changed, 143 insertions(+), 64 deletions(-) create mode 100644 plugins/searcher/ipc_server.py diff --git a/plugins/searcher/ipc_server.py b/plugins/searcher/ipc_server.py new file mode 100644 index 0000000..c654842 --- /dev/null +++ b/plugins/searcher/ipc_server.py @@ -0,0 +1,104 @@ +# Python imports +import os, threading, time, pickle +from multiprocessing.connection import Listener, Client + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk, GLib + +# Application imports + + + + +class IPCServer: + """ Create a listener so that other SolarFM instances send requests back to existing instance. """ + def __init__(self, ipc_address: str = '127.0.0.1', conn_type: str = "socket"): + self.is_ipc_alive = False + self._ipc_port = 4848 + self._ipc_address = ipc_address + self._conn_type = conn_type + self._ipc_authkey = b'' + bytes(f'solarfm-search_grep-ipc', 'utf-8') + self._ipc_timeout = 15.0 + + if conn_type == "socket": + self._ipc_address = f'/tmp/solarfm-search_grep-ipc.sock' + elif conn_type == "full_network": + self._ipc_address = '0.0.0.0' + elif conn_type == "full_network_unsecured": + self._ipc_authkey = None + self._ipc_address = '0.0.0.0' + elif conn_type == "local_network_unsecured": + self._ipc_authkey = None + + + @daemon_threaded + def create_ipc_listener(self) -> None: + if self._conn_type == "socket": + if os.path.exists(self._ipc_address): + os.unlink(self._ipc_address) + + listener = Listener(address=self._ipc_address, family="AF_UNIX", authkey=self._ipc_authkey) + elif "unsecured" not in self._conn_type: + listener = Listener((self._ipc_address, self._ipc_port), authkey=self._ipc_authkey) + else: + listener = Listener((self._ipc_address, self._ipc_port)) + + + self.is_ipc_alive = True + while True: + conn = listener.accept() + start_time = time.perf_counter() + self.handle_message(conn, start_time) + + listener.close() + + def handle_message(self, conn, start_time) -> None: + while True: + msg = conn.recv() + data = msg + + if "SEARCH|" in msg: + file = msg.split("SEARCH|")[1].strip() + if file: + GLib.idle_add(self._load_file_ui, file) + + conn.close() + break + + if "GREP|" in msg: + data = msg.split("GREP|")[1].strip() + if data: + GLib.idle_add(self._load_grep_ui, data) + + conn.close() + break + + + if msg in ['close connection', 'close server']: + conn.close() + break + + # NOTE: Not perfect but insures we don't lock up the connection for too long. + end_time = time.perf_counter() + if (end_time - start_time) > self._ipc_timeout: + conn.close() + break + + + def send_ipc_message(self, message: str = "Empty Data...") -> None: + try: + if self._conn_type == "socket": + conn = Client(address=self._ipc_address, family="AF_UNIX", authkey=self._ipc_authkey) + elif "unsecured" not in self._conn_type: + conn = Client((self._ipc_address, self._ipc_port), authkey=self._ipc_authkey) + else: + conn = Client((self._ipc_address, self._ipc_port)) + + conn.send(message) + conn.close() + except ConnectionRefusedError as e: + print("Connection refused...") + except Exception as e: + print(repr(e)) diff --git a/plugins/searcher/plugin.py b/plugins/searcher/plugin.py index 3a2ae7c..cedbfdf 100644 --- a/plugins/searcher/plugin.py +++ b/plugins/searcher/plugin.py @@ -1,5 +1,5 @@ # Python imports -import os, threading, subprocess, inspect, time, json, base64, shlex, select, signal, pickle +import os, threading, subprocess, inspect, time, json, base64, shlex, select, signal # Lib imports import gi @@ -8,7 +8,7 @@ from gi.repository import Gtk, GLib # Application imports from plugins.plugin_base import PluginBase - +from .ipc_server import IPCServer @@ -74,7 +74,7 @@ class GrepPreviewWidget(Gtk.Box): pause_fifo_update = False -class Plugin(PluginBase): +class Plugin(IPCServer, PluginBase): def __init__(self): super().__init__() @@ -83,9 +83,6 @@ class Plugin(PluginBase): # where self.name should not be needed for message comms self._GLADE_FILE = f"{self.path}/search_dialog.glade" - self._files_fifo_file = f"/tmp/search_files_fifo" - self._grep_fifo_file = f"/tmp/grep_files_fifo" - self._search_dialog = None self._active_path = None self._file_list = None @@ -123,30 +120,7 @@ class Plugin(PluginBase): self._event_system.subscribe("update-file-ui", self._load_file_ui) self._event_system.subscribe("update-grep-ui", self._load_grep_ui) - if not os.path.exists(self._files_fifo_file): - os.mkfifo(self._files_fifo_file, 0o777) - if not os.path.exists(self._grep_fifo_file): - os.mkfifo(self._grep_fifo_file, 0o777) - - self.run_files_fifo_thread() - self.run_grep_fifo_thread() - - - @daemon_threaded - def run_files_fifo_thread(self): - with open(self._files_fifo_file) as fifo: - while True: - select.select([fifo],[],[fifo]) - data = fifo.read() - GLib.idle_add(self._load_file_ui, data) - - @daemon_threaded - def run_grep_fifo_thread(self): - with open(self._grep_fifo_file) as fifo: - while True: - select.select([fifo],[],[fifo]) - data = fifo.read() - GLib.idle_add(self._load_grep_ui, data) + self.create_ipc_listener() def _show_grep_list_page(self, widget=None, eve=None): @@ -166,9 +140,7 @@ class Plugin(PluginBase): query = widget.get_text() if not query in ("", None): target_dir = shlex.quote( self._fm_state.tab.get_current_directory() ) - # command = [f"{self.path}/search.sh", "-t", "file_search", "-d", f"{target_dir}", "-q", f"{query}"] command = ["python", f"{self.path}/search.py", "-t", "file_search", "-d", f"{target_dir}", "-q", f"{query}"] - process = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None) def _stop_find_file_query(self, widget=None, eve=None): diff --git a/plugins/searcher/search.py b/plugins/searcher/search.py index 5756dfe..f06f3fa 100755 --- a/plugins/searcher/search.py +++ b/plugins/searcher/search.py @@ -2,8 +2,9 @@ # Python imports -import os, traceback, argparse, time, json, base64 +import os, traceback, argparse, json, base64 from setproctitle import setproctitle +from multiprocessing.connection import Client # Lib imports @@ -11,8 +12,9 @@ from setproctitle import setproctitle -_files_fifo_file = f"/tmp/search_files_fifo" -_grep_fifo_file = f"/tmp/grep_files_fifo" + +_ipc_address = f'/tmp/solarfm-search_grep-ipc.sock' +_ipc_authkey = b'' + bytes(f'solarfm-search_grep-ipc', 'utf-8') filter = (".mkv", ".mp4", ".webm", ".avi", ".mov", ".m4v", ".mpg", ".mpeg", ".wmv", ".flv") + \ (".png", ".jpg", ".jpeg", ".gif", ".ico", ".tga", ".webp") + \ @@ -21,46 +23,49 @@ filter = (".mkv", ".mp4", ".webm", ".avi", ".mov", ".m4v", ".mpg", ".mpeg", ".wm file_result_set = [] -def file_search(fifo, path, query): +def send_ipc_message(message) -> None: + try: + conn = Client(address=_ipc_address, family="AF_UNIX", authkey=_ipc_authkey) + + conn.send(message) + conn.close() + except ConnectionRefusedError as e: + print("Connection refused...") + except Exception as e: + print(repr(e)) + + +def file_search(path, query): try: for file in os.listdir(path): target = os.path.join(path, file) if os.path.isdir(target): - file_search(fifo, target, query) + file_search(target, query) else: if query.lower() in file.lower(): - # file_result_set.append([target, file]) - data = json.dumps([target, file]) - fifo.write(data) - time.sleep(0.01) + data = f"SEARCH|{json.dumps([target, file])}" + send_ipc_message(data) except Exception as e: print("Couldn't traverse to path. Might be permissions related...") traceback.print_exc() def _search_for_string(file, query): - try: - b64_file = base64.urlsafe_b64encode(file.encode('utf-8')).decode('utf-8') - grep_result_set = {} + b64_file = base64.urlsafe_b64encode(file.encode('utf-8')).decode('utf-8') + grep_result_set = {} - with open(file, 'r') as fp: - for i, line in enumerate(fp): - if query in line: - b64_line = base64.urlsafe_b64encode(line.encode('utf-8')).decode('utf-8') + with open(file, 'r') as fp: + for i, line in enumerate(fp): + if query in line: + b64_line = base64.urlsafe_b64encode(line.encode('utf-8')).decode('utf-8') - if f"{b64_file}" in grep_result_set.keys(): - grep_result_set[f"{b64_file}"][f"{i+1}"] = b64_line - else: - grep_result_set[f"{b64_file}"] = {} - grep_result_set[f"{b64_file}"] = {f"{i+1}": b64_line} + if f"{b64_file}" in grep_result_set.keys(): + grep_result_set[f"{b64_file}"][f"{i+1}"] = b64_line + else: + grep_result_set[f"{b64_file}"] = {} + grep_result_set[f"{b64_file}"] = {f"{i+1}": b64_line} - # NOTE: Push to fifo here after loop - with open(_grep_fifo_file, 'w') as fifo: - data = json.dumps(grep_result_set) - fifo.write(data) - time.sleep(0.05) - except Exception as e: - print("Couldn't read file. Might be binary or other cause...") - traceback.print_exc() + data = f"GREP|{json.dumps(grep_result_set)}" + send_ipc_message(data) def grep_search(path, query): @@ -78,8 +83,7 @@ def grep_search(path, query): def search(args): if args.type == "file_search": - with open(_files_fifo_file, 'w') as fifo: - file_search(fifo, args.dir, args.query) + file_search(args.dir, args.query) if args.type == "grep_search": grep_search(args.dir, args.query) @@ -98,6 +102,5 @@ if __name__ == "__main__": # Read arguments (If any...) args = parser.parse_args() search(args) - except Exception as e: traceback.print_exc() From 982e5869363858701f35523d7e9057a9c8cbe5cd Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Mon, 3 Oct 2022 22:14:18 -0500 Subject: [PATCH 10/30] Restructured searcher layout --- plugins/searcher/mixins/__init__.py | 3 + plugins/searcher/mixins/file_search_mixin.py | 71 +++++++++ plugins/searcher/mixins/grep_search_mixin.py | 73 ++++++++++ plugins/searcher/plugin.py | 136 ++---------------- plugins/searcher/search_dialog.glade | 4 +- plugins/searcher/{ => utils}/ipc_server.py | 41 +++--- plugins/searcher/{ => utils}/search.py | 0 plugins/searcher/widgets/__init__.py | 3 + .../searcher/widgets/file_preview_widget.py | 16 +++ .../searcher/widgets/grep_preview_widget.py | 46 ++++++ 10 files changed, 249 insertions(+), 144 deletions(-) create mode 100644 plugins/searcher/mixins/__init__.py create mode 100644 plugins/searcher/mixins/file_search_mixin.py create mode 100644 plugins/searcher/mixins/grep_search_mixin.py rename plugins/searcher/{ => utils}/ipc_server.py (74%) rename plugins/searcher/{ => utils}/search.py (100%) create mode 100644 plugins/searcher/widgets/__init__.py create mode 100644 plugins/searcher/widgets/file_preview_widget.py create mode 100644 plugins/searcher/widgets/grep_preview_widget.py diff --git a/plugins/searcher/mixins/__init__.py b/plugins/searcher/mixins/__init__.py new file mode 100644 index 0000000..0c12f42 --- /dev/null +++ b/plugins/searcher/mixins/__init__.py @@ -0,0 +1,3 @@ +""" + Mixins Module +""" diff --git a/plugins/searcher/mixins/file_search_mixin.py b/plugins/searcher/mixins/file_search_mixin.py new file mode 100644 index 0000000..4095d65 --- /dev/null +++ b/plugins/searcher/mixins/file_search_mixin.py @@ -0,0 +1,71 @@ +# Python imports +import threading, subprocess, signal, time, json, shlex + +# Lib imports +from gi.repository import GLib + +# Application imports +from ..widgets.file_preview_widget import FilePreviewWidget + + +# NOTE: Threads WILL NOT die with parent's destruction. +def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() + return wrapper + +# NOTE: Threads WILL die with parent's destruction. +def daemon_threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() + return wrapper + + +class FileSearchMixin: + def _run_find_file_query(self, widget=None, eve=None): + self._handle_find_file_query(query=widget) + + @daemon_threaded + def _handle_find_file_query(self, widget=None, eve=None, query=None): + # NOTE: Freeze IPC consumption + self.pause_fifo_update = True + + # NOTE: Kill the former process + if self._list_proc: + if self._list_proc.poll(): + self._list_proc.send_signal(signal.SIGKILL) + while self._list_proc.poll(): + pass + + self._list_proc = None + else: + self._list_proc = None + + GLib.idle_add(self.clear_children, self._file_list) + while len(self._file_list.get_children()) > 0: + ... + + # NOTE: Make sure ui thread redraws + time.sleep(0.5) + self.pause_fifo_update = False + + # NOTE: If query create new process and do all new loop. + if query: + GLib.idle_add(self._exec_find_file_query, query) + + def _exec_find_file_query(self, widget=None, eve=None): + query = widget.get_text() + + if not query in ("", None): + target_dir = shlex.quote( self._fm_state.tab.get_current_directory() ) + command = ["python", f"{self.path}/utils/search.py", "-t", "file_search", "-d", f"{target_dir}", "-q", f"{query}"] + self._list_proc = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None) + + def _load_file_ui(self, data): + if not data in ("", None) and not self.pause_fifo_update: + jdata = json.loads( data ) + target = jdata[0] + file = jdata[1] + + widget = FilePreviewWidget(target, file) + self._file_list.add(widget) diff --git a/plugins/searcher/mixins/grep_search_mixin.py b/plugins/searcher/mixins/grep_search_mixin.py new file mode 100644 index 0000000..860a346 --- /dev/null +++ b/plugins/searcher/mixins/grep_search_mixin.py @@ -0,0 +1,73 @@ +# Python imports +import threading, subprocess, signal, time, json, shlex + +# Lib imports +from gi.repository import GLib + +# Application imports +from ..widgets.grep_preview_widget import GrepPreviewWidget + + +# NOTE: Threads WILL NOT die with parent's destruction. +def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() + return wrapper + +# NOTE: Threads WILL die with parent's destruction. +def daemon_threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() + return wrapper + + +class GrepSearchMixin: + def _run_grep_query(self, widget=None, eve=None): + self._handle_grep_query(query=widget) + + @daemon_threaded + def _handle_grep_query(self, widget=None, eve=None, query=None): + # NOTE: Freeze IPC consumption + self.pause_fifo_update = True + + # NOTE: Kill the former process + if self._grep_proc: + if self._grep_proc.poll(): + self._grep_proc.send_signal(signal.SIGKILL) + while self._grep_proc.poll(): + pass + + self._grep_proc = None + else: + self._grep_proc = None + + # NOTE: Clear children from ui + GLib.idle_add(self.clear_children, self._grep_list) + while len(self._grep_list.get_children()) > 0: + ... + + # NOTE: Make sure ui thread redraws + time.sleep(0.5) + self.pause_fifo_update = False + + # NOTE: If query create new process and do all new loop. + if query: + GLib.idle_add(self._exec_grep_query, query) + + def _exec_grep_query(self, widget=None, eve=None): + query = widget.get_text() + if not query in ("", None): + target_dir = shlex.quote( self._fm_state.tab.get_current_directory() ) + command = ["python", f"{self.path}/utils/search.py", "-t", "grep_search", "-d", f"{target_dir}", "-q", f"{query}"] + self._grep_proc = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None) + + def _load_grep_ui(self, data): + if not data in ("", None) and not self.pause_fifo_update: + jdata = json.loads( data ) + jkeys = jdata.keys() + for key in jkeys: + sub_keys = jdata[key].keys() + grep_result = jdata[key] + + widget = GrepPreviewWidget(key, sub_keys, grep_result) + self._grep_list.add(widget) diff --git a/plugins/searcher/plugin.py b/plugins/searcher/plugin.py index cedbfdf..61eaeda 100644 --- a/plugins/searcher/plugin.py +++ b/plugins/searcher/plugin.py @@ -1,5 +1,5 @@ # Python imports -import os, threading, subprocess, inspect, time, json, base64, shlex, select, signal +import os, threading, inspect # Lib imports import gi @@ -8,7 +8,9 @@ from gi.repository import Gtk, GLib # Application imports from plugins.plugin_base import PluginBase -from .ipc_server import IPCServer +from .mixins.file_search_mixin import FileSearchMixin +from .mixins.grep_search_mixin import GrepSearchMixin +from .utils.ipc_server import IPCServer @@ -27,54 +29,7 @@ def daemon_threaded(fn): -class FilePreviewWidget(Gtk.LinkButton): - def __init__(self, path, file): - super(FilePreviewWidget, self).__init__() - self.set_label(file) - self.set_uri(f"file://{path}") - self.show_all() - - -class GrepPreviewWidget(Gtk.Box): - def __init__(self, _path, sub_keys, data): - super(GrepPreviewWidget, self).__init__() - self.set_orientation(Gtk.Orientation.VERTICAL) - self.line_color = "#e0cc64" - - path = base64.urlsafe_b64decode(_path.encode('utf-8')).decode('utf-8') - _label = '/'.join( path.split("/")[-3:] ) - title = Gtk.LinkButton.new_with_label(uri=f"file://{path}", label=_label) - - self.add(title) - for key in sub_keys: - line_num = key - text = base64.urlsafe_b64decode(data[key].encode('utf-8')).decode('utf-8') - - - box = Gtk.Box() - number_label = Gtk.Label() - text_view = Gtk.Label(label=text[:-1]) - label_text = f"{line_num}" - - number_label.set_markup(label_text) - number_label.set_margin_left(15) - number_label.set_margin_right(5) - number_label.set_margin_top(5) - number_label.set_margin_bottom(5) - text_view.set_margin_top(5) - text_view.set_margin_bottom(5) - text_view.set_line_wrap(True) - - box.add(number_label) - box.add(text_view) - self.add(box) - - self.show_all() - - -pause_fifo_update = False - -class Plugin(IPCServer, PluginBase): +class Plugin(IPCServer, FileSearchMixin, GrepSearchMixin, PluginBase): def __init__(self): super().__init__() @@ -89,11 +44,13 @@ class Plugin(IPCServer, PluginBase): self._grep_list = None self._grep_proc = None self._list_proc = None + self.pause_fifo_update = False + self.update_list_ui_buffer = () def get_ui_element(self): button = Gtk.Button(label=self.name) - button.connect("button-release-event", self._show_grep_list_page) + button.connect("button-release-event", self._show_page) return button def run(self): @@ -123,7 +80,7 @@ class Plugin(IPCServer, PluginBase): self.create_ipc_listener() - def _show_grep_list_page(self, widget=None, eve=None): + def _show_page(self, widget=None, eve=None): self._event_system.emit("get_current_state") state = self._fm_state @@ -134,74 +91,7 @@ class Plugin(IPCServer, PluginBase): self._search_dialog.hide() - def _run_find_file_query(self, widget=None, eve=None): - self._stop_find_file_query() - - query = widget.get_text() - if not query in ("", None): - target_dir = shlex.quote( self._fm_state.tab.get_current_directory() ) - command = ["python", f"{self.path}/search.py", "-t", "file_search", "-d", f"{target_dir}", "-q", f"{query}"] - process = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None) - - def _stop_find_file_query(self, widget=None, eve=None): - pause_fifo_update = True - - if self._list_proc: - if self._list_proc.poll(): - self._list_proc.send_signal(signal.SIGKILL) - while self._list_proc.poll(): - pass - - self._list_proc = None - else: - self._list_proc = None - - self.clear_children(self._file_list) - pause_fifo_update = False - - - def _run_grep_query(self, widget=None, eve=None): - self._stop_grep_query() - - query = widget.get_text() - if not query in ("", None): - target_dir = shlex.quote( self._fm_state.tab.get_current_directory() ) - command = ["python", f"{self.path}/search.py", "-t", "grep_search", "-d", f"{target_dir}", "-q", f"{query}"] - process = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None) - - def _stop_grep_query(self, widget=None, eve=None): - pause_fifo_update = True - - if self._grep_proc: - if self._grep_proc.poll(): - self._grep_proc.send_signal(signal.SIGKILL) - while self._grep_proc.poll(): - pass - - self._grep_proc = None - else: - self._grep_proc = None - - self.clear_children(self._grep_list) - pause_fifo_update = False - - - def _load_file_ui(self, data): - if not data in ("", None) and not pause_fifo_update: - jdata = json.loads( data ) - target = jdata[0] - file = jdata[1] - - widget = FilePreviewWidget(target, file) - self._file_list.add(widget) - - def _load_grep_ui(self, data): - if not data in ("", None) and not pause_fifo_update: - jdata = json.loads( data ) - jkeys = jdata.keys() - for key in jkeys: - sub_keys = jdata[key].keys() - grep_result = jdata[key] - - widget = GrepPreviewWidget(key, sub_keys, grep_result) - self._grep_list.add(widget) + def clear_children(self, widget: type) -> None: + ''' Clear children of a gtk widget. ''' + for child in widget.get_children(): + widget.remove(child) diff --git a/plugins/searcher/search_dialog.glade b/plugins/searcher/search_dialog.glade index 9fbca4b..aaa3b09 100644 --- a/plugins/searcher/search_dialog.glade +++ b/plugins/searcher/search_dialog.glade @@ -83,7 +83,7 @@ False Search for file... - + False @@ -150,7 +150,7 @@ False Query string in file... - + False diff --git a/plugins/searcher/ipc_server.py b/plugins/searcher/utils/ipc_server.py similarity index 74% rename from plugins/searcher/ipc_server.py rename to plugins/searcher/utils/ipc_server.py index c654842..937a474 100644 --- a/plugins/searcher/ipc_server.py +++ b/plugins/searcher/utils/ipc_server.py @@ -57,32 +57,35 @@ class IPCServer: def handle_message(self, conn, start_time) -> None: while True: msg = conn.recv() - data = msg - if "SEARCH|" in msg: - file = msg.split("SEARCH|")[1].strip() - if file: - GLib.idle_add(self._load_file_ui, file) + if not self.pause_fifo_update: + if "SEARCH|" in msg: + file = msg.split("SEARCH|")[1].strip() + if file: + GLib.idle_add(self._load_file_ui, file) - conn.close() - break + conn.close() + break - if "GREP|" in msg: - data = msg.split("GREP|")[1].strip() - if data: - GLib.idle_add(self._load_grep_ui, data) + if "GREP|" in msg: + data = msg.split("GREP|")[1].strip() + if data: + GLib.idle_add(self._load_grep_ui, data) - conn.close() - break + conn.close() + break - if msg in ['close connection', 'close server']: - conn.close() - break + if msg in ['close connection', 'close server']: + conn.close() + break - # NOTE: Not perfect but insures we don't lock up the connection for too long. - end_time = time.perf_counter() - if (end_time - start_time) > self._ipc_timeout: + # NOTE: Not perfect but insures we don't lock up the connection for too long. + end_time = time.perf_counter() + if (end_time - start_time) > self._ipc_timeout: + conn.close() + break + else: conn.close() break diff --git a/plugins/searcher/search.py b/plugins/searcher/utils/search.py similarity index 100% rename from plugins/searcher/search.py rename to plugins/searcher/utils/search.py diff --git a/plugins/searcher/widgets/__init__.py b/plugins/searcher/widgets/__init__.py new file mode 100644 index 0000000..72b072b --- /dev/null +++ b/plugins/searcher/widgets/__init__.py @@ -0,0 +1,3 @@ +""" + Widgets Module +""" diff --git a/plugins/searcher/widgets/file_preview_widget.py b/plugins/searcher/widgets/file_preview_widget.py new file mode 100644 index 0000000..227e37a --- /dev/null +++ b/plugins/searcher/widgets/file_preview_widget.py @@ -0,0 +1,16 @@ +# Python imports + +# Gtk imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +# Application imports + + +class FilePreviewWidget(Gtk.LinkButton): + def __init__(self, path, file): + super(FilePreviewWidget, self).__init__() + self.set_label(file) + self.set_uri(f"file://{path}") + self.show_all() diff --git a/plugins/searcher/widgets/grep_preview_widget.py b/plugins/searcher/widgets/grep_preview_widget.py new file mode 100644 index 0000000..85aa2f8 --- /dev/null +++ b/plugins/searcher/widgets/grep_preview_widget.py @@ -0,0 +1,46 @@ +# Python imports +import base64 + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +# Application imports + + +class GrepPreviewWidget(Gtk.Box): + def __init__(self, _path, sub_keys, data): + super(GrepPreviewWidget, self).__init__() + self.set_orientation(Gtk.Orientation.VERTICAL) + self.line_color = "#e0cc64" + + path = base64.urlsafe_b64decode(_path.encode('utf-8')).decode('utf-8') + _label = '/'.join( path.split("/")[-3:] ) + title = Gtk.LinkButton.new_with_label(uri=f"file://{path}", label=_label) + + self.add(title) + for key in sub_keys: + line_num = key + text = base64.urlsafe_b64decode(data[key].encode('utf-8')).decode('utf-8') + + + box = Gtk.Box() + number_label = Gtk.Label() + text_view = Gtk.Label(label=text[:-1]) + label_text = f"{line_num}" + + number_label.set_markup(label_text) + number_label.set_margin_left(15) + number_label.set_margin_right(5) + number_label.set_margin_top(5) + number_label.set_margin_bottom(5) + text_view.set_margin_top(5) + text_view.set_margin_bottom(5) + text_view.set_line_wrap(True) + + box.add(number_label) + box.add(text_view) + self.add(box) + + self.show_all() From 0dece2cec96254cc2644ff5478bd87dcf1e5e6cb Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Tue, 4 Oct 2022 02:30:46 -0500 Subject: [PATCH 11/30] More searcher plugin updates, added additional file settings --- plugins/searcher/mixins/grep_search_mixin.py | 3 +- plugins/searcher/utils/search.py | 69 ++++++++++++++----- .../searcher/widgets/grep_preview_widget.py | 52 +++++++------- 3 files changed, 78 insertions(+), 46 deletions(-) diff --git a/plugins/searcher/mixins/grep_search_mixin.py b/plugins/searcher/mixins/grep_search_mixin.py index 860a346..cdf1c58 100644 --- a/plugins/searcher/mixins/grep_search_mixin.py +++ b/plugins/searcher/mixins/grep_search_mixin.py @@ -70,4 +70,5 @@ class GrepSearchMixin: grep_result = jdata[key] widget = GrepPreviewWidget(key, sub_keys, grep_result) - self._grep_list.add(widget) + GLib.idle_add(self._grep_list.add, widget) + # self._grep_list.add(widget) diff --git a/plugins/searcher/utils/search.py b/plugins/searcher/utils/search.py index f06f3fa..1355f1d 100755 --- a/plugins/searcher/utils/search.py +++ b/plugins/searcher/utils/search.py @@ -2,7 +2,7 @@ # Python imports -import os, traceback, argparse, json, base64 +import os, traceback, argparse, threading, json, base64, time from setproctitle import setproctitle from multiprocessing.connection import Client @@ -16,11 +16,20 @@ from multiprocessing.connection import Client _ipc_address = f'/tmp/solarfm-search_grep-ipc.sock' _ipc_authkey = b'' + bytes(f'solarfm-search_grep-ipc', 'utf-8') -filter = (".mkv", ".mp4", ".webm", ".avi", ".mov", ".m4v", ".mpg", ".mpeg", ".wmv", ".flv") + \ - (".png", ".jpg", ".jpeg", ".gif", ".ico", ".tga", ".webp") + \ - (".psf", ".mp3", ".ogg", ".flac", ".m4a") +filter = (".cpp", ".css", ".c", ".go", ".html", ".htm", ".java", ".js", ".json", ".lua", ".md", ".py", ".rs", ".toml", ".xml", ".pom") + \ + (".txt", ".text", ".sh", ".cfg", ".conf", ".log") -file_result_set = [] +# NOTE: Threads WILL NOT die with parent's destruction. +def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() + return wrapper + +# NOTE: Threads WILL die with parent's destruction. +def daemon_threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() + return wrapper def send_ipc_message(message) -> None: @@ -42,7 +51,7 @@ def file_search(path, query): if os.path.isdir(target): file_search(target, query) else: - if query.lower() in file.lower(): + if query in file.lower(): data = f"SEARCH|{json.dumps([target, file])}" send_ipc_message(data) except Exception as e: @@ -52,21 +61,40 @@ def file_search(path, query): def _search_for_string(file, query): b64_file = base64.urlsafe_b64encode(file.encode('utf-8')).decode('utf-8') grep_result_set = {} - + padding = 15 with open(file, 'r') as fp: - for i, line in enumerate(fp): - if query in line: - b64_line = base64.urlsafe_b64encode(line.encode('utf-8')).decode('utf-8') + # NOTE: I know there's an issue if there's a very large file with content all on one line will lower and dupe it. + # And, yes, it will only return one instance from the file. + for i, raw in enumerate(fp): + line = None + llower = raw.lower() + if not query in llower: + continue - if f"{b64_file}" in grep_result_set.keys(): - grep_result_set[f"{b64_file}"][f"{i+1}"] = b64_line - else: - grep_result_set[f"{b64_file}"] = {} - grep_result_set[f"{b64_file}"] = {f"{i+1}": b64_line} + if len(raw) > 72: + start = 0 + end = len(raw) - 1 + index = llower.index(query) + sindex = llower.index(query) - 15 if index >= 15 else abs(start - index) - index + eindex = sindex + 15 if end > (index + 15) else abs(index - end) + index + line = raw[sindex:eindex] + else: + line = raw + + b64_line = base64.urlsafe_b64encode(line.encode('utf-8')).decode('utf-8') + if f"{b64_file}" in grep_result_set.keys(): + grep_result_set[f"{b64_file}"][f"{i+1}"] = b64_line + else: + grep_result_set[f"{b64_file}"] = {} + grep_result_set[f"{b64_file}"] = {f"{i+1}": b64_line} data = f"GREP|{json.dumps(grep_result_set)}" send_ipc_message(data) +@daemon_threaded +def _search_for_string_threaded(file, query): + _search_for_string(file, query) + def grep_search(path, query): try: @@ -75,18 +103,23 @@ def grep_search(path, query): if os.path.isdir(target): grep_search(target, query) else: - if not target.lower().endswith(filter): + if target.lower().endswith(filter): + size = os.path.getsize(target) + if size < 5000: _search_for_string(target, query) + else: + _search_for_string_threaded(target, query) + except Exception as e: print("Couldn't traverse to path. Might be permissions related...") traceback.print_exc() def search(args): if args.type == "file_search": - file_search(args.dir, args.query) + file_search(args.dir, args.query.lower()) if args.type == "grep_search": - grep_search(args.dir, args.query) + grep_search(args.dir, args.query.lower()) if __name__ == "__main__": diff --git a/plugins/searcher/widgets/grep_preview_widget.py b/plugins/searcher/widgets/grep_preview_widget.py index 85aa2f8..899d309 100644 --- a/plugins/searcher/widgets/grep_preview_widget.py +++ b/plugins/searcher/widgets/grep_preview_widget.py @@ -10,37 +10,35 @@ from gi.repository import Gtk class GrepPreviewWidget(Gtk.Box): - def __init__(self, _path, sub_keys, data): + def __init__(self, _path, sub_keys, _data): super(GrepPreviewWidget, self).__init__() self.set_orientation(Gtk.Orientation.VERTICAL) self.line_color = "#e0cc64" - path = base64.urlsafe_b64decode(_path.encode('utf-8')).decode('utf-8') - _label = '/'.join( path.split("/")[-3:] ) - title = Gtk.LinkButton.new_with_label(uri=f"file://{path}", label=_label) + path = self.decode_str(_path) + _label = '/'.join( path.split("/")[-3:] ) + title = Gtk.LinkButton.new_with_label(uri=f"file://{path}", label=_label) + + text_view = Gtk.TextView() + text_view.set_editable(False) + text_view.set_wrap_mode(Gtk.WrapMode.NONE) + buffer = text_view.get_buffer() + + for i, key in enumerate(sub_keys): + line_num = self.make_utf8_line_num(self.line_color, key) + text = f"\t\t{ self.decode_str(_data[key]) }" + + itr = buffer.get_end_iter() + buffer.insert_markup(itr, line_num, len(line_num)) + itr = buffer.get_end_iter() + buffer.insert(itr, text, length=len(text)) self.add(title) - for key in sub_keys: - line_num = key - text = base64.urlsafe_b64decode(data[key].encode('utf-8')).decode('utf-8') - - - box = Gtk.Box() - number_label = Gtk.Label() - text_view = Gtk.Label(label=text[:-1]) - label_text = f"{line_num}" - - number_label.set_markup(label_text) - number_label.set_margin_left(15) - number_label.set_margin_right(5) - number_label.set_margin_top(5) - number_label.set_margin_bottom(5) - text_view.set_margin_top(5) - text_view.set_margin_bottom(5) - text_view.set_line_wrap(True) - - box.add(number_label) - box.add(text_view) - self.add(box) - + self.add(text_view) self.show_all() + + def decode_str(self, target): + return base64.urlsafe_b64decode(target.encode('utf-8')).decode('utf-8') + + def make_utf8_line_num(self, color, target): + return bytes(f"\n{target}", "utf-8").decode("utf-8") From e929e9b742ffcd7bbbcf7a464946c2d589f6975b Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Tue, 4 Oct 2022 22:58:27 -0500 Subject: [PATCH 12/30] searcher - updated timings --- plugins/searcher/mixins/file_search_mixin.py | 15 +++--- plugins/searcher/mixins/grep_search_mixin.py | 22 +++++---- plugins/searcher/plugin.py | 5 +- plugins/searcher/utils/ipc_server.py | 49 +++++++------------ plugins/searcher/utils/search.py | 10 ++-- .../searcher/widgets/grep_preview_widget.py | 43 ++++++++++------ user_config/usr/share/solarfm/settings.json | 2 +- 7 files changed, 75 insertions(+), 71 deletions(-) diff --git a/plugins/searcher/mixins/file_search_mixin.py b/plugins/searcher/mixins/file_search_mixin.py index 4095d65..51cddce 100644 --- a/plugins/searcher/mixins/file_search_mixin.py +++ b/plugins/searcher/mixins/file_search_mixin.py @@ -29,6 +29,7 @@ class FileSearchMixin: def _handle_find_file_query(self, widget=None, eve=None, query=None): # NOTE: Freeze IPC consumption self.pause_fifo_update = True + self.search_query = "" # NOTE: Kill the former process if self._list_proc: @@ -41,15 +42,13 @@ class FileSearchMixin: else: self._list_proc = None + # NOTE: Clear children from ui and make sure ui thread redraws GLib.idle_add(self.clear_children, self._file_list) while len(self._file_list.get_children()) > 0: - ... - - # NOTE: Make sure ui thread redraws - time.sleep(0.5) - self.pause_fifo_update = False + time.sleep(0.2) # NOTE: If query create new process and do all new loop. + self.pause_fifo_update = False if query: GLib.idle_add(self._exec_find_file_query, query) @@ -57,12 +56,16 @@ class FileSearchMixin: query = widget.get_text() if not query in ("", None): + self.search_query = query target_dir = shlex.quote( self._fm_state.tab.get_current_directory() ) command = ["python", f"{self.path}/utils/search.py", "-t", "file_search", "-d", f"{target_dir}", "-q", f"{query}"] self._list_proc = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None) def _load_file_ui(self, data): - if not data in ("", None) and not self.pause_fifo_update: + if self.pause_fifo_update: + return + + if not data in ("", None): jdata = json.loads( data ) target = jdata[0] file = jdata[1] diff --git a/plugins/searcher/mixins/grep_search_mixin.py b/plugins/searcher/mixins/grep_search_mixin.py index cdf1c58..a83a24c 100644 --- a/plugins/searcher/mixins/grep_search_mixin.py +++ b/plugins/searcher/mixins/grep_search_mixin.py @@ -29,6 +29,7 @@ class GrepSearchMixin: def _handle_grep_query(self, widget=None, eve=None, query=None): # NOTE: Freeze IPC consumption self.pause_fifo_update = True + self.grep_query = "" # NOTE: Kill the former process if self._grep_proc: @@ -41,34 +42,35 @@ class GrepSearchMixin: else: self._grep_proc = None - # NOTE: Clear children from ui + # NOTE: Clear children from ui and make sure ui thread redraws GLib.idle_add(self.clear_children, self._grep_list) while len(self._grep_list.get_children()) > 0: - ... - - # NOTE: Make sure ui thread redraws - time.sleep(0.5) - self.pause_fifo_update = False + time.sleep(0.2) # NOTE: If query create new process and do all new loop. + self.pause_fifo_update = False if query: GLib.idle_add(self._exec_grep_query, query) def _exec_grep_query(self, widget=None, eve=None): query = widget.get_text() if not query in ("", None): + self.grep_query = query + target_dir = shlex.quote( self._fm_state.tab.get_current_directory() ) command = ["python", f"{self.path}/utils/search.py", "-t", "grep_search", "-d", f"{target_dir}", "-q", f"{query}"] self._grep_proc = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None) def _load_grep_ui(self, data): - if not data in ("", None) and not self.pause_fifo_update: + if self.pause_fifo_update: + return + + if not data in ("", None): jdata = json.loads( data ) jkeys = jdata.keys() for key in jkeys: sub_keys = jdata[key].keys() grep_result = jdata[key] - widget = GrepPreviewWidget(key, sub_keys, grep_result) - GLib.idle_add(self._grep_list.add, widget) - # self._grep_list.add(widget) + widget = GrepPreviewWidget(key, sub_keys, grep_result, self.grep_query) + self._grep_list.add(widget) diff --git a/plugins/searcher/plugin.py b/plugins/searcher/plugin.py index 61eaeda..d506e21 100644 --- a/plugins/searcher/plugin.py +++ b/plugins/searcher/plugin.py @@ -1,5 +1,5 @@ # Python imports -import os, threading, inspect +import os, threading, inspect, time # Lib imports import gi @@ -46,6 +46,8 @@ class Plugin(IPCServer, FileSearchMixin, GrepSearchMixin, PluginBase): self._list_proc = None self.pause_fifo_update = False self.update_list_ui_buffer = () + self.grep_query = "" + self.search_query = "" def get_ui_element(self): @@ -95,3 +97,4 @@ class Plugin(IPCServer, FileSearchMixin, GrepSearchMixin, PluginBase): ''' Clear children of a gtk widget. ''' for child in widget.get_children(): widget.remove(child) + time.sleep(0.01) diff --git a/plugins/searcher/utils/ipc_server.py b/plugins/searcher/utils/ipc_server.py index 937a474..96969cf 100644 --- a/plugins/searcher/utils/ipc_server.py +++ b/plugins/searcher/utils/ipc_server.py @@ -48,47 +48,32 @@ class IPCServer: self.is_ipc_alive = True while True: - conn = listener.accept() - start_time = time.perf_counter() - self.handle_message(conn, start_time) + conn = listener.accept() + + if not self.pause_fifo_update: + self.handle_message(conn) + else: + conn.close() listener.close() - def handle_message(self, conn, start_time) -> None: + def handle_message(self, conn) -> None: while True: msg = conn.recv() - if not self.pause_fifo_update: - if "SEARCH|" in msg: - file = msg.split("SEARCH|")[1].strip() - if file: - GLib.idle_add(self._load_file_ui, file) + if "SEARCH|" in msg: + file = msg.split("SEARCH|")[1].strip() + if file: + GLib.idle_add(self._load_file_ui, file) - conn.close() - break - - if "GREP|" in msg: - data = msg.split("GREP|")[1].strip() - if data: - GLib.idle_add(self._load_grep_ui, data) - - conn.close() - break + if "GREP|" in msg: + data = msg.split("GREP|")[1].strip() + if data: + GLib.idle_add(self._load_grep_ui, data) - if msg in ['close connection', 'close server']: - conn.close() - break - - # NOTE: Not perfect but insures we don't lock up the connection for too long. - end_time = time.perf_counter() - if (end_time - start_time) > self._ipc_timeout: - conn.close() - break - else: - conn.close() - break - + conn.close() + break def send_ipc_message(self, message: str = "Empty Data...") -> None: try: diff --git a/plugins/searcher/utils/search.py b/plugins/searcher/utils/search.py index 1355f1d..31bdcc6 100755 --- a/plugins/searcher/utils/search.py +++ b/plugins/searcher/utils/search.py @@ -33,15 +33,11 @@ def daemon_threaded(fn): def send_ipc_message(message) -> None: - try: - conn = Client(address=_ipc_address, family="AF_UNIX", authkey=_ipc_authkey) - + with Client(address=_ipc_address, family="AF_UNIX", authkey=_ipc_authkey) as conn: conn.send(message) conn.close() - except ConnectionRefusedError as e: - print("Connection refused...") - except Exception as e: - print(repr(e)) + + time.sleep(0.05) def file_search(path, query): diff --git a/plugins/searcher/widgets/grep_preview_widget.py b/plugins/searcher/widgets/grep_preview_widget.py index 899d309..4e6b799 100644 --- a/plugins/searcher/widgets/grep_preview_widget.py +++ b/plugins/searcher/widgets/grep_preview_widget.py @@ -1,5 +1,5 @@ # Python imports -import base64 +import base64, re # Lib imports import gi @@ -10,28 +10,31 @@ from gi.repository import Gtk class GrepPreviewWidget(Gtk.Box): - def __init__(self, _path, sub_keys, _data): + def __init__(self, _path, sub_keys, _data, _grep_query): super(GrepPreviewWidget, self).__init__() - self.set_orientation(Gtk.Orientation.VERTICAL) - self.line_color = "#e0cc64" - path = self.decode_str(_path) - _label = '/'.join( path.split("/")[-3:] ) - title = Gtk.LinkButton.new_with_label(uri=f"file://{path}", label=_label) + self.set_orientation(Gtk.Orientation.VERTICAL) + line_color = "#e0cc64" + highlight_color = "#FBF719" + grep_query = _grep_query + + path = self.decode_str(_path) + lbl = '/'.join( path.split("/")[-3:] ) + title = Gtk.LinkButton.new_with_label(uri=f"file://{path}", label=lbl) text_view = Gtk.TextView() - text_view.set_editable(False) - text_view.set_wrap_mode(Gtk.WrapMode.NONE) buffer = text_view.get_buffer() + text_view.set_editable(False) + text_view.set_monospace(True) + text_view.set_wrap_mode(Gtk.WrapMode.NONE) for i, key in enumerate(sub_keys): - line_num = self.make_utf8_line_num(self.line_color, key) - text = f"\t\t{ self.decode_str(_data[key]) }" - + line_num = self.make_utf8_line_num(line_color, key) itr = buffer.get_end_iter() buffer.insert_markup(itr, line_num, len(line_num)) - itr = buffer.get_end_iter() - buffer.insert(itr, text, length=len(text)) + + decoded = f"\t{self.decode_str(_data[key])}" + self.make_utf8_line_highlight(buffer, itr, i, highlight_color, decoded, grep_query) self.add(title) self.add(text_view) @@ -42,3 +45,15 @@ class GrepPreviewWidget(Gtk.Box): def make_utf8_line_num(self, color, target): return bytes(f"\n{target}", "utf-8").decode("utf-8") + + def make_utf8_line_highlight(self, buffer, itr, i, color, target, query): + parts = re.split(r"(" + query + ")(?i)", target.replace("\n", "")) + for part in parts: + itr = buffer.get_end_iter() + + if not query.lower() == part.lower() and not query.lower() in part.lower(): + buffer.insert(itr, part, length=len(part)) + else: + new_s = f"{part}" + _part = bytes(new_s, "utf-8").decode("utf-8") + buffer.insert_markup(itr, _part, len(_part)) diff --git a/user_config/usr/share/solarfm/settings.json b/user_config/usr/share/solarfm/settings.json index 9b6850b..d9d1789 100644 --- a/user_config/usr/share/solarfm/settings.json +++ b/user_config/usr/share/solarfm/settings.json @@ -23,7 +23,7 @@ "remux_folder_max_disk_usage": "8589934592" }, "filters": { - "code": [".cpp", ".css", ".c", ".go", ".html", ".htm", ".java", ".js", ".json", ".lua", ".md", ".py", ".rs"], + "code": [".cpp", ".css", ".c", ".go", ".html", ".htm", ".java", ".js", ".json", ".lua", ".md", ".py", ".rs", ".toml", ".xml", ".pom"], "videos": [".mkv", ".mp4", ".webm", ".avi", ".mov", ".m4v", ".mpg", ".mpeg", ".wmv", ".flv"], "office": [".doc", ".docx", ".xls", ".xlsx", ".xlt", ".xltx", ".xlm", ".ppt", ".pptx", ".pps", ".ppsx", ".odt", ".rtf"], "images": [".png", ".jpg", ".jpeg", ".gif", ".ico", ".tga", ".webp"], From 206f67f2f01328277194f83010c350c119b5b8a7 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Thu, 6 Oct 2022 20:48:44 -0500 Subject: [PATCH 13/30] Finally resolved UI thread overloads --- plugins/searcher/mixins/file_search_mixin.py | 22 ++--- plugins/searcher/mixins/grep_search_mixin.py | 22 ++--- plugins/searcher/plugin.py | 37 ++++++-- plugins/searcher/search_dialog.glade | 92 +++++++++++++++----- plugins/searcher/utils/search.py | 28 +++--- 5 files changed, 132 insertions(+), 69 deletions(-) diff --git a/plugins/searcher/mixins/file_search_mixin.py b/plugins/searcher/mixins/file_search_mixin.py index 51cddce..ffcc32a 100644 --- a/plugins/searcher/mixins/file_search_mixin.py +++ b/plugins/searcher/mixins/file_search_mixin.py @@ -12,12 +12,14 @@ from ..widgets.file_preview_widget import FilePreviewWidget def threaded(fn): def wrapper(*args, **kwargs): threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() + return wrapper # NOTE: Threads WILL die with parent's destruction. def daemon_threaded(fn): def wrapper(*args, **kwargs): threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() + return wrapper @@ -25,6 +27,7 @@ class FileSearchMixin: def _run_find_file_query(self, widget=None, eve=None): self._handle_find_file_query(query=widget) + # TODO: Merge this logic with nearly the exact same thing in grep_search_mixin @daemon_threaded def _handle_find_file_query(self, widget=None, eve=None, query=None): # NOTE: Freeze IPC consumption @@ -33,19 +36,15 @@ class FileSearchMixin: # NOTE: Kill the former process if self._list_proc: - if self._list_proc.poll(): - self._list_proc.send_signal(signal.SIGKILL) - while self._list_proc.poll(): - pass + if self._list_proc.poll() == None: + self._list_proc.terminate() + while self._list_proc.poll() == None: + ... - self._list_proc = None - else: - self._list_proc = None + self._list_proc = None # NOTE: Clear children from ui and make sure ui thread redraws - GLib.idle_add(self.clear_children, self._file_list) - while len(self._file_list.get_children()) > 0: - time.sleep(0.2) + GLib.idle_add(self.reset_file_list_box) # NOTE: If query create new process and do all new loop. self.pause_fifo_update = False @@ -62,9 +61,6 @@ class FileSearchMixin: self._list_proc = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None) def _load_file_ui(self, data): - if self.pause_fifo_update: - return - if not data in ("", None): jdata = json.loads( data ) target = jdata[0] diff --git a/plugins/searcher/mixins/grep_search_mixin.py b/plugins/searcher/mixins/grep_search_mixin.py index a83a24c..dc8372d 100644 --- a/plugins/searcher/mixins/grep_search_mixin.py +++ b/plugins/searcher/mixins/grep_search_mixin.py @@ -12,12 +12,14 @@ from ..widgets.grep_preview_widget import GrepPreviewWidget def threaded(fn): def wrapper(*args, **kwargs): threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() + return wrapper # NOTE: Threads WILL die with parent's destruction. def daemon_threaded(fn): def wrapper(*args, **kwargs): threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() + return wrapper @@ -25,6 +27,7 @@ class GrepSearchMixin: def _run_grep_query(self, widget=None, eve=None): self._handle_grep_query(query=widget) + # TODO: Merge this logic with nearly the exact same thing in file_search_mixin @daemon_threaded def _handle_grep_query(self, widget=None, eve=None, query=None): # NOTE: Freeze IPC consumption @@ -33,19 +36,15 @@ class GrepSearchMixin: # NOTE: Kill the former process if self._grep_proc: - if self._grep_proc.poll(): - self._grep_proc.send_signal(signal.SIGKILL) - while self._grep_proc.poll(): - pass + if self._grep_proc.poll() == None: + self._grep_proc.terminate() + while self._grep_proc.poll() == None: + ... - self._grep_proc = None - else: - self._grep_proc = None + self._grep_proc = None # NOTE: Clear children from ui and make sure ui thread redraws - GLib.idle_add(self.clear_children, self._grep_list) - while len(self._grep_list.get_children()) > 0: - time.sleep(0.2) + GLib.idle_add(self.reset_grep_box) # NOTE: If query create new process and do all new loop. self.pause_fifo_update = False @@ -62,9 +61,6 @@ class GrepSearchMixin: self._grep_proc = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None) def _load_grep_ui(self, data): - if self.pause_fifo_update: - return - if not data in ("", None): jdata = json.loads( data ) jkeys = jdata.keys() diff --git a/plugins/searcher/plugin.py b/plugins/searcher/plugin.py index d506e21..4b66a63 100644 --- a/plugins/searcher/plugin.py +++ b/plugins/searcher/plugin.py @@ -40,6 +40,8 @@ class Plugin(IPCServer, FileSearchMixin, GrepSearchMixin, PluginBase): self._search_dialog = None self._active_path = None + self.file_list_parent = None + self.grep_list_parent = None self._file_list = None self._grep_list = None self._grep_proc = None @@ -72,10 +74,11 @@ class Plugin(IPCServer, FileSearchMixin, GrepSearchMixin, PluginBase): self._builder.connect_signals(handlers) self._search_dialog = self._builder.get_object("search_dialog") - self._grep_list = self._builder.get_object("grep_list") - self._file_list = self._builder.get_object("file_list") self.fsearch = self._builder.get_object("fsearch") + self.grep_list_parent = self._builder.get_object("grep_list_parent") + self.file_list_parent = self._builder.get_object("file_list_parent") + self._event_system.subscribe("update-file-ui", self._load_file_ui) self._event_system.subscribe("update-grep-ui", self._load_grep_ui) @@ -92,9 +95,29 @@ class Plugin(IPCServer, FileSearchMixin, GrepSearchMixin, PluginBase): response = self._search_dialog.run() self._search_dialog.hide() + # TODO: Merge the below methods into some unified logic + def reset_grep_box(self) -> None: + try: + child = self.grep_list_parent.get_children()[0] + self._grep_list = None + self.grep_list_parent.remove(child) + except Exception: + ... - def clear_children(self, widget: type) -> None: - ''' Clear children of a gtk widget. ''' - for child in widget.get_children(): - widget.remove(child) - time.sleep(0.01) + self._grep_list = Gtk.Box() + self._grep_list.set_orientation(Gtk.Orientation.VERTICAL) + self.grep_list_parent.add(self._grep_list) + self.grep_list_parent.show_all() + + def reset_file_list_box(self) -> None: + try: + child = self.file_list_parent.get_children()[0] + self._file_list = None + self.file_list_parent.remove(child) + except Exception: + ... + + self._file_list = Gtk.Box() + self._file_list.set_orientation(Gtk.Orientation.VERTICAL) + self.file_list_parent.add(self._file_list) + self.file_list_parent.show_all() diff --git a/plugins/searcher/search_dialog.glade b/plugins/searcher/search_dialog.glade index aaa3b09..8578e51 100644 --- a/plugins/searcher/search_dialog.glade +++ b/plugins/searcher/search_dialog.glade @@ -74,16 +74,41 @@ False vertical - + True - True - Query... - edit-find-symbolic - False - False - Search for file... - - + False + + + True + True + Query... + edit-find-symbolic + False + False + Search for file... + + + + True + True + 0 + + + + + gtk-stop + True + True + True + True + + + + False + True + 1 + + False @@ -103,7 +128,7 @@ True False - + True False vertical @@ -141,16 +166,41 @@ False vertical - + True - True - Query... - edit-find-symbolic - False - False - Query string in file... - - + False + + + True + True + Query... + edit-find-symbolic + False + False + Query string in file... + + + + True + True + 0 + + + + + gtk-stop + True + True + True + True + + + + False + True + 1 + + False @@ -170,12 +220,10 @@ True False - + True False vertical - 5 - top diff --git a/plugins/searcher/utils/search.py b/plugins/searcher/utils/search.py index 31bdcc6..5c1efca 100755 --- a/plugins/searcher/utils/search.py +++ b/plugins/searcher/utils/search.py @@ -2,7 +2,7 @@ # Python imports -import os, traceback, argparse, threading, json, base64, time +import os, traceback, argparse, threading, json, base64, time, pickle from setproctitle import setproctitle from multiprocessing.connection import Client @@ -37,19 +37,18 @@ def send_ipc_message(message) -> None: conn.send(message) conn.close() - time.sleep(0.05) + # NOTE: Kinda important as this prevents overloading the UI thread + time.sleep(0.04) def file_search(path, query): try: - for file in os.listdir(path): - target = os.path.join(path, file) - if os.path.isdir(target): - file_search(target, query) - else: - if query in file.lower(): - data = f"SEARCH|{json.dumps([target, file])}" - send_ipc_message(data) + for _path, _dir, _files in os.walk(path, topdown = True): + for file in _files: + if query in file.lower(): + target = os.path.join(_path, file) + data = f"SEARCH|{json.dumps([target, file])}" + send_ipc_message(data) except Exception as e: print("Couldn't traverse to path. Might be permissions related...") traceback.print_exc() @@ -59,8 +58,9 @@ def _search_for_string(file, query): grep_result_set = {} padding = 15 with open(file, 'r') as fp: - # NOTE: I know there's an issue if there's a very large file with content all on one line will lower and dupe it. - # And, yes, it will only return one instance from the file. + # NOTE: I know there's an issue if there's a very large file with content + # all on one line will lower and dupe it. And, yes, it will only + # return one instance from the file. for i, raw in enumerate(fp): line = None llower = raw.lower() @@ -87,11 +87,11 @@ def _search_for_string(file, query): data = f"GREP|{json.dumps(grep_result_set)}" send_ipc_message(data) + @daemon_threaded def _search_for_string_threaded(file, query): _search_for_string(file, query) - def grep_search(path, query): try: for file in os.listdir(path): @@ -101,7 +101,7 @@ def grep_search(path, query): else: if target.lower().endswith(filter): size = os.path.getsize(target) - if size < 5000: + if not size > 5000: _search_for_string(target, query) else: _search_for_string_threaded(target, query) From e96d9e682db34ff0c8b1f416cf9cc4355c93e89d Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sun, 9 Oct 2022 20:59:44 -0500 Subject: [PATCH 14/30] Rewired settings, improved plugin structural coupling, cleanedup session file generation n load --- plugins/README.md | 24 +------ plugins/favorites/plugin.py | 12 ++-- plugins/file_properties/plugin.py | 11 ++- plugins/movie_tv_info/plugin.py | 11 ++- plugins/movie_tv_info/tmdbscraper/__init__.py | 3 + plugins/searcher/mixins/file_search_mixin.py | 8 ++- plugins/searcher/mixins/grep_search_mixin.py | 10 ++- plugins/searcher/plugin.py | 14 ++-- plugins/searcher/utils/ipc_server.py | 10 ++- plugins/searcher/utils/search.py | 68 +++++++++++-------- plugins/template/plugin.py | 2 +- plugins/trasher/plugin.py | 18 +++-- plugins/vod_thumbnailer/plugin.py | 12 ++-- plugins/youtube_download/plugin.py | 5 +- .../SolarFM/solarfm/__builtins__.py | 8 +-- .../solarfm-0.0.1/SolarFM/solarfm/__main__.py | 4 ++ .../solarfm-0.0.1/SolarFM/solarfm/app.py | 13 ++-- .../SolarFM/solarfm/core/controller.py | 22 +++--- .../SolarFM/solarfm/core/controller_data.py | 20 +++--- .../solarfm/core/mixins/ui/grid_mixin.py | 2 +- .../solarfm/core/mixins/ui/pane_mixin.py | 3 +- .../solarfm/core/mixins/ui/tab_mixin.py | 15 ++-- .../mixins/ui/widget_file_action_mixin.py | 18 ++--- .../solarfm/core/mixins/ui/window_mixin.py | 6 +- .../core/signals/keyboard_signals_mixin.py | 5 +- .../SolarFM/solarfm/plugins/plugin_base.py | 30 +++++++- .../solarfm/plugins/plugins_controller.py | 11 ++- .../solarfm/shellfm/windows/controller.py | 18 +++-- .../tabs/icons/mixins/xdg/IconTheme.py | 6 +- .../SolarFM/solarfm/utils/ipc_server.py | 2 +- .../SolarFM/solarfm/utils/settings.py | 13 ++++ 31 files changed, 232 insertions(+), 172 deletions(-) create mode 100644 plugins/movie_tv_info/tmdbscraper/__init__.py diff --git a/plugins/README.md b/plugins/README.md index 45cabc0..1f18e50 100644 --- a/plugins/README.md +++ b/plugins/README.md @@ -25,7 +25,7 @@ requests: {} = { 'pass_fm_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:f"], - f"{name}||do_save:s"] # Bind keys with method and key pare using list. Must pass "name" like shown with delimiter to its right. + f"{name}||do_save:s"] # Bind keys with method and key pare using list. Must pass "name" like shown with delimiter to its right. } ``` @@ -40,25 +40,3 @@ UI Targets:
  • context_menu
  • other
  • - -### 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() - -# Must define in plugin if "pass_ui_objects" is set and an array of valid glade UI IDs. -def set_ui_object_collection(self, ui_objects): - self._ui_objects = ui_objects - -``` diff --git a/plugins/favorites/plugin.py b/plugins/favorites/plugin.py index 3a68f05..e55fe8a 100644 --- a/plugins/favorites/plugin.py +++ b/plugins/favorites/plugin.py @@ -41,11 +41,6 @@ class Plugin(PluginBase): self._selected = None - def get_ui_element(self): - button = Gtk.Button(label=self.name) - button.connect("button-release-event", self._show_favorites_menu) - return button - def run(self): self._builder = Gtk.Builder() self._builder.add_from_file(self._GLADE_FILE) @@ -76,9 +71,14 @@ class Plugin(PluginBase): f.write('[]') + def generate_reference_ui_element(self): + button = Gtk.Button(label=self.name) + button.connect("button-release-event", self._show_favorites_menu) + return button + @threaded def _get_state(self, widget=None, eve=None): - self._event_system.emit("get_current_state + self._event_system.emit("get_current_state") @threaded diff --git a/plugins/file_properties/plugin.py b/plugins/file_properties/plugin.py index 2cb6262..ec41032 100644 --- a/plugins/file_properties/plugin.py +++ b/plugins/file_properties/plugin.py @@ -83,11 +83,6 @@ class Plugin(PluginBase): } - def get_ui_element(self): - button = Gtk.Button(label=self.name) - button.connect("button-release-event", self._show_properties_page) - return button - def run(self): self._builder = Gtk.Builder() self._builder.add_from_file(self._GLADE_FILE) @@ -103,11 +98,15 @@ class Plugin(PluginBase): self._file_owner = self._builder.get_object("file_owner") self._file_group = self._builder.get_object("file_group") + def generate_reference_ui_element(self): + button = Gtk.Button(label=self.name) + button.connect("button-release-event", self._show_properties_page) + return button @threaded def _show_properties_page(self, widget=None, eve=None): - event_system.emit("get_current_state + event_system.emit("get_current_state") state = self._fm_state self._event_message = None diff --git a/plugins/movie_tv_info/plugin.py b/plugins/movie_tv_info/plugin.py index f7f9c3c..3f8459e 100644 --- a/plugins/movie_tv_info/plugin.py +++ b/plugins/movie_tv_info/plugin.py @@ -45,12 +45,6 @@ class Plugin(PluginBase): self._trailer_link = None - - def get_ui_element(self): - button = Gtk.Button(label=self.name) - button.connect("button-release-event", self._show_info_page) - return button - def run(self): self._builder = Gtk.Builder() self._builder.add_from_file(self._GLADE_FILE) @@ -75,6 +69,11 @@ class Plugin(PluginBase): self._file_hash = self._builder.get_object("file_hash") self._trailer_link = self._builder.get_object("trailer_link") + def generate_reference_ui_element(self): + button = Gtk.Button(label=self.name) + button.connect("button-release-event", self._show_info_page) + return button + @threaded def _show_info_page(self, widget=None, eve=None): self._event_system.emit("get_current_state") diff --git a/plugins/movie_tv_info/tmdbscraper/__init__.py b/plugins/movie_tv_info/tmdbscraper/__init__.py new file mode 100644 index 0000000..d36fa8c --- /dev/null +++ b/plugins/movie_tv_info/tmdbscraper/__init__.py @@ -0,0 +1,3 @@ +""" + Pligin Module +""" diff --git a/plugins/searcher/mixins/file_search_mixin.py b/plugins/searcher/mixins/file_search_mixin.py index ffcc32a..749cf9d 100644 --- a/plugins/searcher/mixins/file_search_mixin.py +++ b/plugins/searcher/mixins/file_search_mixin.py @@ -1,8 +1,10 @@ # Python imports -import threading, subprocess, signal, time, json, shlex +import threading, subprocess, signal, json, shlex # Lib imports -from gi.repository import GLib +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk, GLib # Application imports from ..widgets.file_preview_widget import FilePreviewWidget @@ -61,6 +63,8 @@ class FileSearchMixin: self._list_proc = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None) def _load_file_ui(self, data): + Gtk.main_iteration() + if not data in ("", None): jdata = json.loads( data ) target = jdata[0] diff --git a/plugins/searcher/mixins/grep_search_mixin.py b/plugins/searcher/mixins/grep_search_mixin.py index dc8372d..ad5c9da 100644 --- a/plugins/searcher/mixins/grep_search_mixin.py +++ b/plugins/searcher/mixins/grep_search_mixin.py @@ -1,8 +1,10 @@ # Python imports -import threading, subprocess, signal, time, json, shlex +import threading, subprocess, signal, json, shlex # Lib imports -from gi.repository import GLib +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk, GLib # Application imports from ..widgets.grep_preview_widget import GrepPreviewWidget @@ -53,7 +55,7 @@ class GrepSearchMixin: def _exec_grep_query(self, widget=None, eve=None): query = widget.get_text() - if not query in ("", None): + if not query.strip() in ("", None): self.grep_query = query target_dir = shlex.quote( self._fm_state.tab.get_current_directory() ) @@ -61,6 +63,8 @@ class GrepSearchMixin: self._grep_proc = subprocess.Popen(command, cwd=self.path, stdin=None, stdout=None, stderr=None) def _load_grep_ui(self, data): + Gtk.main_iteration() + if not data in ("", None): jdata = json.loads( data ) jkeys = jdata.keys() diff --git a/plugins/searcher/plugin.py b/plugins/searcher/plugin.py index 4b66a63..f941dc9 100644 --- a/plugins/searcher/plugin.py +++ b/plugins/searcher/plugin.py @@ -52,11 +52,6 @@ class Plugin(IPCServer, FileSearchMixin, GrepSearchMixin, PluginBase): self.search_query = "" - def get_ui_element(self): - button = Gtk.Button(label=self.name) - button.connect("button-release-event", self._show_page) - return button - def run(self): self._builder = Gtk.Builder() self._builder.add_from_file(self._GLADE_FILE) @@ -84,6 +79,11 @@ class Plugin(IPCServer, FileSearchMixin, GrepSearchMixin, PluginBase): self.create_ipc_listener() + def generate_reference_ui_element(self): + button = Gtk.Button(label=self.name) + button.connect("button-release-event", self._show_page) + return button + def _show_page(self, widget=None, eve=None): self._event_system.emit("get_current_state") @@ -109,6 +109,8 @@ class Plugin(IPCServer, FileSearchMixin, GrepSearchMixin, PluginBase): self.grep_list_parent.add(self._grep_list) self.grep_list_parent.show_all() + Gtk.main_iteration() + def reset_file_list_box(self) -> None: try: child = self.file_list_parent.get_children()[0] @@ -121,3 +123,5 @@ class Plugin(IPCServer, FileSearchMixin, GrepSearchMixin, PluginBase): self._file_list.set_orientation(Gtk.Orientation.VERTICAL) self.file_list_parent.add(self._file_list) self.file_list_parent.show_all() + + Gtk.main_iteration() diff --git a/plugins/searcher/utils/ipc_server.py b/plugins/searcher/utils/ipc_server.py index 96969cf..e4ca2aa 100644 --- a/plugins/searcher/utils/ipc_server.py +++ b/plugins/searcher/utils/ipc_server.py @@ -1,11 +1,9 @@ # Python imports -import os, threading, time, pickle +import os, threading, pickle from multiprocessing.connection import Listener, Client # Lib imports -import gi -gi.require_version('Gtk', '3.0') -from gi.repository import Gtk, GLib +from gi.repository import GLib # Application imports @@ -64,12 +62,12 @@ class IPCServer: if "SEARCH|" in msg: file = msg.split("SEARCH|")[1].strip() if file: - GLib.idle_add(self._load_file_ui, file) + GLib.idle_add(self._load_file_ui, file, priority=GLib.PRIORITY_LOW) if "GREP|" in msg: data = msg.split("GREP|")[1].strip() if data: - GLib.idle_add(self._load_grep_ui, data) + GLib.idle_add(self._load_grep_ui, data, priority=GLib.PRIORITY_LOW) conn.close() diff --git a/plugins/searcher/utils/search.py b/plugins/searcher/utils/search.py index 5c1efca..692fbc2 100755 --- a/plugins/searcher/utils/search.py +++ b/plugins/searcher/utils/search.py @@ -16,8 +16,9 @@ from multiprocessing.connection import Client _ipc_address = f'/tmp/solarfm-search_grep-ipc.sock' _ipc_authkey = b'' + bytes(f'solarfm-search_grep-ipc', 'utf-8') -filter = (".cpp", ".css", ".c", ".go", ".html", ".htm", ".java", ".js", ".json", ".lua", ".md", ".py", ".rs", ".toml", ".xml", ".pom") + \ - (".txt", ".text", ".sh", ".cfg", ".conf", ".log") +filter = (".mkv", ".mp4", ".webm", ".avi", ".mov", ".m4v", ".mpg", ".mpeg", ".wmv", ".flv") + \ + (".png", ".jpg", ".jpeg", ".gif", ".ico", ".tga", ".webp") + \ + (".psf", ".mp3", ".ogg", ".flac", ".m4a") # NOTE: Threads WILL NOT die with parent's destruction. def threaded(fn): @@ -33,12 +34,12 @@ def daemon_threaded(fn): def send_ipc_message(message) -> None: - with Client(address=_ipc_address, family="AF_UNIX", authkey=_ipc_authkey) as conn: - conn.send(message) - conn.close() + conn = Client(address=_ipc_address, family="AF_UNIX", authkey=_ipc_authkey) + conn.send(message) + conn.close() # NOTE: Kinda important as this prevents overloading the UI thread - time.sleep(0.04) + time.sleep(0.05) def file_search(path, query): @@ -57,35 +58,44 @@ def _search_for_string(file, query): b64_file = base64.urlsafe_b64encode(file.encode('utf-8')).decode('utf-8') grep_result_set = {} padding = 15 + with open(file, 'r') as fp: # NOTE: I know there's an issue if there's a very large file with content # all on one line will lower and dupe it. And, yes, it will only # return one instance from the file. - for i, raw in enumerate(fp): - line = None - llower = raw.lower() - if not query in llower: - continue + try: + for i, raw in enumerate(fp): + line = None + llower = raw.lower() + if not query in llower: + continue - if len(raw) > 72: - start = 0 - end = len(raw) - 1 - index = llower.index(query) - sindex = llower.index(query) - 15 if index >= 15 else abs(start - index) - index - eindex = sindex + 15 if end > (index + 15) else abs(index - end) + index - line = raw[sindex:eindex] - else: - line = raw + if len(raw) > 72: + start = 0 + end = len(raw) - 1 + index = llower.index(query) + sindex = llower.index(query) - 15 if index >= 15 else abs(start - index) - index + eindex = sindex + 15 if end > (index + 15) else abs(index - end) + index + line = raw[sindex:eindex] + else: + line = raw - b64_line = base64.urlsafe_b64encode(line.encode('utf-8')).decode('utf-8') - if f"{b64_file}" in grep_result_set.keys(): - grep_result_set[f"{b64_file}"][f"{i+1}"] = b64_line - else: - grep_result_set[f"{b64_file}"] = {} - grep_result_set[f"{b64_file}"] = {f"{i+1}": b64_line} + b64_line = base64.urlsafe_b64encode(line.encode('utf-8')).decode('utf-8') + if f"{b64_file}" in grep_result_set.keys(): + grep_result_set[f"{b64_file}"][f"{i+1}"] = b64_line + else: + grep_result_set[f"{b64_file}"] = {} + grep_result_set[f"{b64_file}"] = {f"{i+1}": b64_line} + + except Exception as e: + ... + + try: + data = f"GREP|{json.dumps(grep_result_set)}" + send_ipc_message(data) + except Exception as e: + ... - data = f"GREP|{json.dumps(grep_result_set)}" - send_ipc_message(data) @daemon_threaded @@ -99,7 +109,7 @@ def grep_search(path, query): if os.path.isdir(target): grep_search(target, query) else: - if target.lower().endswith(filter): + if not target.lower().endswith(filter): size = os.path.getsize(target) if not size > 5000: _search_for_string(target, query) diff --git a/plugins/template/plugin.py b/plugins/template/plugin.py index dff5d40..5535f4d 100644 --- a/plugins/template/plugin.py +++ b/plugins/template/plugin.py @@ -33,7 +33,7 @@ class Plugin(PluginBase): # where self.name should not be needed for message comms - def get_ui_element(self): + def generate_reference_ui_element(self): button = Gtk.Button(label=self.name) button.connect("button-release-event", self.send_message) return button diff --git a/plugins/trasher/plugin.py b/plugins/trasher/plugin.py index cbfe70d..78cc759 100644 --- a/plugins/trasher/plugin.py +++ b/plugins/trasher/plugin.py @@ -39,7 +39,14 @@ class Plugin(PluginBase): self.trashman.regenerate() - def get_ui_element(self): + + def run(self): + self._event_system.subscribe("show_trash_buttons", self._show_trash_buttons) + self._event_system.subscribe("hide_trash_buttons", self._hide_trash_buttons) + self._event_system.subscribe("delete_files", self.delete_files) + self._event_system.subscribe("trash_files", self.trash_files) + + def generate_reference_ui_element(self): self._builder = Gtk.Builder() self._builder.add_from_file(self._GLADE_FILE) @@ -61,10 +68,6 @@ class Plugin(PluginBase): return trasher - def run(self): - self._event_system.subscribe("show_trash_buttons", self._show_trash_buttons) - self._event_system.subscribe("hide_trash_buttons", self._hide_trash_buttons) - def _show_trash_buttons(self): self._builder.get_object("restore_from_trash").show() self._builder.get_object("empty_trash").show() @@ -74,6 +77,7 @@ class Plugin(PluginBase): self._builder.get_object("empty_trash").hide() def delete_files(self, widget = None, eve = None): + self._event_system.emit("do_hide_context_menu") self._event_system.emit("get_current_state") state = self._fm_state uris = state.selected_files @@ -97,19 +101,23 @@ class Plugin(PluginBase): break def trash_files(self, widget = None, eve = None, verbocity = False): + self._event_system.emit("do_hide_context_menu") self._event_system.emit("get_current_state") state = self._fm_state for uri in state.selected_files: self.trashman.trash(uri, verbocity) def restore_trash_files(self, widget = None, eve = None, verbocity = False): + self._event_system.emit("do_hide_context_menu") self._event_system.emit("get_current_state") state = self._fm_state for uri in state.selected_files: self.trashman.restore(filename=uri.split("/")[-1], verbose = verbocity) def empty_trash(self, widget = None, eve = None, verbocity = False): + self._event_system.emit("do_hide_context_menu") self.trashman.empty(verbose = verbocity) def go_to_trash(self, widget = None, eve = None, verbocity = False): + self._event_system.emit("do_hide_context_menu") self._event_system.emit("go_to_path", self.trash_files_path) diff --git a/plugins/vod_thumbnailer/plugin.py b/plugins/vod_thumbnailer/plugin.py index c74f551..e730797 100644 --- a/plugins/vod_thumbnailer/plugin.py +++ b/plugins/vod_thumbnailer/plugin.py @@ -44,11 +44,6 @@ class Plugin(PluginBase): self._file_hash = None - def get_ui_element(self): - button = Gtk.Button(label=self.name) - button.connect("button-release-event", self._show_thumbnailer_page) - return button - def run(self): self._builder = Gtk.Builder() self._builder.add_from_file(self._GLADE_FILE) @@ -72,10 +67,15 @@ class Plugin(PluginBase): self._thumbnail_preview_img = self._builder.get_object("thumbnail_preview_img") self._file_hash = self._builder.get_object("file_hash") + def generate_reference_ui_element(self): + button = Gtk.Button(label=self.name) + button.connect("button-release-event", self._show_thumbnailer_page) + return button + @threaded def _show_thumbnailer_page(self, widget=None, eve=None): - self._event_system.emit("get_current_state + self._event_system.emit("get_current_state") state = self._fm_state self._event_message = None diff --git a/plugins/youtube_download/plugin.py b/plugins/youtube_download/plugin.py index da13c75..1b4ce50 100644 --- a/plugins/youtube_download/plugin.py +++ b/plugins/youtube_download/plugin.py @@ -33,8 +33,7 @@ class Plugin(PluginBase): self.name = "Youtube Download" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus # where self.name should not be needed for message comms - - def get_ui_element(self): + def generate_reference_ui_element(self): button = Gtk.Button(label=self.name) button.connect("button-release-event", self._do_download) return button @@ -44,7 +43,7 @@ class Plugin(PluginBase): def _do_download(self, widget=None, eve=None): - self._event_system.emit("get_current_state + self._event_system.emit("get_current_state") dir = self._fm_state.tab.get_current_directory() self._download(dir) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py index 428f16f..9ed03c8 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py @@ -6,7 +6,7 @@ import builtins, threading # Application imports from utils.event_system import EventSystem from utils.endpoint_registry import EndpointRegistry - +from utils.settings import Settings @@ -28,11 +28,11 @@ def daemon_threaded_wrapper(fn): # NOTE: Just reminding myself we can add to builtins two different ways... # __builtins__.update({"event_system": Builtins()}) builtins.app_name = "SolarFM" +builtins.settings = Settings() +builtins.logger = settings.get_logger() builtins.event_system = EventSystem() builtins.endpoint_registry = EndpointRegistry() + builtins.threaded = threaded_wrapper builtins.daemon_threaded = daemon_threaded_wrapper builtins.event_sleep_time = 0.05 -builtins.trace_debug = False -builtins.debug = False -builtins.app_settings = None diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py index 2dfcfe2..9bfffcb 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py @@ -15,6 +15,7 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk # Application imports +from __builtins__ import * from app import Application @@ -27,6 +28,9 @@ if __name__ == "__main__": parser = argparse.ArgumentParser() # Add long and short arguments + parser.add_argument("--debug", "-d", default="false", help="Do extra console messaging.") + parser.add_argument("--trace-debug", "-td", default="false", help="Disable saves, ignore IPC lock, do extra console messaging.") + parser.add_argument("--new-tab", "-t", default="", help="Open a file into new tab.") parser.add_argument("--new-window", "-w", default="", help="Open a file into a new window.") # Read arguments (If any...) 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 a16c779..7df637a 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py @@ -4,7 +4,7 @@ import os, inspect, time # Lib imports # Application imports -from __builtins__ import * + from utils.ipc_server import IPCServer from utils.settings import Settings from core.controller import Controller @@ -22,8 +22,14 @@ class Application(IPCServer): def __init__(self, args, unknownargs): super(Application, self).__init__() + if args.debug == "true": + settings.set_debug(True) - if not trace_debug: + if args.trace_debug == "true": + settings.set_trace_debug(True) + + # NOTE: Instance found, sending files to it... + if not settings.is_trace_debug(): self.create_ipc_listener() time.sleep(0.05) @@ -41,10 +47,9 @@ class Application(IPCServer): raise AppLaunchException(f"IPC Server Exists: Will send path(s) to it and close...\nNote: If no fm exists, remove /tmp/{app_name}-ipc.sock") - settings = Settings() settings.create_window() - controller = Controller(args, unknownargs, settings) + controller = Controller(args, unknownargs) if not controller: raise ControllerStartExceptio("Controller exited and doesn't exist...") diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py index c034bd3..428edb4 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py @@ -18,18 +18,18 @@ from .controller_data import Controller_Data class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMixin, Controller_Data): """ Controller coordinates the mixins and is somewhat the root hub of it all. """ - def __init__(self, args, unknownargs, _settings): - self.setup_controller_data(_settings) + def __init__(self, args, unknownargs): + self.setup_controller_data() self.window.show() self.generate_windows(self.fm_controller_data) self.plugins.launch_plugins() - if debug: + if settings.is_debug(): self.window.set_interactive_debugging(True) - - if not trace_debug: + # NOTE: Open files if passed in from cli and not trace debugging... + if not settings.is_trace_debug(): self._subscribe_to_events() if unknownargs: @@ -51,7 +51,9 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi event_system.subscribe("do_hide_context_menu", self.do_hide_context_menu) def tear_down(self, widget=None, eve=None): - self.fm_controller.save_state() + if not settings.is_trace_debug(): + self.fm_controller.save_state() + time.sleep(event_sleep_time) Gtk.main_quit() @@ -62,7 +64,9 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi save_load_dialog = self.builder.get_object("save_load_dialog") if action == "save_session": - self.fm_controller.save_state() + if not settings.is_trace_debug(): + self.fm_controller.save_state() + return elif action == "save_session_as": save_load_dialog.set_action(Gtk.FileChooserAction.SAVE) @@ -88,8 +92,8 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi save_load_dialog.hide() def load_session(self, session_json): - if debug: - self.logger.debug(f"Session Data: {session_json}") + if settings.is_debug(): + logger.debug(f"Session Data: {session_json}") self.ctrl_down = False self.shift_down = False diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py index 3e69f9f..14524e0 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py @@ -28,17 +28,15 @@ class Controller_Data: """ Controller_Data contains most of the state of the app at ay given time. It also has some support methods. """ __slots__ = "settings", "builder", "logger", "keybindings", "trashman", "fm_controller", "window", "window1", "window2", "window3", "window4" - def setup_controller_data(self, _settings: type) -> None: - self.settings = _settings - self.builder = self.settings.get_builder() - self.logger = self.settings.get_logger() - self.keybindings = self.settings.get_keybindings() + def setup_controller_data(self) -> None: + self.builder = settings.get_builder() + self.keybindings = settings.get_keybindings() self.fm_controller = WindowController() - self.plugins = PluginsController(_settings) + self.plugins = PluginsController() self.fm_controller_data = self.fm_controller.get_state_from_file() - self.window = self.settings.get_main_window() + self.window = settings.get_main_window() self.window1 = self.builder.get_object("window_1") self.window2 = self.builder.get_object("window_2") self.window3 = self.builder.get_object("window_3") @@ -64,7 +62,7 @@ class Controller_Data: self.trash_files_path = f"{GLib.get_user_data_dir()}/Trash/files" self.trash_info_path = f"{GLib.get_user_data_dir()}/Trash/info" - self.icon_theme = self.settings.get_icon_theme() + self.icon_theme = settings.get_icon_theme() # In compress commands: # %n: First selected filename/dir to archive @@ -116,9 +114,9 @@ class Controller_Data: self.shift_down = False self.alt_down = False - self.success_color = self.settings.get_success_color() - self.warning_color = self.settings.get_warning_color() - self.error_color = self.settings.get_error_color() + self.success_color = settings.get_success_color() + self.warning_color = settings.get_warning_color() + self.error_color = settings.get_error_color() # sys.excepthook = self.custom_except_hook self.window.connect("delete-event", self.tear_down) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py index 2d3afeb..c93a3ca 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py @@ -61,7 +61,7 @@ class GridMixin: self.create_icon(i, tab, store, dir, file[0]) # NOTE: Not likely called often from here but it could be useful - if save_state: + if save_state and not trace_debug: self.fm_controller.save_state() @threaded diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/pane_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/pane_mixin.py index 3d2c888..380efa7 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/pane_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/pane_mixin.py @@ -56,4 +56,5 @@ class PaneMixin: def _save_state(self, state, pane_index): window = self.fm_controller.get_window_by_index(pane_index - 1) window.set_is_hidden(state) - self.fm_controller.save_state() + if not settings.is_trace_debug(): + self.fm_controller.save_state() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/tab_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/tab_mixin.py index 539f840..683e6b4 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/tab_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/tab_mixin.py @@ -22,7 +22,7 @@ class TabMixin(GridMixin): notebook = self.builder.get_object(f"window_{wid}") path_entry = self.builder.get_object(f"path_entry") tab = self.fm_controller.add_tab_for_window_by_nickname(f"window_{wid}") - tab.logger = self.logger + tab.logger = logger tab.set_wid(wid) if not path: @@ -63,7 +63,8 @@ class TabMixin(GridMixin): watcher.cancel() self.get_fm_window(wid).delete_tab_by_id(tid) notebook.remove_page(page) - self.fm_controller.save_state() + if not settings.is_trace_debug(): + self.fm_controller.save_state() self.set_window_title() def on_tab_reorder(self, child, page_num, new_index): @@ -80,7 +81,8 @@ class TabMixin(GridMixin): tab = window.get_tab_by_id(tid) self.set_file_watcher(tab) - self.fm_controller.save_state() + if not settings.is_trace_debug(): + self.fm_controller.save_state() def on_tab_switch_update(self, notebook, content=None, index=None): self.selected_files.clear() @@ -115,7 +117,8 @@ class TabMixin(GridMixin): tab_label.set_label(tab.get_end_of_path()) self.set_window_title() self.set_file_watcher(tab) - self.fm_controller.save_state() + if not settings.is_trace_debug(): + self.fm_controller.save_state() def do_action_from_bar_controls(self, widget, eve=None): action = widget.get_name() @@ -127,7 +130,9 @@ class TabMixin(GridMixin): if action == "create_tab": dir = tab.get_current_directory() self.create_tab(wid, None, dir) - self.fm_controller.save_state() + if not settings.is_trace_debug(): + self.fm_controller.save_state() + return if action == "go_up": tab.pop_from_path() 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 0058147..e680801 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 @@ -40,8 +40,8 @@ class WidgetFileActionMixin: if tab.get_dir_watcher(): watcher = tab.get_dir_watcher() watcher.cancel() - if debug: - self.logger.debug(f"Watcher Is Cancelled: {watcher.is_cancelled()}") + if settings.is_debug(): + logger.debug(f"Watcher Is Cancelled: {watcher.is_cancelled()}") cur_dir = tab.get_current_directory() @@ -59,8 +59,8 @@ 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 settings.is_debug(): + logger.debug(eve_type) if eve_type in [Gio.FileMonitorEvent.MOVED_IN, Gio.FileMonitorEvent.MOVED_OUT]: self.update_on_soft_lock_end(data[0]) @@ -389,11 +389,11 @@ class WidgetFileActionMixin: target = Gio.File.new_for_path(full_path) start = "-copy" - if debug: - self.logger.debug(f"Path: {full_path}") - self.logger.debug(f"Base Path: {base_path}") - self.logger.debug(f'Name: {file_name}') - self.logger.debug(f"Extension: {extension}") + if settings.is_debug(): + logger.debug(f"Path: {full_path}") + logger.debug(f"Base Path: {base_path}") + logger.debug(f'Name: {file_name}') + logger.debug(f"Extension: {extension}") i = 2 while target.query_exists(): diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py index 2ab6be1..fddb9d3 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py @@ -24,8 +24,8 @@ class WindowMixin(TabMixin): for j, value in enumerate(session_json): i = j + 1 notebook_tggl_button = self.builder.get_object(f"tggl_notebook_{i}") - is_hidden = True if value[0]["window"]["isHidden"] == "True" else False - tabs = value[0]["window"]["tabs"] + is_hidden = True if value["window"]["isHidden"] == "True" else False + tabs = value["window"]["tabs"] self.fm_controller.create_window() notebook_tggl_button.set_active(True) @@ -112,7 +112,7 @@ class WindowMixin(TabMixin): file_size = file_info.get_size() combined_size += file_size except WindowException as e: - if debug: + if settings.is_debug(): print(repr(e)) 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 e4151f8..bde0689 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 @@ -55,7 +55,7 @@ class KeyboardSignalsMixin: sender, eve_type = mapping.split("||") self.handle_plugin_key_event(sender, eve_type) else: - if debug: + if settings.is_debug(): print(f"on_global_key_release_controller > key > {keyname}") if self.ctrl_down: @@ -91,5 +91,6 @@ class KeyboardSignalsMixin: self.get_fm_window(wid).delete_tab_by_id(tid) notebook.remove_page(page) - self.fm_controller.save_state() + if not trace_debug: + self.fm_controller.save_state() self.set_window_title() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugin_base.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugin_base.py index effd377..44aec62 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugin_base.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugin_base.py @@ -6,6 +6,10 @@ import os, time # Application imports +class PluginBaseException(Exception): + ... + + class PluginBase: def __init__(self): self.name = "Example Plugin" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus @@ -18,14 +22,23 @@ class PluginBase: def set_fm_event_system(self, fm_event_system): + """ + Requests Key: 'pass_fm_events': "true" + Must define in plugin if "pass_fm_events" is set to "true" string. + """ self._event_system = fm_event_system def set_ui_object_collection(self, ui_objects): + """ + Requests Key: "pass_ui_objects": [""] + Request reference to a UI component. Will be passed back as array to plugin. + Must define in plugin if set and an array of valid glade UI IDs is given. + """ self._ui_objects = ui_objects def clear_children(self, widget: type) -> None: - ''' Clear children of a gtk widget. ''' + """ Clear children of a gtk widget. """ for child in widget.get_children(): widget.remove(child) @@ -34,3 +47,18 @@ class PluginBase: def _update_fm_state_info(self, state): self._fm_state = state + + def generate_reference_ui_element(self): + """ + Requests Key: 'ui_target': "plugin_control_list", + Must define regardless if needed and can 'pass' if plugin doesn't use it. + Must return a widget if "ui_target" is set. + """ + raise PluginBaseException("Method hasn't been overriden...") + + def run(self): + """ + Must define regardless if needed and can 'pass' if plugin doesn't need it. + Is intended to be used to setup internal signals or custom Gtk Builders/UI logic. + """ + raise PluginBaseException("Method hasn't been overriden...") diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins_controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins_controller.py index 63cccfe..41adcef 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins_controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins_controller.py @@ -20,14 +20,13 @@ class InvalidPluginException(Exception): class PluginsController: """PluginsController controller""" - def __init__(self, settings: type): + def __init__(self): path = os.path.dirname(os.path.realpath(__file__)) sys.path.insert(0, path) # NOTE: I think I'm not using this correctly... - self._settings = settings - self._builder = self._settings.get_builder() - self._plugins_path = self._settings.get_plugins_path() - self._keybindings = self._settings.get_keybindings() + self._builder = settings.get_builder() + self._plugins_path = settings.get_plugins_path() + self._keybindings = settings.get_keybindings() self._plugins_dir_watcher = None self._plugin_collection = [] @@ -95,7 +94,7 @@ class PluginsController: keys = loading_data.keys() if "ui_target" in keys: - loading_data["ui_target"].add( plugin.reference.get_ui_element() ) + loading_data["ui_target"].add( plugin.reference.generate_reference_ui_element() ) loading_data["ui_target"].show_all() if "pass_ui_objects" in keys: diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/controller.py index abec786..f2b0e16 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/controller.py @@ -158,17 +158,15 @@ class WindowController: tabs.append(tab.get_current_directory()) windows.append( - [ - { - 'window':{ - "ID": window.get_id(), - "Name": window.get_name(), - "Nickname": window.get_nickname(), - "isHidden": f"{window.is_hidden()}", - 'tabs': tabs - } + { + 'window':{ + "ID": window.get_id(), + "Name": window.get_name(), + "Nickname": window.get_nickname(), + "isHidden": f"{window.is_hidden()}", + 'tabs': tabs } - ] + } ) with open(session_file, 'w') as outfile: diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/IconTheme.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/IconTheme.py index 2ff3c05..61ee2c3 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/IconTheme.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/xdg/IconTheme.py @@ -322,10 +322,8 @@ def getIconPath(iconname, size = None, theme = None, extensions = ["png", "svg", icon_cache[tmp] = [time.time(), icon] return icon except UnicodeDecodeError as e: - if debug: - raise e - else: - pass + ... + # we haven't found anything? "hicolor" is our fallback if theme != "hicolor": diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py index f71691b..5aa0ebd 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py @@ -59,7 +59,7 @@ class IPCServer: def handle_message(self, conn, start_time) -> None: while True: msg = conn.recv() - if debug: + if settings.is_debug(): print(msg) if "FILE|" in msg: 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 3826e3d..0967a25 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 @@ -63,6 +63,9 @@ class Settings: self._builder = Gtk.Builder() self._builder.add_from_file(self._GLADE_FILE) + self._trace_debug = False + self._debug = False + def create_window(self) -> None: # Get window and connect signals @@ -111,3 +114,13 @@ class Settings: def get_success_color(self) -> str: return self._success_color def get_warning_color(self) -> str: return self._warning_color def get_error_color(self) -> str: return self._error_color + + def is_trace_debug(self) -> str: return self._trace_debug + def is_debug(self) -> str: return self._debug + + + def set_trace_debug(self, trace_debug): + self._trace_debug = trace_debug + + def set_debug(self, debug): + self._debug = debug From 49ed89201a43e52621d33f107e55dfa99a32de83 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sun, 9 Oct 2022 21:56:12 -0500 Subject: [PATCH 15/30] Externalized archiver to plugin --- plugins/archiver/__init__.py | 3 + plugins/archiver/__main__.py | 3 + plugins/archiver/archiver.glade | 164 +++++++++++++++++ plugins/archiver/manifest.json | 12 ++ plugins/archiver/plugin.py | 122 +++++++++++++ .../SolarFM/solarfm/core/controller.py | 2 - .../SolarFM/solarfm/core/controller_data.py | 26 --- .../core/mixins/exception_hook_mixin.py | 5 - .../solarfm/core/mixins/show_hide_mixin.py | 20 --- .../mixins/ui/widget_file_action_mixin.py | 13 -- .../usr/share/solarfm/Main_Window.glade | 165 ------------------ 11 files changed, 304 insertions(+), 231 deletions(-) create mode 100644 plugins/archiver/__init__.py create mode 100644 plugins/archiver/__main__.py create mode 100644 plugins/archiver/archiver.glade create mode 100644 plugins/archiver/manifest.json create mode 100644 plugins/archiver/plugin.py diff --git a/plugins/archiver/__init__.py b/plugins/archiver/__init__.py new file mode 100644 index 0000000..d36fa8c --- /dev/null +++ b/plugins/archiver/__init__.py @@ -0,0 +1,3 @@ +""" + Pligin Module +""" diff --git a/plugins/archiver/__main__.py b/plugins/archiver/__main__.py new file mode 100644 index 0000000..a576329 --- /dev/null +++ b/plugins/archiver/__main__.py @@ -0,0 +1,3 @@ +""" + Pligin Package +""" diff --git a/plugins/archiver/archiver.glade b/plugins/archiver/archiver.glade new file mode 100644 index 0000000..1879b61 --- /dev/null +++ b/plugins/archiver/archiver.glade @@ -0,0 +1,164 @@ + + + + + + $(which 7za || echo 7zr) a %o %N + + + False + True + center + dialog + center + True + True + + + False + vertical + 2 + + + False + end + + + gtk-cancel + True + True + True + True + + + True + True + 0 + + + + + gtk-ok + True + True + True + True + + + True + True + 1 + + + + + False + False + 0 + + + + + True + False + vertical + + + True + False + True + + + True + False + Compress Commands: + 0.20000000298023224 + + + + + + False + True + 0 + + + + + + + + True + False + Archive Format: + 1 + + + + + + False + True + 2 + + + + + True + False + 0 + 0 + + 7Zip (*.7z) + Zip (*.zip *.ZIP) + RAR (*.rar *.RAR) + Tar (*.tar) + Tar bzip2 (*.tar.bz2) + Tar Gzip (*.tar.gz *.tgz) + Tar xz (*.tar.xz *.txz) + Gzip (*.gz) + XZ (*.xz) + + + + + False + True + 3 + + + + + False + True + 0 + + + + + 72 + True + True + arc_command_buffer + + + True + True + 1 + + + + + False + True + 2 + + + + + + button21 + button22 + + + diff --git a/plugins/archiver/manifest.json b/plugins/archiver/manifest.json new file mode 100644 index 0000000..761a2eb --- /dev/null +++ b/plugins/archiver/manifest.json @@ -0,0 +1,12 @@ +{ + "manifest": { + "name": "Archiver", + "author": "ITDominator", + "version": "0.0.1", + "support": "", + "requests": { + "ui_target": "context_menu", + "pass_fm_events": "true" + } + } +} diff --git a/plugins/archiver/plugin.py b/plugins/archiver/plugin.py new file mode 100644 index 0000000..1c02225 --- /dev/null +++ b/plugins/archiver/plugin.py @@ -0,0 +1,122 @@ +# Python imports +import os, threading, subprocess, inspect, shlex + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +# Application imports +from plugins.plugin_base import PluginBase + + +# NOTE: Threads WILL NOT die with parent's destruction. +def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() + return wrapper + +# NOTE: Threads WILL die with parent's destruction. +def daemon_threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() + return wrapper + + + + +class Plugin(PluginBase): + def __init__(self): + super().__init__() + self.path = os.path.dirname(os.path.realpath(__file__)) + self._GLADE_FILE = f"{self.path}/archiver.glade" + self.name = "Archiver" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus + # where self.name should not be needed for message comms + self._archiver_dialogue = None + self._arc_command_buffer = None + + # In compress commands: + # %n: First selected filename/dir to archive + # %N: All selected filenames/dirs to archive, or (with %O) a single filename + # %o: Resulting single archive file + # %O: Resulting archive per source file/directory (use changes %N meaning) + # + # In extract commands: + # %x: Archive file to extract + # %g: Unique extraction target filename with optional subfolder + # %G: Unique extraction target filename, never with subfolder + # + # In list commands: + # %x: Archive to list + # + # Plus standard bash variables are accepted. + self.arc_commands = [ '$(which 7za || echo 7zr) a %o %N', + 'zip -r %o %N', + 'rar a -r %o %N', + 'tar -cvf %o %N', + 'tar -cvjf %o %N', + 'tar -cvzf %o %N', + 'tar -cvJf %o %N', + 'gzip -c %N > %O', + 'xz -cz %N > %O' + ] + + + def generate_reference_ui_element(self): + self._builder = Gtk.Builder() + self._builder.add_from_file(self._GLADE_FILE) + + classes = [self] + handlers = {} + for c in classes: + methods = None + try: + methods = inspect.getmembers(c, predicate=inspect.ismethod) + handlers.update(methods) + except Exception as e: + print(repr(e)) + + self._builder.connect_signals(handlers) + + self._archiver_dialogue = self._builder.get_object("archiver_dialogue") + self._arc_command_buffer = self._builder.get_object("arc_command_buffer") + + button = Gtk.Button(label=self.name) + button.connect("button-release-event", self.show_archiver_dialogue) + return button + + def run(self): + ... + + def show_archiver_dialogue(self, widget=None, eve=None): + self._event_system.emit("get_current_state") + state = self._fm_state + + self._archiver_dialogue.set_action(Gtk.FileChooserAction.SAVE) + self._archiver_dialogue.set_current_folder(state.tab.get_current_directory()) + self._archiver_dialogue.set_current_name("arc.7z") + + response = self._archiver_dialogue.run() + if response == Gtk.ResponseType.OK: + save_target = self._archiver_dialogue.get_filename() + self.archive_files(save_target, state) + if (response == Gtk.ResponseType.CANCEL) or (response == Gtk.ResponseType.DELETE_EVENT): + pass + + self._archiver_dialogue.hide() + + def archive_files(self, save_target, state): + paths = [shlex.quote(p) for p in state.selected_files] + + sItr, eItr = self._arc_command_buffer.get_bounds() + pre_command = self._arc_command_buffer.get_text(sItr, eItr, False) + pre_command = pre_command.replace("%o", shlex.quote(save_target)) + pre_command = pre_command.replace("%N", ' '.join(paths)) + command = f"{state.tab.terminal_app} -e {shlex.quote(pre_command)}" + current_dir = state.tab.get_current_directory() + + state.tab.execute(shlex.split(command), start_dir=shlex.quote(current_dir)) + + def set_arc_buffer_text(self, widget=None, eve=None): + sid = widget.get_active_id() + self.arc_command_buffer.set_text(self.arc_commands[int(sid)]) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py index 428edb4..22ab397 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py @@ -128,8 +128,6 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi self.copy_files() if action == "paste": self.paste_files() - if action == "archive": - self.show_archiver_dialogue() if action == "create": self.create_files() if action in ["save_session", "save_session_as", "load_session"]: diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py index 14524e0..112140b 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py @@ -64,32 +64,6 @@ class Controller_Data: self.trash_info_path = f"{GLib.get_user_data_dir()}/Trash/info" self.icon_theme = settings.get_icon_theme() - # In compress commands: - # %n: First selected filename/dir to archive - # %N: All selected filenames/dirs to archive, or (with %O) a single filename - # %o: Resulting single archive file - # %O: Resulting archive per source file/directory (use changes %N meaning) - # - # In extract commands: - # %x: Archive file to extract - # %g: Unique extraction target filename with optional subfolder - # %G: Unique extraction target filename, never with subfolder - # - # In list commands: - # %x: Archive to list - # - # Plus standard bash variables are accepted. - self.arc_commands = [ '$(which 7za || echo 7zr) a %o %N', - 'zip -r %o %N', - 'rar a -r %o %N', - 'tar -cvf %o %N', - 'tar -cvjf %o %N', - 'tar -cvzf %o %N', - 'tar -cvJf %o %N', - 'gzip -c %N > %O', - 'xz -cz %N > %O' - ] - self.notebooks = [self.window1, self.window2, self.window3, self.window4] self.selected_files = [] self.to_copy_files = [] diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/exception_hook_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/exception_hook_mixin.py index 660b9b6..4ec0b0a 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/exception_hook_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/exception_hook_mixin.py @@ -51,8 +51,3 @@ class ExceptionHookMixin: f.write(text) save_location_prompt.destroy() - - - def set_arc_buffer_text(self, widget=None, eve=None): - sid = widget.get_active_id() - self.arc_command_buffer.set_text(self.arc_commands[int(sid)]) 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 331c6ef..8377158 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 @@ -55,26 +55,6 @@ class ShowHideMixin: self.builder.get_object("about_page").hide() - def show_archiver_dialogue(self, widget=None, eve=None): - wid, tid = self.fm_controller.get_active_wid_and_tid() - tab = self.get_fm_window(wid).get_tab_by_id(tid) - archiver_dialogue = self.builder.get_object("archiver_dialogue") - archiver_dialogue.set_action(Gtk.FileChooserAction.SAVE) - archiver_dialogue.set_current_folder(tab.get_current_directory()) - archiver_dialogue.set_current_name("arc.7z") - - response = archiver_dialogue.run() - if response == Gtk.ResponseType.OK: - self.archive_files(archiver_dialogue) - if (response == Gtk.ResponseType.CANCEL) or (response == Gtk.ResponseType.DELETE_EVENT): - pass - - archiver_dialogue.hide() - - def hide_archiver_dialogue(self, widget=None, eve=None): - self.builder.get_object("archiver_dialogue").hide() - - def show_appchooser_menu(self, widget=None, eve=None): appchooser_menu = self.builder.get_object("appchooser_menu") appchooser_widget = self.builder.get_object("appchooser_widget") 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 e680801..04f8400 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 @@ -143,19 +143,6 @@ class WidgetFileActionMixin: command = f"{shlex.quote(path)}" if not in_terminal else f"{state.tab.terminal_app} -e {shlex.quote(path)}" state.tab.execute(shlex.split(command), start_dir=state.tab.get_current_directory()) - def archive_files(self, archiver_dialogue): - state = self.get_current_state() - 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() - pre_command = self.arc_command_buffer.get_text(sItr, eItr, False) - pre_command = pre_command.replace("%o", shlex.quote(save_target)) - pre_command = pre_command.replace("%N", ' '.join(paths)) - command = f"{state.tab.terminal_app} -e {shlex.quote(pre_command)}" - - state.tab.execute(shlex.split(command), start_dir=shlex.quote(state.tab.get_current_directory())) - def rename_files(self): rename_label = self.builder.get_object("file_to_rename_label") rename_input = self.builder.get_object("new_rename_fname") diff --git a/user_config/usr/share/solarfm/Main_Window.glade b/user_config/usr/share/solarfm/Main_Window.glade index 2343d0c..d102e67 100644 --- a/user_config/usr/share/solarfm/Main_Window.glade +++ b/user_config/usr/share/solarfm/Main_Window.glade @@ -452,170 +452,6 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe appchooser_select_btn
    - - $(which 7za || echo 7zr) a %o %N - - - False - True - center - dialog - center - True - True - - - False - vertical - 2 - - - False - end - - - gtk-cancel - True - True - True - True - - - True - True - 0 - - - - - gtk-ok - True - True - True - True - - - True - True - 1 - - - - - False - False - 0 - - - - - True - False - vertical - - - True - False - True - - - True - False - Compress Commands: - 0.20000000298023224 - - - - - - False - True - 0 - - - - - - - - True - False - Archive Format: - 1 - - - - - - False - True - 2 - - - - - True - False - 0 - 0 - - 7Zip (*.7z) - Zip (*.zip *.ZIP) - RAR (*.rar *.RAR) - Tar (*.tar) - Tar bzip2 (*.tar.bz2) - Tar Gzip (*.tar.gz *.tgz) - Tar xz (*.tar.xz *.txz) - Gzip (*.gz) - XZ (*.xz) - - - - - False - True - 3 - - - - - False - True - 0 - - - - - 72 - True - True - arc_command_buffer - - - True - True - 1 - - - - - False - True - 2 - - - - - - button21 - button22 - - - - True - False - gtk-save-as - True False @@ -912,7 +748,6 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe True True Archive... - archive_img True From 75da08d0819fada0ab51e5fece11036527c2dd36 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Thu, 20 Oct 2022 22:23:14 -0500 Subject: [PATCH 16/30] Added dirty start check, added images to plugin buttons, cleanup --- plugins/archiver/plugin.py | 4 ++ plugins/file_properties/plugin.py | 4 ++ plugins/movie_tv_info/plugin.py | 4 ++ plugins/searcher/mixins/file_search_mixin.py | 2 +- plugins/searcher/mixins/grep_search_mixin.py | 5 ++- plugins/searcher/plugin.py | 4 ++ plugins/searcher/utils/search.py | 14 +++---- plugins/trasher/trasher.glade | 2 +- plugins/vod_thumbnailer/plugin.py | 5 +++ .../solarfm-0.0.1/SolarFM/solarfm/__main__.py | 1 - .../SolarFM/solarfm/core/controller.py | 1 + .../tabs/icons/mixins/desktopiconmixin.py | 7 +--- .../shellfm/windows/tabs/utils/launcher.py | 2 +- .../shellfm/windows/tabs/utils/settings.py | 2 +- .../SolarFM/solarfm/utils/ipc_server.py | 4 +- .../SolarFM/solarfm/utils/settings.py | 42 +++++++++++++++++++ .../usr/share/solarfm/Main_Window.glade | 28 ------------- 17 files changed, 81 insertions(+), 50 deletions(-) diff --git a/plugins/archiver/plugin.py b/plugins/archiver/plugin.py index 1c02225..ccfd732 100644 --- a/plugins/archiver/plugin.py +++ b/plugins/archiver/plugin.py @@ -81,8 +81,12 @@ class Plugin(PluginBase): self._archiver_dialogue = self._builder.get_object("archiver_dialogue") self._arc_command_buffer = self._builder.get_object("arc_command_buffer") + icon = Gtk.Image(stock=Gtk.STOCK_FLOPPY) button = Gtk.Button(label=self.name) + + button.set_image(icon) button.connect("button-release-event", self.show_archiver_dialogue) + return button def run(self): diff --git a/plugins/file_properties/plugin.py b/plugins/file_properties/plugin.py index ec41032..c20bc1a 100644 --- a/plugins/file_properties/plugin.py +++ b/plugins/file_properties/plugin.py @@ -99,8 +99,12 @@ class Plugin(PluginBase): self._file_group = self._builder.get_object("file_group") def generate_reference_ui_element(self): + icon = Gtk.Image(stock=Gtk.STOCK_PROPERTIES ) button = Gtk.Button(label=self.name) + button.connect("button-release-event", self._show_properties_page) + button.set_image(icon) + return button diff --git a/plugins/movie_tv_info/plugin.py b/plugins/movie_tv_info/plugin.py index 3f8459e..2bae410 100644 --- a/plugins/movie_tv_info/plugin.py +++ b/plugins/movie_tv_info/plugin.py @@ -70,8 +70,12 @@ class Plugin(PluginBase): self._trailer_link = self._builder.get_object("trailer_link") def generate_reference_ui_element(self): + icon = Gtk.Image(stock=Gtk.STOCK_FIND) button = Gtk.Button(label=self.name) + button.connect("button-release-event", self._show_info_page) + button.set_image(icon) + return button @threaded diff --git a/plugins/searcher/mixins/file_search_mixin.py b/plugins/searcher/mixins/file_search_mixin.py index 749cf9d..6f17f08 100644 --- a/plugins/searcher/mixins/file_search_mixin.py +++ b/plugins/searcher/mixins/file_search_mixin.py @@ -49,8 +49,8 @@ class FileSearchMixin: GLib.idle_add(self.reset_file_list_box) # NOTE: If query create new process and do all new loop. - self.pause_fifo_update = False if query: + self.pause_fifo_update = False GLib.idle_add(self._exec_find_file_query, query) def _exec_find_file_query(self, widget=None, eve=None): diff --git a/plugins/searcher/mixins/grep_search_mixin.py b/plugins/searcher/mixins/grep_search_mixin.py index ad5c9da..a7590c1 100644 --- a/plugins/searcher/mixins/grep_search_mixin.py +++ b/plugins/searcher/mixins/grep_search_mixin.py @@ -1,5 +1,6 @@ # Python imports -import threading, subprocess, signal, json, shlex +import ctypes, threading, subprocess, signal, json, shlex +libgcc_s = ctypes.CDLL('libgcc_s.so.1') # Lib imports import gi @@ -49,8 +50,8 @@ class GrepSearchMixin: GLib.idle_add(self.reset_grep_box) # NOTE: If query create new process and do all new loop. - self.pause_fifo_update = False if query: + self.pause_fifo_update = False GLib.idle_add(self._exec_grep_query, query) def _exec_grep_query(self, widget=None, eve=None): diff --git a/plugins/searcher/plugin.py b/plugins/searcher/plugin.py index f941dc9..ab63097 100644 --- a/plugins/searcher/plugin.py +++ b/plugins/searcher/plugin.py @@ -80,8 +80,12 @@ class Plugin(IPCServer, FileSearchMixin, GrepSearchMixin, PluginBase): self.create_ipc_listener() def generate_reference_ui_element(self): + icon = Gtk.Image(stock=Gtk.STOCK_FIND) button = Gtk.Button(label=self.name) + button.connect("button-release-event", self._show_page) + button.set_image(icon) + return button diff --git a/plugins/searcher/utils/search.py b/plugins/searcher/utils/search.py index 692fbc2..be0b31c 100755 --- a/plugins/searcher/utils/search.py +++ b/plugins/searcher/utils/search.py @@ -16,9 +16,9 @@ from multiprocessing.connection import Client _ipc_address = f'/tmp/solarfm-search_grep-ipc.sock' _ipc_authkey = b'' + bytes(f'solarfm-search_grep-ipc', 'utf-8') -filter = (".mkv", ".mp4", ".webm", ".avi", ".mov", ".m4v", ".mpg", ".mpeg", ".wmv", ".flv") + \ - (".png", ".jpg", ".jpeg", ".gif", ".ico", ".tga", ".webp") + \ - (".psf", ".mp3", ".ogg", ".flac", ".m4a") +filter = (".cpp", ".css", ".c", ".go", ".html", ".htm", ".java", ".js", ".json", ".lua", ".md", ".py", ".rs", ".toml", ".xml", ".pom") + \ + (".txt", ".text", ".sh", ".cfg", ".conf", ".log") + # NOTE: Threads WILL NOT die with parent's destruction. def threaded(fn): @@ -59,7 +59,7 @@ def _search_for_string(file, query): grep_result_set = {} padding = 15 - with open(file, 'r') as fp: + with open(file, 'rb') as fp: # NOTE: I know there's an issue if there's a very large file with content # all on one line will lower and dupe it. And, yes, it will only # return one instance from the file. @@ -80,7 +80,7 @@ def _search_for_string(file, query): else: line = raw - b64_line = base64.urlsafe_b64encode(line.encode('utf-8')).decode('utf-8') + b64_line = base64.urlsafe_b64encode(line).decode('utf-8') if f"{b64_file}" in grep_result_set.keys(): grep_result_set[f"{b64_file}"][f"{i+1}"] = b64_line else: @@ -109,7 +109,7 @@ def grep_search(path, query): if os.path.isdir(target): grep_search(target, query) else: - if not target.lower().endswith(filter): + if target.lower().endswith(filter): size = os.path.getsize(target) if not size > 5000: _search_for_string(target, query) @@ -125,7 +125,7 @@ def search(args): file_search(args.dir, args.query.lower()) if args.type == "grep_search": - grep_search(args.dir, args.query.lower()) + grep_search(args.dir, args.query.lower().encode("utf-8")) if __name__ == "__main__": diff --git a/plugins/trasher/trasher.glade b/plugins/trasher/trasher.glade index 54837b9..2867476 100644 --- a/plugins/trasher/trasher.glade +++ b/plugins/trasher/trasher.glade @@ -47,6 +47,7 @@ True True Empty Trash... + 20
    @@ -63,7 +64,6 @@ True True Move to Trash... - 20 trash_img True diff --git a/plugins/vod_thumbnailer/plugin.py b/plugins/vod_thumbnailer/plugin.py index e730797..694972f 100644 --- a/plugins/vod_thumbnailer/plugin.py +++ b/plugins/vod_thumbnailer/plugin.py @@ -68,8 +68,13 @@ class Plugin(PluginBase): self._file_hash = self._builder.get_object("file_hash") def generate_reference_ui_element(self): + pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(f"{self.path}/../../icons/video.png", 16, 16, True) + icon = Gtk.Image.new_from_pixbuf(pixbuf) button = Gtk.Button(label=self.name) + + button.set_image(icon) button.connect("button-release-event", self._show_thumbnailer_page) + return button diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py index 9bfffcb..daee641 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py @@ -1,6 +1,5 @@ #!/usr/bin/python3 - # Python imports import argparse, faulthandler, traceback from setproctitle import setproctitle diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py index 22ab397..2174dda 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py @@ -54,6 +54,7 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi if not settings.is_trace_debug(): self.fm_controller.save_state() + settings.clear_pid() time.sleep(event_sleep_time) Gtk.main_quit() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/desktopiconmixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/desktopiconmixin.py index 3f69ea3..3ee2a50 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/desktopiconmixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/desktopiconmixin.py @@ -50,13 +50,8 @@ class DesktopIconMixin: return None def traverse_icons_folder(self, path, icon): - alt_icon_path = "" - for (dirpath, dirnames, filenames) in os.walk(path): for file in filenames: appNM = "application-x-" + icon if icon in file or appNM in file: - alt_icon_path = dirpath + "/" + file - break - - return alt_icon_path + return f"{dirpath}/{file}" diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/launcher.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/launcher.py index eab19cd..87f7da6 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/launcher.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/launcher.py @@ -1,5 +1,5 @@ # System import -import os, threading, subprocess, shlex +import os, threading, subprocess # Lib imports diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/settings.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/settings.py index 928bf6a..b1e5dec 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/settings.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/utils/settings.py @@ -25,7 +25,7 @@ class Settings: FFMPG_THUMBNLR = f"{CONFIG_PATH}/ffmpegthumbnailer" # Thumbnail generator binary REMUX_FOLDER = f"{USER_HOME}/.remuxs" # Remuxed files folder - ICON_DIRS = ["/usr/share/pixmaps", "/usr/share/icons", f"{USER_HOME}/.icons" ,] + ICON_DIRS = ["/usr/share/icons", f"{USER_HOME}/.icons" "/usr/share/pixmaps"] BASE_THUMBS_PTH = f"{USER_HOME}/.thumbnails" # Used for thumbnail generation ABS_THUMBS_PTH = f"{BASE_THUMBS_PTH}/normal" # Used for thumbnail generation STEAM_ICONS_PTH = f"{BASE_THUMBS_PTH}/steam_icons" diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py index 5aa0ebd..f8c4fd9 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py @@ -38,8 +38,8 @@ class IPCServer: @daemon_threaded def create_ipc_listener(self) -> None: if self._conn_type == "socket": - if os.path.exists(self._ipc_address): - return + if os.path.exists(self._ipc_address) and settings.is_dirty_start(): + os.unlink(self._ipc_address) listener = Listener(address=self._ipc_address, family="AF_UNIX", authkey=self._ipc_authkey) elif "unsecured" not in self._conn_type: 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 0967a25..392b672 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 @@ -31,6 +31,7 @@ class Settings: self._KEY_BINDINGS = f"{self._CONFIG_PATH}/key-bindings.json" self._DEFAULT_ICONS = f"{self._CONFIG_PATH}/icons" self._WINDOW_ICON = f"{self._DEFAULT_ICONS}/{app_name.lower()}.png" + self._PID_FILE = f"{self._CONFIG_PATH}/solarfm.pid" self._ICON_THEME = Gtk.IconTheme.get_default() if not os.path.exists(self._CONFIG_PATH): @@ -65,6 +66,44 @@ class Settings: self._trace_debug = False self._debug = False + self._dirty_start = False + + self._check_for_dirty_state() + + + def _check_for_dirty_state(self): + if not os.path.exists(self._PID_FILE): + self._write_new_pid() + else: + with open(self._PID_FILE, "r") as _pid: + pid = _pid.readline().strip() + if pid not in ("", None): + self._check_alive_status(int(pid)) + else: + self._write_new_pid() + + """ Check For the existence of a unix pid. """ + def _check_alive_status(self, pid): + print(f"PID Found: {pid}") + try: + os.kill(pid, 0) + except OSError: + print("SolarFM Is starting dirty...") + self._dirty_start = True + self._write_new_pid() + + print("PID is alive... Let downstream errors handle app closure.") + + def _write_new_pid(self): + pid = os.getpid() + self._write_pid(pid) + + def _clean_pid(self): + os.unlink(self._PID_FILE) + + def _write_pid(self, pid): + with open(self._PID_FILE, "w") as _pid: + _pid.write(f"{pid}") def create_window(self) -> None: @@ -104,6 +143,7 @@ class Settings: return monitors + def get_main_window(self) -> Gtk.ApplicationWindow: return self._main_window def get_builder(self) -> Gtk.Builder: return self._builder def get_logger(self) -> Logger: return self._logger @@ -117,6 +157,8 @@ class Settings: def is_trace_debug(self) -> str: return self._trace_debug def is_debug(self) -> str: return self._debug + def is_dirty_start(self) -> bool: return self._dirty_start + def clear_pid(self): self._clean_pid() def set_trace_debug(self, trace_debug): diff --git a/user_config/usr/share/solarfm/Main_Window.glade b/user_config/usr/share/solarfm/Main_Window.glade index d102e67..fa62070 100644 --- a/user_config/usr/share/solarfm/Main_Window.glade +++ b/user_config/usr/share/solarfm/Main_Window.glade @@ -740,23 +740,6 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe 4
    - - - Archive - archive - True - True - True - Archive... - True - - - - False - True - 5 - -
    @@ -1079,17 +1062,6 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
    - - - gtk-delete - delete - True - False - True - True - - -
    From efa42a301c278f1d3db3218707ca43413baf52af Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sat, 22 Oct 2022 23:53:33 -0500 Subject: [PATCH 17/30] small refactoring --- .../solarfm-0.0.1/SolarFM/solarfm/__main__.py | 7 ++++ .../solarfm-0.0.1/SolarFM/solarfm/app.py | 41 ++++++++----------- .../SolarFM/solarfm/core/controller.py | 24 +++-------- .../SolarFM/solarfm/core/controller_data.py | 4 ++ .../SolarFM/solarfm/utils/ipc_server.py | 11 +++-- .../SolarFM/solarfm/utils/settings.py | 11 +++-- 6 files changed, 45 insertions(+), 53 deletions(-) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py index daee641..b61b260 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py @@ -35,6 +35,13 @@ if __name__ == "__main__": # Read arguments (If any...) args, unknownargs = parser.parse_known_args() + if args.debug == "true": + settings.set_debug(True) + + if args.trace_debug == "true": + settings.set_trace_debug(True) + + settings.do_dirty_start_check() Application(args, unknownargs) Gtk.main() except Exception as e: 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 7df637a..82436d4 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py @@ -1,57 +1,50 @@ # Python imports -import os, inspect, time +import os, inspect # Lib imports # Application imports from utils.ipc_server import IPCServer -from utils.settings import Settings from core.controller import Controller class AppLaunchException(Exception): ... -class ControllerStartExceptio(Exception): +class ControllerStartException(Exception): ... class Application(IPCServer): - """ Create Settings and Controller classes. Bind signal to Builder. Inherit from Builtins to bind global methods and classes. """ + """ Inherit from IPCServer; create Controller classe; bind any signal(s) to Builder. """ def __init__(self, args, unknownargs): super(Application, self).__init__() - if args.debug == "true": - settings.set_debug(True) + self.args, self.unknownargs = args, unknownargs - if args.trace_debug == "true": - settings.set_trace_debug(True) - - # NOTE: Instance found, sending files to it... if not settings.is_trace_debug(): - self.create_ipc_listener() - time.sleep(0.05) + try: + self.create_ipc_listener() + except Exception: + ... if not self.is_ipc_alive: - if unknownargs: - for arg in unknownargs: - if os.path.isdir(arg): - message = f"FILE|{arg}" - self.send_ipc_message(message) + for arg in unknownargs + [args.new_tab,]: + if os.path.isdir(arg): + message = f"FILE|{arg}" + self.send_ipc_message(message) - if args.new_tab and os.path.isdir(args.new_tab): - message = f"FILE|{args.new_tab}" - self.send_ipc_message(message) - - raise AppLaunchException(f"IPC Server Exists: Will send path(s) to it and close...\nNote: If no fm exists, remove /tmp/{app_name}-ipc.sock") + raise AppLaunchException(f"{app_name} IPC Server Exists: Will send path(s) to it and close...") settings.create_window() + self._load_controller_and_builder() - controller = Controller(args, unknownargs) + def _load_controller_and_builder(self): + controller = Controller(self.args, self.unknownargs) if not controller: - raise ControllerStartExceptio("Controller exited and doesn't exist...") + raise ControllerStartException("Controller exited and doesn't exist...") # Gets the methods from the classes and sets to handler. # Then, builder connects to any signals it needs. diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py index 2174dda..abb3b2d 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py @@ -19,27 +19,14 @@ from .controller_data import Controller_Data class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMixin, Controller_Data): """ Controller coordinates the mixins and is somewhat the root hub of it all. """ def __init__(self, args, unknownargs): + self._subscribe_to_events() self.setup_controller_data() - self.window.show() - self.generate_windows(self.fm_controller_data) self.plugins.launch_plugins() - if settings.is_debug(): - self.window.set_interactive_debugging(True) - - # NOTE: Open files if passed in from cli and not trace debugging... - if not settings.is_trace_debug(): - self._subscribe_to_events() - - if unknownargs: - for arg in unknownargs: - if os.path.isdir(arg): - message = f"FILE|{arg}" - event_system.emit("post_file_to_ipc", message) - - if args.new_tab and os.path.isdir(args.new_tab): - message = f"FILE|{args.new_tab}" + for arg in unknownargs + [args.new_tab,]: + if os.path.isdir(arg): + message = f"FILE|{arg}" event_system.emit("post_file_to_ipc", message) @@ -88,7 +75,7 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi session_json = self.fm_controller.get_state_from_file(path) self.load_session(session_json) if (response == Gtk.ResponseType.CANCEL) or (response == Gtk.ResponseType.DELETE_EVENT): - pass + ... save_load_dialog.hide() @@ -137,7 +124,6 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi - @endpoint_registry.register(rule="go_home") def go_home(self, widget=None, eve=None): self.builder.get_object("go_home").released() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py index 112140b..904a8ad 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py @@ -96,6 +96,10 @@ class Controller_Data: self.window.connect("delete-event", self.tear_down) GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.tear_down) + self.window.show() + if settings.is_debug(): + self.window.set_interactive_debugging(True) + def get_current_state(self) -> State: ''' diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py index f8c4fd9..f1dbd4f 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py @@ -31,11 +31,10 @@ class IPCServer: self._subscribe_to_events() + def _subscribe_to_events(self): event_system.subscribe("post_file_to_ipc", self.send_ipc_message) - - @daemon_threaded def create_ipc_listener(self) -> None: if self._conn_type == "socket": if os.path.exists(self._ipc_address) and settings.is_dirty_start(): @@ -49,14 +48,18 @@ class IPCServer: self.is_ipc_alive = True + self._run_ipc_loop(listener) + + @daemon_threaded + def _run_ipc_loop(self, listener) -> None: while True: conn = listener.accept() start_time = time.perf_counter() - self.handle_message(conn, start_time) + self._handle_ipc_message(conn, start_time) listener.close() - def handle_message(self, conn, start_time) -> None: + def _handle_ipc_message(self, conn, start_time) -> None: while True: msg = conn.recv() if settings.is_debug(): 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 392b672..58eb0f4 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 @@ -31,7 +31,7 @@ class Settings: self._KEY_BINDINGS = f"{self._CONFIG_PATH}/key-bindings.json" self._DEFAULT_ICONS = f"{self._CONFIG_PATH}/icons" self._WINDOW_ICON = f"{self._DEFAULT_ICONS}/{app_name.lower()}.png" - self._PID_FILE = f"{self._CONFIG_PATH}/solarfm.pid" + self._PID_FILE = f"{self._CONFIG_PATH}/{app_name.lower()}.pid" self._ICON_THEME = Gtk.IconTheme.get_default() if not os.path.exists(self._CONFIG_PATH): @@ -68,10 +68,8 @@ class Settings: self._debug = False self._dirty_start = False - self._check_for_dirty_state() - - def _check_for_dirty_state(self): + def do_dirty_start_check(self): if not os.path.exists(self._PID_FILE): self._write_new_pid() else: @@ -88,11 +86,12 @@ class Settings: try: os.kill(pid, 0) except OSError: - print("SolarFM Is starting dirty...") + print(f"{app_name} is starting dirty...") self._dirty_start = True self._write_new_pid() + return - print("PID is alive... Let downstream errors handle app closure.") + print("PID is alive... Let downstream errors (sans debug args) handle app closure propigation.") def _write_new_pid(self): pid = os.getpid() From 9697e8ca169c6e945687a40c86501222569d09e2 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Tue, 25 Oct 2022 23:27:21 -0500 Subject: [PATCH 18/30] Moving to use ContextMenu --- plugins/archiver/manifest.json | 2 +- plugins/archiver/plugin.py | 11 +- plugins/file_properties/plugin.py | 12 +- plugins/movie_tv_info/manifest.json | 2 +- plugins/movie_tv_info/plugin.py | 12 +- plugins/searcher/plugin.py | 14 +- plugins/trasher/plugin.py | 53 ++-- plugins/trasher/trasher.glade | 126 -------- plugins/vod_thumbnailer/manifest.json | 2 +- plugins/vod_thumbnailer/plugin.py | 11 +- .../SolarFM/solarfm/__builtins__.py | 1 - .../solarfm-0.0.1/SolarFM/solarfm/__main__.py | 2 +- .../SolarFM/solarfm/core/controller_data.py | 85 ++++++ .../solarfm/core/mixins/show_hide_mixin.py | 4 +- .../SolarFM/solarfm/plugins/manifest.py | 3 +- .../usr/share/solarfm/Main_Window.glade | 280 ------------------ 16 files changed, 154 insertions(+), 466 deletions(-) delete mode 100644 plugins/trasher/trasher.glade diff --git a/plugins/archiver/manifest.json b/plugins/archiver/manifest.json index 761a2eb..5d9fc1b 100644 --- a/plugins/archiver/manifest.json +++ b/plugins/archiver/manifest.json @@ -5,7 +5,7 @@ "version": "0.0.1", "support": "", "requests": { - "ui_target": "context_menu", + "ui_target": "context_menu_plugins", "pass_fm_events": "true" } } diff --git a/plugins/archiver/plugin.py b/plugins/archiver/plugin.py index ccfd732..5426094 100644 --- a/plugins/archiver/plugin.py +++ b/plugins/archiver/plugin.py @@ -81,13 +81,12 @@ class Plugin(PluginBase): self._archiver_dialogue = self._builder.get_object("archiver_dialogue") self._arc_command_buffer = self._builder.get_object("arc_command_buffer") - icon = Gtk.Image(stock=Gtk.STOCK_FLOPPY) - button = Gtk.Button(label=self.name) + item = Gtk.ImageMenuItem(self.name) + item.set_image( Gtk.Image(stock=Gtk.STOCK_FLOPPY) ) + item.connect("activate", self.show_archiver_dialogue) + item.set_always_show_image(True) + return item - button.set_image(icon) - button.connect("button-release-event", self.show_archiver_dialogue) - - return button def run(self): ... diff --git a/plugins/file_properties/plugin.py b/plugins/file_properties/plugin.py index c20bc1a..e28d1e3 100644 --- a/plugins/file_properties/plugin.py +++ b/plugins/file_properties/plugin.py @@ -99,13 +99,11 @@ class Plugin(PluginBase): self._file_group = self._builder.get_object("file_group") def generate_reference_ui_element(self): - icon = Gtk.Image(stock=Gtk.STOCK_PROPERTIES ) - button = Gtk.Button(label=self.name) - - button.connect("button-release-event", self._show_properties_page) - button.set_image(icon) - - return button + item = Gtk.ImageMenuItem(self.name) + item.set_image( Gtk.Image(stock=Gtk.STOCK_PROPERTIES) ) + item.connect("activate", self._show_properties_page) + item.set_always_show_image(True) + return item @threaded diff --git a/plugins/movie_tv_info/manifest.json b/plugins/movie_tv_info/manifest.json index 9290b70..2bff10f 100644 --- a/plugins/movie_tv_info/manifest.json +++ b/plugins/movie_tv_info/manifest.json @@ -5,7 +5,7 @@ "version": "0.0.1", "support": "", "requests": { - "ui_target": "context_menu", + "ui_target": "context_menu_plugins", "pass_fm_events": "true" } } diff --git a/plugins/movie_tv_info/plugin.py b/plugins/movie_tv_info/plugin.py index 2bae410..f16f282 100644 --- a/plugins/movie_tv_info/plugin.py +++ b/plugins/movie_tv_info/plugin.py @@ -70,13 +70,11 @@ class Plugin(PluginBase): self._trailer_link = self._builder.get_object("trailer_link") def generate_reference_ui_element(self): - icon = Gtk.Image(stock=Gtk.STOCK_FIND) - button = Gtk.Button(label=self.name) - - button.connect("button-release-event", self._show_info_page) - button.set_image(icon) - - return button + item = Gtk.ImageMenuItem(self.name) + item.set_image( Gtk.Image(stock=Gtk.STOCK_FIND) ) + item.connect("activate", self._show_info_page) + item.set_always_show_image(True) + return item @threaded def _show_info_page(self, widget=None, eve=None): diff --git a/plugins/searcher/plugin.py b/plugins/searcher/plugin.py index ab63097..17eaa63 100644 --- a/plugins/searcher/plugin.py +++ b/plugins/searcher/plugin.py @@ -80,13 +80,11 @@ class Plugin(IPCServer, FileSearchMixin, GrepSearchMixin, PluginBase): self.create_ipc_listener() def generate_reference_ui_element(self): - icon = Gtk.Image(stock=Gtk.STOCK_FIND) - button = Gtk.Button(label=self.name) - - button.connect("button-release-event", self._show_page) - button.set_image(icon) - - return button + item = Gtk.ImageMenuItem(self.name) + item.set_image( Gtk.Image(stock=Gtk.STOCK_FIND) ) + item.connect("activate", self._show_page) + item.set_always_show_image(True) + return item def _show_page(self, widget=None, eve=None): @@ -113,6 +111,7 @@ class Plugin(IPCServer, FileSearchMixin, GrepSearchMixin, PluginBase): self.grep_list_parent.add(self._grep_list) self.grep_list_parent.show_all() + time.sleep(0.05) Gtk.main_iteration() def reset_file_list_box(self) -> None: @@ -128,4 +127,5 @@ class Plugin(IPCServer, FileSearchMixin, GrepSearchMixin, PluginBase): self.file_list_parent.add(self._file_list) self.file_list_parent.show_all() + time.sleep(0.05) Gtk.main_iteration() diff --git a/plugins/trasher/plugin.py b/plugins/trasher/plugin.py index 78cc759..69618d9 100644 --- a/plugins/trasher/plugin.py +++ b/plugins/trasher/plugin.py @@ -47,34 +47,49 @@ class Plugin(PluginBase): self._event_system.subscribe("trash_files", self.trash_files) def generate_reference_ui_element(self): - self._builder = Gtk.Builder() - self._builder.add_from_file(self._GLADE_FILE) + trash_a = Gtk.MenuItem("Trash Actions") + trash_menu = Gtk.Menu() - classes = [self] - handlers = {} - for c in classes: - methods = None - try: - methods = inspect.getmembers(c, predicate=inspect.ismethod) - handlers.update(methods) - except Exception as e: - print(repr(e)) + self.restore = Gtk.MenuItem("Restore From Trash") + self.restore.connect("activate", self.restore_trash_files) - self._builder.connect_signals(handlers) + self.empty = Gtk.MenuItem("Empty Trash") + self.empty.connect("activate", self.empty_trash) - trasher = self._builder.get_object("trasher") - trasher.show_all() + trash = Gtk.ImageMenuItem("Trash") + trash.set_image( Gtk.Image.new_from_icon_name("user-trash", 16) ) + trash.connect("activate", self.trash_files) + trash.set_always_show_image(True) - return trasher + go_to = Gtk.ImageMenuItem("Go To Trash") + go_to.set_image( Gtk.Image.new_from_icon_name("user-trash", 16) ) + go_to.connect("activate", self.go_to_trash) + go_to.set_always_show_image(True) + + delete = Gtk.ImageMenuItem("Delete") + delete.set_image( Gtk.Image(stock=Gtk.STOCK_DELETE) ) + delete.connect("activate", self.delete_files) + delete.set_always_show_image(True) + + trash_a.set_submenu(trash_menu) + trash_a.show_all() + self._appen_menu_items(trash_menu, [self.restore, self.empty, trash, go_to, delete]) + + return trash_a + + + def _appen_menu_items(self, menu, items): + for item in items: + menu.append(item) def _show_trash_buttons(self): - self._builder.get_object("restore_from_trash").show() - self._builder.get_object("empty_trash").show() + self.restore.show() + self.empty.show() def _hide_trash_buttons(self): - self._builder.get_object("restore_from_trash").hide() - self._builder.get_object("empty_trash").hide() + self.restore.hide() + self.empty.hide() def delete_files(self, widget = None, eve = None): self._event_system.emit("do_hide_context_menu") diff --git a/plugins/trasher/trasher.glade b/plugins/trasher/trasher.glade deleted file mode 100644 index 2867476..0000000 --- a/plugins/trasher/trasher.glade +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - True - False - user-trash - - - True - False - user-trash - - - True - True - 5 - 10 - True - - - True - False - vertical - - - Restore From Trash - restore_from_trash - True - True - True - Restore From Trash... - - - - False - True - 0 - - - - - Empty Trash - empty_trash - True - True - True - Empty Trash... - 20 - - - - False - True - 1 - - - - - Trash - trash - True - True - True - Move to Trash... - trash_img - True - - - - False - True - 2 - - - - - Go To Trash - go_to_trash - True - True - True - Go To Trash... - trash_img2 - True - - - - False - True - 3 - - - - - gtk-delete - delete - True - True - True - Delete... - 20 - True - True - - - - False - True - 4 - - - - - - - True - False - False - Trash - center - - - - diff --git a/plugins/vod_thumbnailer/manifest.json b/plugins/vod_thumbnailer/manifest.json index 154bd40..21523f0 100644 --- a/plugins/vod_thumbnailer/manifest.json +++ b/plugins/vod_thumbnailer/manifest.json @@ -5,7 +5,7 @@ "version": "0.0.1", "support": "", "requests": { - "ui_target": "context_menu", + "ui_target": "context_menu_plugins", "pass_fm_events": "true" } } diff --git a/plugins/vod_thumbnailer/plugin.py b/plugins/vod_thumbnailer/plugin.py index 694972f..81a97a6 100644 --- a/plugins/vod_thumbnailer/plugin.py +++ b/plugins/vod_thumbnailer/plugin.py @@ -70,12 +70,11 @@ class Plugin(PluginBase): def generate_reference_ui_element(self): pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(f"{self.path}/../../icons/video.png", 16, 16, True) icon = Gtk.Image.new_from_pixbuf(pixbuf) - button = Gtk.Button(label=self.name) - - button.set_image(icon) - button.connect("button-release-event", self._show_thumbnailer_page) - - return button + item = Gtk.ImageMenuItem("Delete") + item.set_image( icon ) + item.connect("activate", self._show_thumbnailer_page) + item.set_always_show_image(True) + return item @threaded diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py index 9ed03c8..a96c67f 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py @@ -24,7 +24,6 @@ def daemon_threaded_wrapper(fn): - # NOTE: Just reminding myself we can add to builtins two different ways... # __builtins__.update({"event_system": Builtins()}) builtins.app_name = "SolarFM" diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py index b61b260..e1fa0e9 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py @@ -22,7 +22,7 @@ if __name__ == "__main__": """ Set process title, get arguments, and create GTK main thread. """ try: - setproctitle('SolarFM') + setproctitle(f"{app_name}") faulthandler.enable() # For better debug info parser = argparse.ArgumentParser() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py index 904a8ad..dabae41 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py @@ -4,6 +4,8 @@ from dataclasses import dataclass # Lib imports import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk from gi.repository import GLib # Application imports @@ -100,6 +102,89 @@ class Controller_Data: if settings.is_debug(): self.window.set_interactive_debugging(True) + self.build_context_menu() + + + def build_context_menu(self) -> None: + main_menu = Gtk.Menu() + open_menu = Gtk.Menu() + filea_menu = Gtk.Menu() + plugins_menu = Gtk.Menu() + open_a = Gtk.MenuItem("Open Actions") + file_a = Gtk.MenuItem("File Actions") + plugins = Gtk.MenuItem("Plugins") + + self._appen_menu_items(main_menu, [open_a, file_a, plugins]) + + open = Gtk.ImageMenuItem("Open") + open.set_name("open") + open.set_image( Gtk.Image(stock=Gtk.STOCK_OPEN) ) + open.set_always_show_image(True) + open.connect("activate", self.do_action_from_menu_controls) + + open_with = Gtk.ImageMenuItem("Open With") + open_with.set_name("open_with") + open_with.set_image( Gtk.Image(stock=Gtk.STOCK_OPEN) ) + open_with.set_always_show_image(True) + open_with.connect("activate", self.do_action_from_menu_controls) + + execute = Gtk.ImageMenuItem("Execute") + execute.set_name("execute") + execute.set_image( Gtk.Image(stock=Gtk.STOCK_EXECUTE) ) + execute.set_always_show_image(True) + execute.connect("activate", self.do_action_from_menu_controls) + + execute_term = Gtk.ImageMenuItem("Execute in Terminal") + execute_term.set_name("execute_in_terminal") + execute_term.set_image( Gtk.Image(stock=Gtk.STOCK_EXECUTE) ) + execute_term.set_always_show_image(True) + execute_term.connect("activate", self.do_action_from_menu_controls) + + self._appen_menu_items(open_menu, [open, open_with, execute, execute_term]) + + new = Gtk.ImageMenuItem("New") + new.set_name("create") + new.set_image( Gtk.Image(stock=Gtk.STOCK_ADD) ) + new.set_always_show_image(True) + new.connect("activate", self.do_action_from_menu_controls) + + rename = Gtk.ImageMenuItem("Rename") + rename.set_name("rename") + rename.set_image( Gtk.Image(stock=Gtk.STOCK_EDIT) ) + rename.set_always_show_image(True) + rename.connect("activate", self.do_action_from_menu_controls) + + cut = Gtk.ImageMenuItem("Cut") + cut.set_name("cut") + cut.set_image( Gtk.Image(stock=Gtk.STOCK_CUT) ) + cut.set_always_show_image(True) + cut.connect("activate", self.do_action_from_menu_controls) + + copy = Gtk.ImageMenuItem("Copy") + copy.set_name("copy") + copy.set_image( Gtk.Image(stock=Gtk.STOCK_COPY) ) + copy.set_always_show_image(True) + copy.connect("activate", self.do_action_from_menu_controls) + + paste = Gtk.ImageMenuItem("Paste") + paste.set_name("paste") + paste.set_image( Gtk.Image(stock=Gtk.STOCK_PASTE) ) + paste.set_always_show_image(True) + paste.connect("activate", self.do_action_from_menu_controls) + + self._appen_menu_items(filea_menu, [new, rename, cut, copy, paste]) + open_a.set_submenu(open_menu) + file_a.set_submenu(filea_menu) + plugins.set_submenu(plugins_menu) + + main_menu.attach_to_widget(self.window, None) + main_menu.show_all() + self.builder.expose_object("context_menu", main_menu) + self.builder.expose_object("context_menu_plugins", plugins_menu) + + def _appen_menu_items(self, menu, items): + for item in items: + menu.append(item) def get_current_state(self) -> State: ''' 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 8377158..239b155 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 @@ -83,10 +83,10 @@ class ShowHideMixin: self.builder.get_object("plugin_controls").hide() def show_context_menu(self, widget=None, eve=None): - self.builder.get_object("context_menu_popup").run() + self.builder.get_object("context_menu").popup_at_pointer(None) def hide_context_menu(self, widget=None, eve=None): - self.builder.get_object("context_menu_popup").hide() + self.builder.get_object("context_menu").popdown() def show_new_file_menu(self, widget=None, eve=None): if widget: diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/manifest.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/manifest.py index e53c0c5..3d23ecf 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/manifest.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/manifest.py @@ -56,7 +56,8 @@ class ManifestProcessor: if requests["ui_target"] in [ "none", "other", "main_Window", "main_menu_bar", "main_menu_bttn_box_bar", "path_menu_bar", "plugin_control_list", - "context_menu", "window_1", "window_2", "window_3", "window_4" + "context_menu", "context_menu_plugins", "window_1", + "window_2", "window_3", "window_4" ]: if requests["ui_target"] == "other": if "ui_target_id" in keys: diff --git a/user_config/usr/share/solarfm/Main_Window.glade b/user_config/usr/share/solarfm/Main_Window.glade index fa62070..0176307 100644 --- a/user_config/usr/share/solarfm/Main_Window.glade +++ b/user_config/usr/share/solarfm/Main_Window.glade @@ -457,11 +457,6 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False gtk-new - - True - False - gtk-execute - True False @@ -488,287 +483,12 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe gtk-execute - - True - False - gtk-open - True False gtk-edit 3 - - True - False - gtk-edit - - - False - False - mouse - splashscreen - True - False - False - static - - - - False - vertical - 2 - - - False - end - - - - - - - - - False - False - 0 - - - - - True - False - vertical - - - True - True - 5 - 5 - True - True - - - True - False - vertical - - - gtk-open - open - True - True - True - Open... - True - - - - False - True - 0 - - - - - Open With - open_with - True - True - True - open_with_img - True - - - - False - True - 1 - - - - - gtk-execute - execute - True - True - True - True - - - - False - True - 2 - - - - - Execute in Terminal - execute_in_terminal - True - True - True - exec_in_term_img - - - - False - True - 3 - - - - - - - True - False - False - Open - center - - - - - False - True - 4 - - - - - True - True - 5 - 5 - True - True - - - True - False - vertical - - - gtk-new - create - True - True - True - New File/Folder... - True - - - - False - True - 0 - - - - - Rename - rename - True - True - True - Rename... - rename_img2 - True - - - - False - True - 1 - - - - - gtk-cut - cut - True - True - True - Cut... - True - True - - - - False - True - 2 - - - - - gtk-copy - copy - True - True - True - Copy... - True - True - - - - False - True - 3 - - - - - gtk-paste - paste - True - True - True - Paste... - True - True - - - - False - True - 4 - - - - - - - True - False - False - File Actions - center - end - - - - - False - True - 5 - - - - - False - True - 1 - - - - - False dialog From d0612a2b3757e3fa545fb651c89b6b6b82d0e213 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Wed, 26 Oct 2022 19:47:54 -0500 Subject: [PATCH 19/30] Building context menu from config file --- plugins/vod_thumbnailer/plugin.py | 3 +- .../SolarFM/solarfm/core/context_menu.py | 66 +++++++++++++++ .../SolarFM/solarfm/core/controller.py | 13 ++- .../SolarFM/solarfm/core/controller_data.py | 83 ------------------- .../SolarFM/solarfm/utils/settings.py | 7 ++ .../usr/share/solarfm/contexct_menu.json | 16 ++++ 6 files changed, 102 insertions(+), 86 deletions(-) create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/core/context_menu.py create mode 100644 user_config/usr/share/solarfm/contexct_menu.json diff --git a/plugins/vod_thumbnailer/plugin.py b/plugins/vod_thumbnailer/plugin.py index 81a97a6..45e08e2 100644 --- a/plugins/vod_thumbnailer/plugin.py +++ b/plugins/vod_thumbnailer/plugin.py @@ -70,7 +70,8 @@ class Plugin(PluginBase): def generate_reference_ui_element(self): pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_scale(f"{self.path}/../../icons/video.png", 16, 16, True) icon = Gtk.Image.new_from_pixbuf(pixbuf) - item = Gtk.ImageMenuItem("Delete") + item = Gtk.ImageMenuItem(self.name) + item.set_image( icon ) item.connect("activate", self._show_thumbnailer_page) item.set_always_show_image(True) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/context_menu.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/context_menu.py new file mode 100644 index 0000000..f6f6cec --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/context_menu.py @@ -0,0 +1,66 @@ +# Python imports + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk +from gi.repository import GLib + +# Application imports + + +class ContextMenu(Gtk.Menu): + def __init__(self): + super(ContextMenu, self).__init__() + self._builder = settings.get_builder() + self._context_menu_data = settings.get_context_menu_data() + self._window = settings.get_main_window() + + + def make_submenu(self, name, data, keys): + menu = Gtk.Menu() + menu_item = Gtk.MenuItem(name) + + for key in keys: + if isinstance(data, dict): + entry = self.make_menu_item(key, data[key]) + elif isinstance(data, list): + entry = self.make_menu_item(key, data) + else: + continue + + menu.append(entry) + + menu_item.set_submenu(menu) + return menu_item + + def make_menu_item(self, name, data) -> Gtk.MenuItem: + if isinstance(data, dict): + return self.make_submenu(name, data, data.keys()) + elif isinstance(data, list): + entry = Gtk.ImageMenuItem(name) + icon = getattr(Gtk, f"{data[0]}") + entry.set_image( Gtk.Image(stock=icon) ) + entry.set_always_show_image(True) + entry.connect("activate", self._emit, (data[1])) + return entry + + def build_context_menu(self) -> None: + data = self._context_menu_data + dkeys = data.keys() + plugins_entry = None + + for dkey in dkeys: + entry = self.make_menu_item(dkey, data[dkey]) + self.append(entry) + if dkey == "Plugins": + plugins_entry = entry + + self.attach_to_widget(self._window, None) + self.show_all() + self._builder.expose_object("context_menu", self) + if plugins_entry: + self._builder.expose_object("context_menu_plugins", plugins_entry.get_submenu()) + + def _emit(self, menu_item, type): + event_system.emit("do_action_from_menu_controls", type) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py index abb3b2d..c301a38 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py @@ -12,7 +12,7 @@ from .mixins.ui_mixin import UIMixin from .signals.ipc_signals_mixin import IPCSignalsMixin from .signals.keyboard_signals_mixin import KeyboardSignalsMixin from .controller_data import Controller_Data - +from .context_menu import ContextMenu @@ -22,6 +22,10 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi self._subscribe_to_events() self.setup_controller_data() self.generate_windows(self.fm_controller_data) + + cm = ContextMenu() + cm.build_context_menu() + self.plugins.launch_plugins() for arg in unknownargs + [args.new_tab,]: @@ -36,6 +40,7 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi event_system.subscribe("display_message", self.display_message) event_system.subscribe("go_to_path", self.go_to_path) event_system.subscribe("do_hide_context_menu", self.do_hide_context_menu) + event_system.subscribe("do_action_from_menu_controls", self.do_action_from_menu_controls) def tear_down(self, widget=None, eve=None): if not settings.is_trace_debug(): @@ -95,7 +100,11 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi def do_action_from_menu_controls(self, widget, eve = None): - action = widget.get_name() + if not isinstance(widget, str): + action = widget.get_name() + else: + action = widget + self.hide_context_menu() self.hide_new_file_menu() self.hide_edit_file_menu() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py index dabae41..9f9943c 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py @@ -102,89 +102,6 @@ class Controller_Data: if settings.is_debug(): self.window.set_interactive_debugging(True) - self.build_context_menu() - - - def build_context_menu(self) -> None: - main_menu = Gtk.Menu() - open_menu = Gtk.Menu() - filea_menu = Gtk.Menu() - plugins_menu = Gtk.Menu() - open_a = Gtk.MenuItem("Open Actions") - file_a = Gtk.MenuItem("File Actions") - plugins = Gtk.MenuItem("Plugins") - - self._appen_menu_items(main_menu, [open_a, file_a, plugins]) - - open = Gtk.ImageMenuItem("Open") - open.set_name("open") - open.set_image( Gtk.Image(stock=Gtk.STOCK_OPEN) ) - open.set_always_show_image(True) - open.connect("activate", self.do_action_from_menu_controls) - - open_with = Gtk.ImageMenuItem("Open With") - open_with.set_name("open_with") - open_with.set_image( Gtk.Image(stock=Gtk.STOCK_OPEN) ) - open_with.set_always_show_image(True) - open_with.connect("activate", self.do_action_from_menu_controls) - - execute = Gtk.ImageMenuItem("Execute") - execute.set_name("execute") - execute.set_image( Gtk.Image(stock=Gtk.STOCK_EXECUTE) ) - execute.set_always_show_image(True) - execute.connect("activate", self.do_action_from_menu_controls) - - execute_term = Gtk.ImageMenuItem("Execute in Terminal") - execute_term.set_name("execute_in_terminal") - execute_term.set_image( Gtk.Image(stock=Gtk.STOCK_EXECUTE) ) - execute_term.set_always_show_image(True) - execute_term.connect("activate", self.do_action_from_menu_controls) - - self._appen_menu_items(open_menu, [open, open_with, execute, execute_term]) - - new = Gtk.ImageMenuItem("New") - new.set_name("create") - new.set_image( Gtk.Image(stock=Gtk.STOCK_ADD) ) - new.set_always_show_image(True) - new.connect("activate", self.do_action_from_menu_controls) - - rename = Gtk.ImageMenuItem("Rename") - rename.set_name("rename") - rename.set_image( Gtk.Image(stock=Gtk.STOCK_EDIT) ) - rename.set_always_show_image(True) - rename.connect("activate", self.do_action_from_menu_controls) - - cut = Gtk.ImageMenuItem("Cut") - cut.set_name("cut") - cut.set_image( Gtk.Image(stock=Gtk.STOCK_CUT) ) - cut.set_always_show_image(True) - cut.connect("activate", self.do_action_from_menu_controls) - - copy = Gtk.ImageMenuItem("Copy") - copy.set_name("copy") - copy.set_image( Gtk.Image(stock=Gtk.STOCK_COPY) ) - copy.set_always_show_image(True) - copy.connect("activate", self.do_action_from_menu_controls) - - paste = Gtk.ImageMenuItem("Paste") - paste.set_name("paste") - paste.set_image( Gtk.Image(stock=Gtk.STOCK_PASTE) ) - paste.set_always_show_image(True) - paste.connect("activate", self.do_action_from_menu_controls) - - self._appen_menu_items(filea_menu, [new, rename, cut, copy, paste]) - open_a.set_submenu(open_menu) - file_a.set_submenu(filea_menu) - plugins.set_submenu(plugins_menu) - - main_menu.attach_to_widget(self.window, None) - main_menu.show_all() - self.builder.expose_object("context_menu", main_menu) - self.builder.expose_object("context_menu_plugins", plugins_menu) - - def _appen_menu_items(self, menu, items): - for item in items: - menu.append(item) def get_current_state(self) -> State: ''' 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 58eb0f4..80b8dac 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 @@ -31,6 +31,7 @@ class Settings: self._KEY_BINDINGS = f"{self._CONFIG_PATH}/key-bindings.json" self._DEFAULT_ICONS = f"{self._CONFIG_PATH}/icons" self._WINDOW_ICON = f"{self._DEFAULT_ICONS}/{app_name.lower()}.png" + self._CONTEXT_MENU = f"{self._CONFIG_PATH}/contexct_menu.json" self._PID_FILE = f"{self._CONFIG_PATH}/{app_name.lower()}.pid" self._ICON_THEME = Gtk.IconTheme.get_default() @@ -41,6 +42,8 @@ class Settings: if not os.path.exists(self._GLADE_FILE): self._GLADE_FILE = f"{self._USR_SOLARFM}/Main_Window.glade" + if not os.path.exists(self._CONTEXT_MENU): + self._CONTEXT_MENU = f"{self._USR_SOLARFM}/contexct_menu.json" if not os.path.exists(self._KEY_BINDINGS): self._KEY_BINDINGS = f"{self._USR_SOLARFM}/key-bindings.json" if not os.path.exists(self._CSS_FILE): @@ -59,6 +62,9 @@ class Settings: keybindings = json.load(file)["keybindings"] self._keybindings.configure(keybindings) + with open(self._CONTEXT_MENU) as file: + self._context_menu_data = json.load(file) + self._main_window = None self._logger = Logger(self._CONFIG_PATH, _fh_log_lvl=20).get_logger() self._builder = Gtk.Builder() @@ -143,6 +149,7 @@ class Settings: return monitors + def get_context_menu_data(self) -> Gtk.Builder: return self._context_menu_data def get_main_window(self) -> Gtk.ApplicationWindow: return self._main_window def get_builder(self) -> Gtk.Builder: return self._builder def get_logger(self) -> Logger: return self._logger diff --git a/user_config/usr/share/solarfm/contexct_menu.json b/user_config/usr/share/solarfm/contexct_menu.json new file mode 100644 index 0000000..fde59f3 --- /dev/null +++ b/user_config/usr/share/solarfm/contexct_menu.json @@ -0,0 +1,16 @@ +{ + "Open Actions": { + "Open": ["STOCK_OPEN", "open"], + "Open With": ["STOCK_OPEN", "open_with"], + "Execute": ["STOCK_EXECUTE", "execute"], + "Execute in Terminal": ["STOCK_EXECUTE", "execute_in_terminal"] + }, + "File Actions": { + "New": ["STOCK_ADD", "create"], + "Rename": ["STOCK_EDIT", "rename"], + "Cut": ["STOCK_CUT", "cut"], + "Copy": ["STOCK_COPY", "copy"], + "Paste": ["STOCK_PASTE", "paste"] + }, + "Plugins": {} +} From eeef0a43300ce05dcd6874659d61916b4ab00aed Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Thu, 27 Oct 2022 17:23:27 -0500 Subject: [PATCH 20/30] Search changes, plugin changes --- plugins/favorites/manifest.json | 2 +- plugins/favorites/plugin.py | 4 +- plugins/searcher/manifest.json | 2 +- plugins/searcher/plugin.py | 2 + .../SolarFM/solarfm/core/controller_data.py | 3 - .../mixins/ui/widget_file_action_mixin.py | 35 +++-- .../solarfm/core/mixins/ui/window_mixin.py | 3 - .../SolarFM/solarfm/core/mixins/ui_mixin.py | 2 +- .../core/signals/keyboard_signals_mixin.py | 16 -- .../usr/share/solarfm/Main_Window.glade | 146 +++++++----------- 10 files changed, 87 insertions(+), 128 deletions(-) diff --git a/plugins/favorites/manifest.json b/plugins/favorites/manifest.json index b6f006b..ce8a2f3 100644 --- a/plugins/favorites/manifest.json +++ b/plugins/favorites/manifest.json @@ -8,7 +8,7 @@ "ui_target": "main_menu_bttn_box_bar", "pass_fm_events": "true", "pass_ui_objects": ["path_entry"], - "bind_keys": [] + "bind_keys": ["Favorites||show_favorites_menu:f"] } } } diff --git a/plugins/favorites/plugin.py b/plugins/favorites/plugin.py index e55fe8a..9dc40ed 100644 --- a/plugins/favorites/plugin.py +++ b/plugins/favorites/plugin.py @@ -30,7 +30,7 @@ class Plugin(PluginBase): super().__init__() self.name = "Favorites" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus - # where self.name should not be needed for message comms + # where self.name should not be needed for message comms self.path = os.path.dirname(os.path.realpath(__file__)) self._GLADE_FILE = f"{self.path}/favorites.glade" self._FAVORITES_FILE = f"{self.path}/favorites.json" @@ -70,6 +70,8 @@ class Plugin(PluginBase): with open(self._FAVORITES_FILE, 'a') as f: f.write('[]') + self._event_system.subscribe("show_favorites_menu", self._show_favorites_menu) + def generate_reference_ui_element(self): button = Gtk.Button(label=self.name) diff --git a/plugins/searcher/manifest.json b/plugins/searcher/manifest.json index 845a31f..6b09f7d 100644 --- a/plugins/searcher/manifest.json +++ b/plugins/searcher/manifest.json @@ -7,7 +7,7 @@ "requests": { "ui_target": "context_menu", "pass_fm_events": "true", - "bind_keys": ["Search||_show_grep_list_page:f"] + "bind_keys": ["Search||show_search_page:s"] } } } diff --git a/plugins/searcher/plugin.py b/plugins/searcher/plugin.py index 17eaa63..a154805 100644 --- a/plugins/searcher/plugin.py +++ b/plugins/searcher/plugin.py @@ -76,6 +76,8 @@ class Plugin(IPCServer, FileSearchMixin, GrepSearchMixin, PluginBase): self._event_system.subscribe("update-file-ui", self._load_file_ui) self._event_system.subscribe("update-grep-ui", self._load_grep_ui) + self._event_system.subscribe("show_search_page", self._show_page) + self.create_ipc_listener() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py index 9f9943c..f842051 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py @@ -79,9 +79,6 @@ class Controller_Data: self.is_pane4_hidden = False self.override_drop_dest = None - self.is_searching = False - self.search_icon_grid = None - self.search_tab = None self.cancel_creation = False self.skip_edit = False 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 04f8400..007008a 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 @@ -102,24 +102,27 @@ class WidgetFileActionMixin: self.set_bottom_labels(tab) - def popup_search_files(self, wid, keyname): - entry = self.builder.get_object(f"win{wid}_search_field") - self.builder.get_object(f"win{wid}_search").popup() - entry.set_text(keyname) - entry.grab_focus_without_selecting() - entry.set_position(-1) - def do_file_search(self, widget, eve=None): - query = widget.get_text().lower() - self.search_icon_grid.unselect_all() - for i, file in enumerate(self.search_tab.get_files()): - if query and query in file[0].lower(): - path = Gtk.TreePath().new_from_indices([i]) - self.search_icon_grid.select_path(path) + if not self.ctrl_down and not self.shift_down and not self.alt_down: + target = widget.get_name() + notebook = self.builder.get_object(target) + page = notebook.get_current_page() + nth_page = notebook.get_nth_page(page) + icon_grid = nth_page.get_children()[0] - items = self.search_icon_grid.get_selected_items() - if len(items) > 0: - self.search_icon_grid.scroll_to_path(items[-1], True, 0.5, 0.5) + wid, tid = icon_grid.get_name().split("|") + tab = self.get_fm_window(wid).get_tab_by_id(tid) + query = widget.get_text().lower() + + icon_grid.unselect_all() + for i, file in enumerate(tab.get_files()): + if query and query in file[0].lower(): + path = Gtk.TreePath().new_from_indices([i]) + icon_grid.select_path(path) + + items = icon_grid.get_selected_items() + if len(items) == 1: + icon_grid.scroll_to_path(items[-1], True, 0.5, 0.5) def open_files(self): diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py index fddb9d3..5b3d9b1 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py @@ -161,9 +161,6 @@ class WindowMixin(TabMixin): def grid_set_selected_items(self, icons_grid): self.selected_files = icons_grid.get_selected_items() - def grid_cursor_toggled(self, icons_grid): - print("wat...") - def grid_icon_single_click(self, icons_grid, eve): try: self.path_menu.popdown() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui_mixin.py index d127b28..6d61e3e 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui_mixin.py @@ -11,4 +11,4 @@ from .show_hide_mixin import ShowHideMixin class UIMixin(WidgetFileActionMixin, PaneMixin, WindowMixin, ShowHideMixin): - pass + ... 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 bde0689..00062a6 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 @@ -21,7 +21,6 @@ class KeyboardSignalsMixin: self.ctrl_down = False self.shift_down = False self.alt_down = False - self.is_searching = False def on_global_key_press_controller(self, eve, user_data): keyname = Gdk.keyval_name(user_data.keyval).lower() @@ -62,21 +61,6 @@ class KeyboardSignalsMixin: if keyname in ["1", "kp_1", "2", "kp_2", "3", "kp_3", "4", "kp_4"]: self.builder.get_object(f"tggl_notebook_{keyname.strip('kp_')}").released() - if re.fullmatch(valid_keyvalue_pat, keyname): - if not self.is_searching and not self.ctrl_down \ - and not self.shift_down and not self.alt_down: - focused_obj = self.window.get_focus() - if isinstance(focused_obj, Gtk.IconView): - self.is_searching = True - state = self.get_current_state() - self.search_tab = state.tab - self.search_icon_grid = state.icon_grid - - self.unset_keys_and_data() - self.popup_search_files(state.wid, keyname) - return True - - def handle_plugin_key_event(self, sender, eve_type): event_system.emit(eve_type) diff --git a/user_config/usr/share/solarfm/Main_Window.glade b/user_config/usr/share/solarfm/Main_Window.glade index 0176307..c5d79b1 100644 --- a/user_config/usr/share/solarfm/Main_Window.glade +++ b/user_config/usr/share/solarfm/Main_Window.glade @@ -1073,6 +1073,21 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe + + + window_1 + True + True + edit-find-symbolic + False + False + Search... + + + + False + + False @@ -1111,6 +1126,21 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe + + + window_2 + True + True + edit-find-symbolic + False + False + Search... + + + + False + + False @@ -1163,6 +1193,21 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe + + + window_3 + True + True + edit-find-symbolic + False + False + Search... + + + + False + + False @@ -1200,6 +1245,21 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe + + + window_4 + True + True + edit-find-symbolic + False + False + Search... + + + + False + + False @@ -2045,92 +2105,6 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
    - - False - 5 - 5 - 5 - 5 - window_1 - bottom - none - - - - 52 - True - True - - - - - - - False - 5 - 5 - 5 - 5 - window_2 - bottom - none - - - - 96 - True - True - - - - - - - False - 5 - 5 - 5 - 5 - window_3 - none - - - - 96 - True - True - - - - - - - False - 5 - 5 - 5 - 5 - window_4 - none - - - - 96 - True - True - - - - - False False From ee123c4916cb95cfcdcc6ab30bab3975785c48ed Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Thu, 27 Oct 2022 21:26:58 -0500 Subject: [PATCH 21/30] Added better type hinting --- .../SolarFM/solarfm/core/controller_data.py | 1 - .../solarfm/shellfm/windows/controller.py | 60 +++++++------- .../solarfm/shellfm/windows/tabs/path.py | 18 ++--- .../solarfm/shellfm/windows/tabs/tab.py | 80 +++++++++---------- .../SolarFM/solarfm/shellfm/windows/window.py | 42 +++++----- 5 files changed, 103 insertions(+), 98 deletions(-) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py index f842051..6011cf7 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py @@ -117,7 +117,6 @@ class Controller_Data: state.store = state.icon_grid.get_model() state.warning_alert = self.warning_alert - selected_files = state.icon_grid.get_selected_items() if selected_files: state.selected_files = self.format_to_uris(state.store, state.wid, state.tid, selected_files, True) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/controller.py index f2b0e16..b4aa623 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/controller.py @@ -16,100 +16,100 @@ def threaded(fn): class WindowController: def __init__(self): - USER_HOME = path.expanduser('~') - CONFIG_PATH = USER_HOME + "/.config/solarfm" - self._session_file = CONFIG_PATH + "/session.json" + USER_HOME: str = path.expanduser('~') + CONFIG_PATH: str = f"{USER_HOME}/.config/solarfm" + self._session_file: srr = f"{CONFIG_PATH}/session.json" - self._event_sleep_time = 1 - self._active_window_id = "" - self._active_tab_id = "" - self._windows = [] + self._event_sleep_time: int = 1 + self._active_window_id: str = "" + self._active_tab_id: str = "" + self._windows: list = [] - def set_wid_and_tid(self, wid, tid): + def set_wid_and_tid(self, wid: int, tid: int) -> None: self._active_window_id = str(wid) self._active_tab_id = str(tid) - def get_active_wid_and_tid(self): + def get_active_wid_and_tid(self) -> list: return self._active_window_id, self._active_tab_id - def create_window(self): + def create_window(self) -> Window: window = Window() window.set_nickname(f"window_{str(len(self._windows) + 1)}") self._windows.append(window) return window - def add_tab_for_window(self, win_id): + def add_tab_for_window(self, win_id: str) -> None: for window in self._windows: if window.get_id() == win_id: return window.create_tab() - def add_tab_for_window_by_name(self, name): + def add_tab_for_window_by_name(self, name: str) -> None: for window in self._windows: if window.get_name() == name: return window.create_tab() - def add_tab_for_window_by_nickname(self, nickname): + def add_tab_for_window_by_nickname(self, nickname: str) -> None: for window in self._windows: if window.get_nickname() == nickname: return window.create_tab() - def pop_window(self): + def pop_window(self) -> None: self._windows.pop() - def delete_window_by_id(self, win_id): + def delete_window_by_id(self, win_id: str) -> None: for window in self._windows: if window.get_id() == win_id: self._windows.remove(window) break - def delete_window_by_name(self, name): + def delete_window_by_name(self, name: str) -> str: for window in self._windows: if window.get_name() == name: self._windows.remove(window) break - def delete_window_by_nickname(self, nickname): + def delete_window_by_nickname(self, nickname: str) -> str: for window in self._windows: if window.get_nickname() == nickname: self._windows.remove(window) break - def get_window_by_id(self, win_id): + def get_window_by_id(self, win_id: str) -> Window: for window in self._windows: if window.get_id() == win_id: return window raise(f"No Window by ID {win_id} found!") - def get_window_by_name(self, name): + def get_window_by_name(self, name: str) -> Window: for window in self._windows: if window.get_name() == name: return window raise(f"No Window by Name {name} found!") - def get_window_by_nickname(self, nickname): + def get_window_by_nickname(self, nickname: str) -> Window: for window in self._windows: if window.get_nickname() == nickname: return window raise(f"No Window by Nickname {nickname} found!") - def get_window_by_index(self, index): + def get_window_by_index(self, index: int) -> Window: return self._windows[index] - def get_all_windows(self): + def get_all_windows(self) -> list: return self._windows - def set_window_nickname(self, win_id = None, nickname = ""): + def set_window_nickname(self, win_id: str = None, nickname: str = "") -> None: for window in self._windows: if window.get_id() == win_id: window.set_nickname(nickname) - def list_windows(self): + def list_windows(self) -> None: print("\n[ ---- Windows ---- ]\n") for window in self._windows: print(f"\nID: {window.get_id()}") @@ -121,18 +121,18 @@ class WindowController: - def list_files_from_tabs_of_window(self, win_id): + def list_files_from_tabs_of_window(self, win_id: str) -> None: for window in self._windows: if window.get_id() == win_id: window.list_files_from_tabs() break - def get_tabs_count(self, win_id): + def get_tabs_count(self, win_id: str) -> int: for window in self._windows: if window.get_id() == win_id: return window.get_tabs_count() - def get_tabs_from_window(self, win_id): + def get_tabs_from_window(self, win_id: str) -> list: for window in self._windows: if window.get_id() == win_id: return window.get_all_tabs() @@ -140,13 +140,13 @@ class WindowController: - def unload_tabs_and_windows(self): + def unload_tabs_and_windows(self) -> None: for window in self._windows: window.get_all_tabs().clear() self._windows.clear() - def save_state(self, session_file = None): + def save_state(self, session_file: str = None) -> None: if not session_file: session_file = self._session_file @@ -174,7 +174,7 @@ class WindowController: else: raise Exception("Window data corrupted! Can not save session!") - def get_state_from_file(self, session_file = None): + def get_state_from_file(self, session_file: str = None) -> dict: if not session_file: session_file = self._session_file diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/path.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/path.py index ecce282..6b3c415 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/path.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/path.py @@ -7,20 +7,20 @@ import os class Path: - def get_home(self): + def get_home(self) -> str: return os.path.expanduser("~") + self.subpath - def get_path(self): + def get_path(self) -> str: return f"/{'/'.join(self.path)}" if self.path else f"/{''.join(self.path)}" - def get_path_list(self): + def get_path_list(self) -> list: return self.path - def push_to_path(self, dir): + def push_to_path(self, dir: str): self.path.append(dir) self.load_directory() - def pop_from_path(self): + def pop_from_path(self) -> None: try: self.path.pop() @@ -32,9 +32,9 @@ class Path: except Exception as e: pass - def set_path(self, path): + def set_path(self, path: str) -> bool: if path == self.get_path(): - return + return False if os.path.isdir(path): self.path = list( filter(None, path.replace("\\", "/").split('/')) ) @@ -43,7 +43,7 @@ class Path: return False - def set_path_with_sub_path(self, sub_path): + def set_path_with_sub_path(self, sub_path: str) -> bool: path = os.path.join(self.get_home(), sub_path) if path == self.get_path(): return False @@ -55,7 +55,7 @@ class Path: return False - def set_to_home(self): + def set_to_home(self) -> None: home = os.path.expanduser("~") + self.subpath path = list( filter(None, home.replace("\\", "/").split('/')) ) self.path = path diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/tab.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/tab.py index 70acaed..de483ae 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/tab.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/tab.py @@ -20,25 +20,25 @@ from .path import Path class Tab(Settings, FileHandler, Launcher, Icon, Path): def __init__(self): - self.logger = None - self._id_length = 10 + self.logger = None + self._id_length: int = 10 - self._id = "" - self._wid = None - self._dir_watcher = None - self._hide_hidden = self.HIDE_HIDDEN_FILES - self._files = [] - self._dirs = [] - self._vids = [] - self._images = [] - self._desktop = [] - self._ungrouped = [] - self._hidden = [] + self._id: str = "" + self._wid: str = None + self._dir_watcher = None + self._hide_hidden: bool = self.HIDE_HIDDEN_FILES + self._files: list = [] + self._dirs: list = [] + self._vids: list = [] + self._images: list = [] + self._desktop: list = [] + self._ungrouped: list = [] + self._hidden: list = [] self._generate_id() self.set_to_home() - def load_directory(self): + def load_directory(self) -> None: path = self.get_path() self._dirs = [] self._vids = [] @@ -97,7 +97,7 @@ class Tab(Settings, FileHandler, Launcher, Icon, Path): return False - def get_not_hidden_count(self): + def get_not_hidden_count(self) -> int: return len(self._files) + \ len(self._dirs) + \ len(self._vids) + \ @@ -105,13 +105,13 @@ class Tab(Settings, FileHandler, Launcher, Icon, Path): len(self._desktop) + \ len(self._ungrouped) - def get_hidden_count(self): + def get_hidden_count(self) -> int: return len(self._hidden) - def get_files_count(self): + def get_files_count(self) -> int: return len(self._files) - def get_path_part_from_hash(self, hash): + def get_path_part_from_hash(self, hash: str) -> str: files = self.get_files() file = None @@ -122,7 +122,7 @@ class Tab(Settings, FileHandler, Launcher, Icon, Path): return file - def get_files_formatted(self): + def get_files_formatted(self) -> dict: files = self._hash_set(self._files), dirs = self._hash_set(self._dirs), videos = self.get_videos(), @@ -154,7 +154,7 @@ class Tab(Settings, FileHandler, Launcher, Icon, Path): return data - def get_gtk_icon_str_combo(self): + def get_gtk_icon_str_combo(self) -> list: data = [] dir = self.get_current_directory() for file in self._files: @@ -163,57 +163,57 @@ class Tab(Settings, FileHandler, Launcher, Icon, Path): return data - def get_current_directory(self): + def get_current_directory(self) -> str: return self.get_path() - def get_current_sub_path(self): + def get_current_sub_path(self) -> str: path = self.get_path() home = f"{self.get_home()}/" return path.replace(home, "") - def get_end_of_path(self): + def get_end_of_path(self) -> str: parts = self.get_current_directory().split("/") size = len(parts) return parts[size - 1] - def set_hiding_hidden(self, state): + def set_hiding_hidden(self, state: bool) -> None: self._hide_hidden = state - def is_hiding_hidden(self): + def is_hiding_hidden(self) -> bool: return self._hide_hidden - def get_dot_dots(self): + def get_dot_dots(self) -> list: return self._hash_set(['.', '..']) - def get_files(self): + def get_files(self) -> list: return self._hash_set(self._files) - def get_dirs(self): + def get_dirs(self) -> list: return self._hash_set(self._dirs) - def get_videos(self): + def get_videos(self) -> list: return self._hash_set(self._vids) - def get_images(self): + def get_images(self) -> list: return self._hash_set(self._images) - def get_desktops(self): + def get_desktops(self) -> list: return self._hash_set(self._desktop) - def get_ungrouped(self): + def get_ungrouped(self) -> list: return self._hash_set(self._ungrouped) - def get_hidden(self): + def get_hidden(self) -> list: return self._hash_set(self._hidden) - def get_id(self): + def get_id(self) -> str: return self._id - def set_wid(self, _wid): + def set_wid(self, _wid: str) -> None: self._wid = _wid - def get_wid(self): + def get_wid(self) -> str: return self._wid def set_dir_watcher(self, watcher): @@ -228,19 +228,19 @@ class Tab(Settings, FileHandler, Launcher, Icon, Path): def _natural_keys(self, text): return [ self._atoi(c) for c in re.split('(\d+)',text) ] - def _hash_text(self, text): + def _hash_text(self, text) -> str: return hashlib.sha256(str.encode(text)).hexdigest()[:18] - def _hash_set(self, arry): + def _hash_set(self, arry: list) -> list: data = [] for arr in arry: data.append([arr, self._hash_text(arr)]) return data - def _random_with_N_digits(self, n): + def _random_with_N_digits(self, n: int) -> int: range_start = 10**(n-1) range_end = (10**n)-1 return randint(range_start, range_end) - def _generate_id(self): + def _generate_id(self) -> str: self._id = str(self._random_with_N_digits(self._id_length)) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/window.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/window.py index ec61cd6..8105091 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/window.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/window.py @@ -11,62 +11,68 @@ from .tabs.tab import Tab class Window: def __init__(self): - self._id_length = 10 - self._id = "" - self._name = "" - self._nickname = "" - self._isHidden = False - self._tabs = [] + self._id_length: int = 10 + self._id: str = "" + self._name: str = "" + self._nickname:str = "" + self._isHidden: bool = False + self._active_tab: int = 0 + self._tabs: list = [] self._generate_id() self._set_name() - def create_tab(self): + def create_tab(self) -> Tab: tab = Tab() self._tabs.append(tab) return tab - def pop_tab(self): + def pop_tab(self) -> None: self._tabs.pop() - def delete_tab_by_id(self, tid): + def delete_tab_by_id(self, tid: str): for tab in self._tabs: if tab.get_id() == tid: self._tabs.remove(tab) break - def get_tab_by_id(self, tid): + def get_tab_by_id(self, tid: str) -> Tab: for tab in self._tabs: if tab.get_id() == tid: return tab - def get_tab_by_index(self, index): + def get_tab_by_index(self, index) -> Tab: return self._tabs[index] - def get_tabs_count(self): + def get_tabs_count(self) -> int: return len(self._tabs) - def get_all_tabs(self): + def get_all_tabs(self) -> list: return self._tabs - def get_id(self): + def get_id(self) -> str: return self._id - def get_name(self): + def get_name(self) -> str: return self._name - def get_nickname(self): + def get_nickname(self) -> str: return self._nickname - def is_hidden(self): + def is_hidden(self) -> bool: return self._isHidden - def list_files_from_tabs(self): + def list_files_from_tabs(self) -> None: for tab in self._tabs: print(tab.get_files()) + def set_active_tab(self, index: int): + self._active_tab = index + + def get_active_tab(self) -> Tab: + return self._tabs[self._active_tab] def set_nickname(self, nickname): self._nickname = f"{nickname}" From 9cde8345cfed2fe5c2c1c8b15bbf327ea12a2650 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Wed, 2 Nov 2022 22:06:30 -0500 Subject: [PATCH 22/30] Tab dnd between windows (state not preserved, yet), plugin fix --- plugins/archiver/plugin.py | 2 +- src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py | 9 +++++++-- .../SolarFM/solarfm/core/mixins/ui/tab_mixin.py | 5 +++++ user_config/usr/share/solarfm/Main_Window.glade | 8 ++++++++ 4 files changed, 21 insertions(+), 3 deletions(-) diff --git a/plugins/archiver/plugin.py b/plugins/archiver/plugin.py index 5426094..393de1f 100644 --- a/plugins/archiver/plugin.py +++ b/plugins/archiver/plugin.py @@ -122,4 +122,4 @@ class Plugin(PluginBase): def set_arc_buffer_text(self, widget=None, eve=None): sid = widget.get_active_id() - self.arc_command_buffer.set_text(self.arc_commands[int(sid)]) + self._arc_command_buffer.set_text(self.arc_commands[int(sid)]) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py index e1fa0e9..e256a29 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py @@ -18,9 +18,9 @@ from __builtins__ import * from app import Application -if __name__ == "__main__": - """ Set process title, get arguments, and create GTK main thread. """ + +def run(): try: setproctitle(f"{app_name}") faulthandler.enable() # For better debug info @@ -47,3 +47,8 @@ if __name__ == "__main__": except Exception as e: traceback.print_exc() quit() + + +if __name__ == "__main__": + """ Set process title, get arguments, and create GTK main thread. """ + run() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/tab_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/tab_mixin.py index 683e6b4..2460417 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/tab_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/tab_mixin.py @@ -35,6 +35,7 @@ class TabMixin(GridMixin): tab_widget = self.create_tab_widget(tab) scroll, store = self.create_scroll_and_store(tab, wid) index = notebook.append_page(scroll, tab_widget) + notebook.set_tab_detachable(scroll, True) self.fm_controller.set_wid_and_tid(wid, tab.get_id()) path_entry.set_text(tab.get_current_directory()) @@ -67,6 +68,10 @@ class TabMixin(GridMixin): self.fm_controller.save_state() self.set_window_title() + # NOTE: Not actually getting called even tho set in the glade file... + def on_tab_dnded(self, notebook, page, x, y): + ... + def on_tab_reorder(self, child, page_num, new_index): wid, tid = page_num.get_name().split("|") window = self.get_fm_window(wid) diff --git a/user_config/usr/share/solarfm/Main_Window.glade b/user_config/usr/share/solarfm/Main_Window.glade index c5d79b1..de3b02a 100644 --- a/user_config/usr/share/solarfm/Main_Window.glade +++ b/user_config/usr/share/solarfm/Main_Window.glade @@ -1053,6 +1053,8 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe 5 False True + sfm_windows + @@ -1106,6 +1108,8 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe 5 False True + sfm_windows + @@ -1173,6 +1177,8 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe 5 False True + sfm_windows + @@ -1225,6 +1231,8 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe 5 False True + sfm_windows + From 4f9fe37613b21b2db9fe130004b6fd6a84991ef6 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Fri, 25 Nov 2022 00:34:16 -0600 Subject: [PATCH 23/30] added disable plugin load arg, added disk usage plugin, updated icon gen --- plugins/disk_usage/__init__.py | 3 + plugins/disk_usage/__main__.py | 3 + plugins/disk_usage/du_usage.glade | 129 ++++++++++++++++++ plugins/disk_usage/manifest.json | 12 ++ plugins/disk_usage/plugin.py | 104 ++++++++++++++ .../solarfm-0.0.1/SolarFM/solarfm/__main__.py | 2 + .../SolarFM/solarfm/core/controller.py | 3 +- .../tabs/icons/mixins/desktopiconmixin.py | 12 +- .../usr/share/solarfm/fileicons/3g2.png | Bin 0 -> 3780 bytes .../usr/share/solarfm/fileicons/3gp.png | Bin 0 -> 3771 bytes .../usr/share/solarfm/fileicons/ai.png | Bin 0 -> 3547 bytes .../usr/share/solarfm/fileicons/air.png | Bin 0 -> 3170 bytes .../usr/share/solarfm/fileicons/asf.png | Bin 0 -> 3126 bytes .../usr/share/solarfm/fileicons/avi.png | Bin 0 -> 3208 bytes .../usr/share/solarfm/fileicons/bib.png | Bin 0 -> 3531 bytes .../usr/share/solarfm/fileicons/cls.png | Bin 0 -> 1763 bytes .../usr/share/solarfm/fileicons/csv.png | Bin 0 -> 4519 bytes .../usr/share/solarfm/fileicons/deb.png | Bin 0 -> 2915 bytes .../usr/share/solarfm/fileicons/djvu.png | Bin 0 -> 4973 bytes .../usr/share/solarfm/fileicons/dmg.png | Bin 0 -> 2523 bytes .../usr/share/solarfm/fileicons/doc.png | Bin 0 -> 1536 bytes .../usr/share/solarfm/fileicons/docx.png | Bin 0 -> 4144 bytes .../usr/share/solarfm/fileicons/dwf.png | Bin 0 -> 4319 bytes .../usr/share/solarfm/fileicons/dwg.png | Bin 0 -> 3107 bytes .../usr/share/solarfm/fileicons/eps.png | Bin 0 -> 2299 bytes .../usr/share/solarfm/fileicons/epub.png | Bin 0 -> 2772 bytes .../usr/share/solarfm/fileicons/exe.png | Bin 0 -> 2651 bytes user_config/usr/share/solarfm/fileicons/f.png | Bin 0 -> 2050 bytes .../usr/share/solarfm/fileicons/f77.png | Bin 0 -> 2187 bytes .../usr/share/solarfm/fileicons/f90.png | Bin 0 -> 2212 bytes .../usr/share/solarfm/fileicons/flac.png | Bin 0 -> 1121 bytes .../usr/share/solarfm/fileicons/flv.png | Bin 0 -> 3181 bytes .../usr/share/solarfm/fileicons/gif.png | Bin 0 -> 2802 bytes .../usr/share/solarfm/fileicons/gz.png | Bin 0 -> 1480 bytes .../usr/share/solarfm/fileicons/ico.png | Bin 0 -> 3130 bytes .../usr/share/solarfm/fileicons/indd.png | Bin 0 -> 4031 bytes .../usr/share/solarfm/fileicons/iso.png | Bin 0 -> 2704 bytes .../usr/share/solarfm/fileicons/jpeg.png | Bin 0 -> 3319 bytes .../usr/share/solarfm/fileicons/jpg.png | Bin 0 -> 3319 bytes .../usr/share/solarfm/fileicons/log.png | Bin 0 -> 2486 bytes .../usr/share/solarfm/fileicons/m4a.png | Bin 0 -> 3806 bytes .../usr/share/solarfm/fileicons/m4v.png | Bin 0 -> 3178 bytes .../usr/share/solarfm/fileicons/midi.png | Bin 0 -> 3511 bytes .../usr/share/solarfm/fileicons/mkv.png | Bin 0 -> 1915 bytes .../usr/share/solarfm/fileicons/mov.png | Bin 0 -> 3197 bytes .../usr/share/solarfm/fileicons/mp3.png | Bin 0 -> 4064 bytes .../usr/share/solarfm/fileicons/mp4.png | Bin 0 -> 3156 bytes .../usr/share/solarfm/fileicons/mpeg.png | Bin 0 -> 3195 bytes .../usr/share/solarfm/fileicons/mpg.png | Bin 0 -> 3195 bytes .../usr/share/solarfm/fileicons/msi.png | Bin 0 -> 2655 bytes .../usr/share/solarfm/fileicons/odp.png | Bin 0 -> 2895 bytes .../usr/share/solarfm/fileicons/ods.png | Bin 0 -> 2435 bytes .../usr/share/solarfm/fileicons/odt.png | Bin 0 -> 3701 bytes .../usr/share/solarfm/fileicons/oga.png | Bin 0 -> 3163 bytes .../usr/share/solarfm/fileicons/ogg.png | Bin 0 -> 5100 bytes .../usr/share/solarfm/fileicons/ogv.png | Bin 0 -> 5921 bytes .../usr/share/solarfm/fileicons/pdf.png | Bin 0 -> 3408 bytes .../usr/share/solarfm/fileicons/png.png | Bin 0 -> 3014 bytes .../usr/share/solarfm/fileicons/pps.png | Bin 0 -> 3178 bytes .../usr/share/solarfm/fileicons/ppsx.png | Bin 0 -> 3306 bytes .../usr/share/solarfm/fileicons/ppt.png | Bin 0 -> 3390 bytes .../usr/share/solarfm/fileicons/pptx.png | Bin 0 -> 3942 bytes .../usr/share/solarfm/fileicons/psd.png | Bin 0 -> 3898 bytes .../usr/share/solarfm/fileicons/pub.png | Bin 0 -> 3822 bytes .../usr/share/solarfm/fileicons/py.png | Bin 0 -> 2431 bytes .../usr/share/solarfm/fileicons/qt.png | Bin 0 -> 3731 bytes .../usr/share/solarfm/fileicons/ra.png | Bin 0 -> 3141 bytes .../usr/share/solarfm/fileicons/ram.png | Bin 0 -> 3269 bytes .../usr/share/solarfm/fileicons/rar.png | Bin 0 -> 3168 bytes .../usr/share/solarfm/fileicons/rm.png | Bin 0 -> 3185 bytes .../usr/share/solarfm/fileicons/rpm.png | Bin 0 -> 3278 bytes .../usr/share/solarfm/fileicons/rtf.png | Bin 0 -> 2400 bytes .../usr/share/solarfm/fileicons/rv.png | Bin 0 -> 3136 bytes .../usr/share/solarfm/fileicons/skp.png | Bin 0 -> 3208 bytes .../usr/share/solarfm/fileicons/spx.png | Bin 0 -> 1283 bytes .../usr/share/solarfm/fileicons/sql.png | Bin 0 -> 1883 bytes .../usr/share/solarfm/fileicons/sty.png | Bin 0 -> 1301 bytes .../usr/share/solarfm/fileicons/tar.png | Bin 0 -> 2781 bytes .../usr/share/solarfm/fileicons/tex.png | Bin 0 -> 2936 bytes .../usr/share/solarfm/fileicons/tgz.png | Bin 0 -> 1757 bytes .../usr/share/solarfm/fileicons/tiff.png | Bin 0 -> 3127 bytes .../usr/share/solarfm/fileicons/ttf.png | Bin 0 -> 2699 bytes .../usr/share/solarfm/fileicons/txt.png | Bin 0 -> 1734 bytes .../usr/share/solarfm/fileicons/vob.png | Bin 0 -> 3184 bytes .../usr/share/solarfm/fileicons/wav.png | Bin 0 -> 3169 bytes .../usr/share/solarfm/fileicons/wmv.png | Bin 0 -> 3205 bytes .../usr/share/solarfm/fileicons/xls.png | Bin 0 -> 4439 bytes .../usr/share/solarfm/fileicons/xlsx.png | Bin 0 -> 4560 bytes .../usr/share/solarfm/fileicons/xml.png | Bin 0 -> 1596 bytes .../usr/share/solarfm/fileicons/xpi.png | Bin 0 -> 4283 bytes .../usr/share/solarfm/fileicons/zip.png | Bin 0 -> 2099 bytes 91 files changed, 266 insertions(+), 2 deletions(-) create mode 100644 plugins/disk_usage/__init__.py create mode 100644 plugins/disk_usage/__main__.py create mode 100644 plugins/disk_usage/du_usage.glade create mode 100644 plugins/disk_usage/manifest.json create mode 100644 plugins/disk_usage/plugin.py create mode 100644 user_config/usr/share/solarfm/fileicons/3g2.png create mode 100644 user_config/usr/share/solarfm/fileicons/3gp.png create mode 100644 user_config/usr/share/solarfm/fileicons/ai.png create mode 100644 user_config/usr/share/solarfm/fileicons/air.png create mode 100644 user_config/usr/share/solarfm/fileicons/asf.png create mode 100644 user_config/usr/share/solarfm/fileicons/avi.png create mode 100644 user_config/usr/share/solarfm/fileicons/bib.png create mode 100644 user_config/usr/share/solarfm/fileicons/cls.png create mode 100644 user_config/usr/share/solarfm/fileicons/csv.png create mode 100644 user_config/usr/share/solarfm/fileicons/deb.png create mode 100644 user_config/usr/share/solarfm/fileicons/djvu.png create mode 100644 user_config/usr/share/solarfm/fileicons/dmg.png create mode 100644 user_config/usr/share/solarfm/fileicons/doc.png create mode 100644 user_config/usr/share/solarfm/fileicons/docx.png create mode 100644 user_config/usr/share/solarfm/fileicons/dwf.png create mode 100644 user_config/usr/share/solarfm/fileicons/dwg.png create mode 100644 user_config/usr/share/solarfm/fileicons/eps.png create mode 100644 user_config/usr/share/solarfm/fileicons/epub.png create mode 100644 user_config/usr/share/solarfm/fileicons/exe.png create mode 100644 user_config/usr/share/solarfm/fileicons/f.png create mode 100644 user_config/usr/share/solarfm/fileicons/f77.png create mode 100644 user_config/usr/share/solarfm/fileicons/f90.png create mode 100644 user_config/usr/share/solarfm/fileicons/flac.png create mode 100644 user_config/usr/share/solarfm/fileicons/flv.png create mode 100644 user_config/usr/share/solarfm/fileicons/gif.png create mode 100644 user_config/usr/share/solarfm/fileicons/gz.png create mode 100644 user_config/usr/share/solarfm/fileicons/ico.png create mode 100644 user_config/usr/share/solarfm/fileicons/indd.png create mode 100644 user_config/usr/share/solarfm/fileicons/iso.png create mode 100644 user_config/usr/share/solarfm/fileicons/jpeg.png create mode 100644 user_config/usr/share/solarfm/fileicons/jpg.png create mode 100644 user_config/usr/share/solarfm/fileicons/log.png create mode 100644 user_config/usr/share/solarfm/fileicons/m4a.png create mode 100644 user_config/usr/share/solarfm/fileicons/m4v.png create mode 100644 user_config/usr/share/solarfm/fileicons/midi.png create mode 100644 user_config/usr/share/solarfm/fileicons/mkv.png create mode 100644 user_config/usr/share/solarfm/fileicons/mov.png create mode 100644 user_config/usr/share/solarfm/fileicons/mp3.png create mode 100644 user_config/usr/share/solarfm/fileicons/mp4.png create mode 100644 user_config/usr/share/solarfm/fileicons/mpeg.png create mode 100644 user_config/usr/share/solarfm/fileicons/mpg.png create mode 100644 user_config/usr/share/solarfm/fileicons/msi.png create mode 100644 user_config/usr/share/solarfm/fileicons/odp.png create mode 100644 user_config/usr/share/solarfm/fileicons/ods.png create mode 100644 user_config/usr/share/solarfm/fileicons/odt.png create mode 100644 user_config/usr/share/solarfm/fileicons/oga.png create mode 100644 user_config/usr/share/solarfm/fileicons/ogg.png create mode 100644 user_config/usr/share/solarfm/fileicons/ogv.png create mode 100644 user_config/usr/share/solarfm/fileicons/pdf.png create mode 100644 user_config/usr/share/solarfm/fileicons/png.png create mode 100644 user_config/usr/share/solarfm/fileicons/pps.png create mode 100644 user_config/usr/share/solarfm/fileicons/ppsx.png create mode 100644 user_config/usr/share/solarfm/fileicons/ppt.png create mode 100644 user_config/usr/share/solarfm/fileicons/pptx.png create mode 100644 user_config/usr/share/solarfm/fileicons/psd.png create mode 100644 user_config/usr/share/solarfm/fileicons/pub.png create mode 100644 user_config/usr/share/solarfm/fileicons/py.png create mode 100644 user_config/usr/share/solarfm/fileicons/qt.png create mode 100644 user_config/usr/share/solarfm/fileicons/ra.png create mode 100644 user_config/usr/share/solarfm/fileicons/ram.png create mode 100644 user_config/usr/share/solarfm/fileicons/rar.png create mode 100644 user_config/usr/share/solarfm/fileicons/rm.png create mode 100644 user_config/usr/share/solarfm/fileicons/rpm.png create mode 100644 user_config/usr/share/solarfm/fileicons/rtf.png create mode 100644 user_config/usr/share/solarfm/fileicons/rv.png create mode 100644 user_config/usr/share/solarfm/fileicons/skp.png create mode 100644 user_config/usr/share/solarfm/fileicons/spx.png create mode 100644 user_config/usr/share/solarfm/fileicons/sql.png create mode 100644 user_config/usr/share/solarfm/fileicons/sty.png create mode 100644 user_config/usr/share/solarfm/fileicons/tar.png create mode 100644 user_config/usr/share/solarfm/fileicons/tex.png create mode 100644 user_config/usr/share/solarfm/fileicons/tgz.png create mode 100644 user_config/usr/share/solarfm/fileicons/tiff.png create mode 100644 user_config/usr/share/solarfm/fileicons/ttf.png create mode 100644 user_config/usr/share/solarfm/fileicons/txt.png create mode 100644 user_config/usr/share/solarfm/fileicons/vob.png create mode 100644 user_config/usr/share/solarfm/fileicons/wav.png create mode 100644 user_config/usr/share/solarfm/fileicons/wmv.png create mode 100644 user_config/usr/share/solarfm/fileicons/xls.png create mode 100644 user_config/usr/share/solarfm/fileicons/xlsx.png create mode 100644 user_config/usr/share/solarfm/fileicons/xml.png create mode 100644 user_config/usr/share/solarfm/fileicons/xpi.png create mode 100644 user_config/usr/share/solarfm/fileicons/zip.png diff --git a/plugins/disk_usage/__init__.py b/plugins/disk_usage/__init__.py new file mode 100644 index 0000000..d36fa8c --- /dev/null +++ b/plugins/disk_usage/__init__.py @@ -0,0 +1,3 @@ +""" + Pligin Module +""" diff --git a/plugins/disk_usage/__main__.py b/plugins/disk_usage/__main__.py new file mode 100644 index 0000000..a576329 --- /dev/null +++ b/plugins/disk_usage/__main__.py @@ -0,0 +1,3 @@ +""" + Pligin Package +""" diff --git a/plugins/disk_usage/du_usage.glade b/plugins/disk_usage/du_usage.glade new file mode 100644 index 0000000..1b609dc --- /dev/null +++ b/plugins/disk_usage/du_usage.glade @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + 420 + 450 + False + True + center + True + dialog + True + True + False + False + center + + + False + vertical + 2 + + + False + end + + + gtk-close + True + True + True + True + + + + True + True + 2 + + + + + False + False + 0 + + + + + True + False + vertical + + + True + False + 5 + 5 + 5 + 5 + Current Directory: + center + + + False + True + 0 + + + + + True + True + in + + + True + True + du_store + False + + + + + + Disk Usage + + + + 0 + + + + + + 1 + + + + + + + + + True + True + 1 + + + + + True + True + 1 + + + + + + diff --git a/plugins/disk_usage/manifest.json b/plugins/disk_usage/manifest.json new file mode 100644 index 0000000..8bcb387 --- /dev/null +++ b/plugins/disk_usage/manifest.json @@ -0,0 +1,12 @@ +{ + "manifest": { + "name": "Disk Usage", + "author": "ITDominator", + "version": "0.0.1", + "support": "", + "requests": { + "ui_target": "context_menu_plugins", + "pass_fm_events": "true" + } + } +} diff --git a/plugins/disk_usage/plugin.py b/plugins/disk_usage/plugin.py new file mode 100644 index 0000000..790caa2 --- /dev/null +++ b/plugins/disk_usage/plugin.py @@ -0,0 +1,104 @@ +# Python imports +import os, threading, subprocess, time, inspect + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +# Application imports +from plugins.plugin_base import PluginBase + + +# NOTE: Threads WILL NOT die with parent's destruction. +def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() + return wrapper + +# NOTE: Threads WILL die with parent's destruction. +def daemon_threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() + return wrapper + + + + +class Plugin(PluginBase): + def __init__(self): + super().__init__() + + self.name = "Disk Usage" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus + # where self.name should not be needed for message comms + + self.path = os.path.dirname(os.path.realpath(__file__)) + self._GLADE_FILE = f"{self.path}/du_usage.glade" + self._du_dialog = None + self._du_store = None + + + def run(self): + self._builder = Gtk.Builder() + self._builder.add_from_file(self._GLADE_FILE) + + classes = [self] + handlers = {} + for c in classes: + methods = None + try: + methods = inspect.getmembers(c, predicate=inspect.ismethod) + handlers.update(methods) + except Exception as e: + print(repr(e)) + + self._builder.connect_signals(handlers) + + self._du_dialog = self._builder.get_object("du_dialog") + self._du_store = self._builder.get_object("du_store") + self._current_dir_lbl = self._builder.get_object("current_dir_lbl") + + self._event_system.subscribe("show_du_menu", self._show_du_menu) + + def generate_reference_ui_element(self): + item = Gtk.ImageMenuItem(self.name) + item.set_image( Gtk.Image(stock=Gtk.STOCK_HARDDISK) ) + item.connect("activate", self._show_du_menu) + item.set_always_show_image(True) + return item + + def _get_state(self, widget=None, eve=None): + self._event_system.emit("get_current_state") + + def _set_current_dir_lbl(self, widget=None, eve=None): + self._current_dir_lbl.set_label(f"Current Directory:\n{self._fm_state.tab.get_current_directory()}") + + def _show_du_menu(self, widget=None, eve=None): + self._fm_state = None + self._get_state() + self._set_current_dir_lbl() + self.load_du_data() + self._du_dialog.run() + + def load_du_data(self): + self._du_store.clear() + + path = self._fm_state.tab.get_current_directory() + # NOTE: -h = human readable, -d = depth asigned to 1 + command = ["du", "-h", "-d", "1", path] + proc = subprocess.Popen(command, stdout=subprocess.PIPE) + raw_data = proc.communicate()[0] + data = raw_data.decode("utf-8").strip() # NOTE: Will return data AFTER completion (if any) + parts = data.split("\n") + + # NOTE: Last entry is curret dir. Move to top of list and pop off... + size, file = parts[-1].split("\t") + self._du_store.append([size, file.split("/")[-1]]) + parts.pop() + + for part in parts: + size, file = part.split("\t") + self._du_store.append([size, file.split("/")[-1]]) + + def _hide_du_menu(self, widget=None, eve=None): + self._du_dialog.hide() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py index e256a29..91e1fd0 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py @@ -29,9 +29,11 @@ def run(): # Add long and short arguments parser.add_argument("--debug", "-d", default="false", help="Do extra console messaging.") parser.add_argument("--trace-debug", "-td", default="false", help="Disable saves, ignore IPC lock, do extra console messaging.") + parser.add_argument("--no-plugins", "-np", default="false", help="Do not load plugins.") parser.add_argument("--new-tab", "-t", default="", help="Open a file into new tab.") parser.add_argument("--new-window", "-w", default="", help="Open a file into a new window.") + # Read arguments (If any...) args, unknownargs = parser.parse_known_args() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py index c301a38..661f72e 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py @@ -26,7 +26,8 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi cm = ContextMenu() cm.build_context_menu() - self.plugins.launch_plugins() + if args.no_plugins == "false": + self.plugins.launch_plugins() for arg in unknownargs + [args.new_tab,]: if os.path.isdir(arg): diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/desktopiconmixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/desktopiconmixin.py index 3ee2a50..93105fb 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/desktopiconmixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/mixins/desktopiconmixin.py @@ -3,6 +3,11 @@ import os, subprocess, hashlib from os.path import isfile # Gtk imports +import gi +gi.require_version('Gtk', '3.0') + +from gi.repository import Gtk +from gi.repository import Gio # Application imports from .xdg.DesktopEntry import DesktopEntry @@ -36,8 +41,13 @@ class DesktopIconMixin: elif os.path.exists(icon): return self.create_scaled_image(icon, self.sys_icon_wh) else: - alt_icon_path = "" + gio_icon = Gio.Icon.new_for_string(icon) + gicon = Gtk.Image.new_from_gicon(gio_icon, 32) + pixbuf = gicon.get_pixbuf() + if pixbuf: + return pixbuf + alt_icon_path = "" for dir in self.ICON_DIRS: alt_icon_path = self.traverse_icons_folder(dir, icon) if alt_icon_path != "": diff --git a/user_config/usr/share/solarfm/fileicons/3g2.png b/user_config/usr/share/solarfm/fileicons/3g2.png new file mode 100644 index 0000000000000000000000000000000000000000..cccf50a8fa973db06465c39ba1dbbcc99447af26 GIT binary patch literal 3780 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 literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/ai.png b/user_config/usr/share/solarfm/fileicons/ai.png new file mode 100644 index 0000000000000000000000000000000000000000..ddb172fe56041dd50f9ba44be8fa53adcebdeaae GIT binary patch literal 3547 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 VFXbTjN|XQq002ovPDHLkV1mFFyNCb) literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/air.png b/user_config/usr/share/solarfm/fileicons/air.png new file mode 100644 index 0000000000000000000000000000000000000000..076f08e7d7ad9bff52bdcc623586ea0aa5387b37 GIT binary patch literal 3170 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 IM6N<$f)KSF&Hw-a literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/asf.png b/user_config/usr/share/solarfm/fileicons/asf.png new file mode 100644 index 0000000000000000000000000000000000000000..b700cf40b964016385688aece63ef41bd4ab9666 GIT binary patch literal 3126 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 QJOBUy07*qoM6N<$f>?~xy#N3J literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/avi.png b/user_config/usr/share/solarfm/fileicons/avi.png new file mode 100644 index 0000000000000000000000000000000000000000..f4436f748617621c45659611fa536693577e874f GIT binary patch literal 3208 zcmV;340rR1P)XrpyoMQc&{+dI`e1flx7M&_8XwfrwG8WnZmp$K#?pCl)ZKB)QWxDC)0SGMsx@E^ zpeUlFAW0*-S-hE1C`kIce$2; zl#~=mBA}w8f|wB_?tu`a(I}EpYrVa_WnK*?=)Hr$d{I%6Fkv#89P2Uv?%g};-Me@4 zo;`aG%$_~__iuPW18yG5`xXuZxH3_=_y3xz>ts-QioJlR?0g3 z#{IT!+cshd1J7n=W{RmQEhqutIqWfX=+Lir@7}$C-n@A;U-NK_0Vb@jrlzK@w6xTr zYQ5w@(AKS6DJCX{#*ZIQ%a$#}VKimR6k^k&`Sa(~gb5QUD=SMFUA%ZPL2KFpBKaI4 zUc0v%&?5F4K76<^F>BVWNmW%Teoh}*s){j^y$--laoW|&Ycsq=+L2q?4u;9M>Xt? z`}kCsnulLNdyI61**aclHk*gDw*P+c;K40CbHfMx$ptWZ$ zB;&iEpPz#<%)?jscP$In;?jih5Do)0>P-ln9zT9uqz&R>`&X`9q05&q3j@oSFQ??> zWa`?rtAjWkjv&~S29%Jr=A*TLZEVn*TeCiZML1Nu5e|b&3knKo<;s+9>)u3fuE8c^RE z2}DD{Y0Lp_pba?bnl)=0+4jPP3mpW&hV^j(wyu}Fo=Gjy%}HMvU>kqU5SnxK>eX0& zxKf`g@S#D>Kx?g}v_v;`pfkxKHUtI+Iwel80k;BKT3XtVOyCJtZ(4oB$?9|~OS+!T zXUm8YBa)#3Epe`LB0VDt<^28q8>JCVIkVoxnq`2o7a4NEf>p?g7L}EiIb}f0 zh=NkBM3n>Mo^ozigf|7}E}gik0f&PVGc0;4Vw_3cX0zIfZ$J-=rIs(uLDFEL0b-3t zZ$Uf7q*haUiDSK6KH!XaIIcFZ=uP7nNnyQFBwJegRuVltXSLSxRWD3nFW|bTXDe}J zm=fl|tTV*<)fxctD(iGMR9b*_27?zB7Z*#KVX@SS1GP38nP6XGs;sPBPz%f%O|3b$ z_;TJ=&CVPkNaf6=+)k&PBEBi9Yt^ZOOl_@&o<4oX=u%0(9Q3@sZH)gW&Wj@KwojZ0 z7EnpaTh3@ZP+?&K6%`hdkFOsyk;nN?5COjRL9Zo_Y|xvHGs&yXV84(wAn|!_u4n*> z9-ht3ea=K$C?n&Bm=eqd_aKy*m^Rd@Q)kvz5xso*lJ6NQFE3xtg(j3xw2tqV2CzBQ z<;-mK8$tc0rSB+*b0pYuh}kTTiNJ;w1qB7sqD714(`*irDE;C^ zF4_3FpMQQuUAlB`M7%!cxMTnlaA&B31aO-B;strMVw7NuFE1|>Nt?~Vyrv=N2iAMo z1yW}(J;7{cWfkS;=ZUldZZ_tis=9{#Cy$~cJCa3XN^WbQxdqydkgpdNmk7%Evorep z`$=j62i~}GBaI$CTIvO?Llp;RD>wkArjFs~if9EVe^phLw0ZMps%Bxp%tFoWIo~Se zcw=UB@_sMKeJ}%RfM^hg%N!iUMR+gTyLT@gI&?_x&zLcTj-?-`B}gx0TLPuH$pry4drNWPc_6A=;ND6HJpKywS#{hFFu zmRJSV)>KnuWOpe+w{PF>Ft5jtAJg7_d+Bc*Hc%{QrEl3!;Naom?O8jU9K>Vn+qaMA z&Yer$x_w6X?ma-LqvDbhN!wLpz+EjE+zl8Y7|4T~70+a3WYBG{g|#X8OZE*L--ATo zyvb*R!sVHqoTrYAl%0-|s!+<*sZ*zl8F?`x!9Q$3B0B@kC5FF312w_~oDMYt5T~9~ z2t-(z7p}Ot9_-w$q*6O}>=-2^Bv4w~R_fEYAN$1jWMcI3Vc)<$uU@_4>$RX%Fe4MP z4l9~px~k2sg2tU;t!v0_fG|1FbLkleFdrXZ!EvC_mtUqxn*~JOU~@qy;}X}|GIhujqHDvE|l6&T1GMx!(0OFuGKTRDBrl zf%Uz5^`>DdDb$t&5)wN!005Q4h7F@2c6bX%>?v$6iHS+FGmfIG(zGFBaeZFqis=sbQA)vdoZdXfLU3}_zegA@!&t1 z$U6dm#c$uzfdl`bZ@*nmsiVixg$oyG-Cx&88r`{b7bZB6HDacu#9kC1--Gw||wL&L;Jqp zD-D3i$Vfpim>29nIx3ppaPSJ|)I$7$83+##Yc`GfAO=jFA|qWuW@%(qgZQMR1V)uV zOV8F#zU}#7vnHeq-;QamkC7n)BKPCiy)~y>87)VdDvp6AaM=2>oC7|uw zzh81AboQWG#1Xu6_$UnMH&LyVgoJo0TyA9C6#VYpr;m7sLF#~p<`SIU&%YBzM@LH) zSIV%P&8U=^7*Bos_Lg;lEb$GTnU=GoBR$Dx&7i~A)XSC+@&Rq7q#9K*G0}`3p_KV3 zi?TVjsH(0aPY-(o7XixNYDb#|bAZaE#1!=Y?VSrNRr39$q$Fw8Bf&%Jfp>Es4TW3{ z5C`ce^-QF0-G3wSiQ)vE2-OHi?u1ejjQX+0@bUtXt2Ira5ljvZWDNXns#o;E3@FKi zI7D6$f(t1#4uw?li8%x<lp!Fc-jai(89s)WWS)F5l1x zYB&&wtJkeaE5MLStJT{c)!$L;!GM-%^}fJa4~W-#sPjJrg*;VBsW0+D5SY+Qtf^u| z+nQ|Lmb5kMr)uBL@w|sUYC2~?r2x*x>eZ`vZ`-!*Dw|F1hxkiN|0aV4<3yP+xfC93(rMLtrDhjBmpyHOAq*f}5OWn98Xb8z- zh-j`LY6^xTI|#_4fC|D4GsFDPeczAyITPABSH|w?zUMsW`eNYLZf^ccK){z{t*opEIyyR;n*^Hz)yYc+JdiCO=rKzc@g7IJ26S@A8fR=6Av{7u_xan&& z%XHA`bkJ(GP%4$EtSm>-ojb_N%tCH%F2cjZlX#Kmo_kJVW@gs1PoF-|RzRy(trTn? z9v%T8%gV}@vU9T~T3jF}Cr8T8&X%&WvZTz+OerHHLrP0alhV`Eq?kb_{gsWGt;@Gic*}TS!FTSXF{`u$s90Bv^&))$uCR|WZ zfTE%z6c!dXj(K@`xPALJj{+o0fCS;zty@S>PX`GDi9&KpGLn*#5EmDR-Me?|NE}X_ zIFZHcv~S;DME#iwXx+NCp#UZ)G2uIu227qIU$Za<3!vx-P-FxQ1qczz$vAiJ9H$Ug zty-l6S!86S5Yf4FXCdNQ3)l`aP8-?tO~KDu07oJ}KcAyzC_+So(yUR)rAwC(9UTpS ze}7!Ocu|N52?@!NH;#@SJ3ey(OiYr!-W0{f#fCy?EFclN^qm|U3y_*xgezCBAv-6t zF$$+noxK7h)X^xgRa8{SBA%2^#tYyG$Rr6* z7C~b{{%w?$7E82$bTdqvw$N+jS&dOu**Nn-LKa0ofitd?-X5 zIB+0CE^HGM6XOIdSg@cm0>#CpM6^thl~Gz+Dhr@}z7VA)MI@pGvGKP61QXUlUAqf9 zwI6`B6qyo)g(u+rxzqS&$96T9h?9ie>S0s_O3F$^1lVHZQZd72E&z9w z6l|j{^IrfKpkC=m&+72?d{D1xNs%fr=hI}_U@XB$e1JK`8_DT^92BJKrw>DPM0aQh+-%Brd=Q2I1az~aS=g@DS+3aPFR z$jU5+rp5t~W(Z>5UN-pd`)Z_IohiI}C}b?s)9g{NnTnOZ{SXm0oZgLrvdSI@z8e5{ z*WUQ`(lB^>yp9<&x&v^)yxAtm$$k$d#d)w8IsqzG6-B(9+ZWNQ)YQ~)5t}uuh<$u~ zgn$PRtE3ubHEL^t#H3u*R*w>Sn{=TMwFOtG4+5YbhobzkxR^K&jt*}Cm@LwUS{dQ( z{{1=tn260A-h%t=p7`?Z_l|0T)dXm$#5 zrzr8st5Q|t(IX)BW)6T)2o^XT>WSWOuE)C{FkvgKTiXwzAp>D+(+zPkgX#V)crP@? zp1^nDVBZ~w{{23j9A881vEJl}3Fy`HU0lAJ4-1Qdh}1WCa*JL;0<>y1??TNJuypBC z7QlHZu_xF&8qFi>=gSa(;wS)1uJKW|%f*KdAi!w4(ux0d!-DuqB`-NS4sm@U=C7;Yd1XivfcdVVcVv z>PRP;38=2FrXsF`BuV0QT)KQ2JUm=5(87m` zIt+jbd=^;4Vq^#ufBRH;7)mQ9Ed;0^TG7^ODaZ#0TfwCJMA+Eb!fB!dCQP1;&o^uU zHE!%EPFEJekzfJ5ChtN<3E-W8&H**_(GcCD`&@U78aoob`^*Cnfr=spe7rWHRfj;h zx-;34&{bPQU2R1oxafyr{U&R4?Ku&SM10EBNm#br7pJM;sn=?uUxx+AQ4njfwQSYW zxFY5h;{4|*$aeInC=xLTgN6)5-+%f6*oWY~3hdmTOtY>;dg^-(HBi@3>kU|0hB=nwP!Jk>-&^R0{!IcVxzIqm=)WMpH)xtbtq$YF7n*QvM&-6fYcOgI8Cy|6zBE0 zb~WKKk27HzJfEQu7AB`H{f260L^~}DP>UaHd1NpN5e$G&p`wq zCn|)PguFMuaw7z$wWPc|L6^=Q1)s)?KuPcS($O@**Pc93BW7%zY) zlp%-rPR2ECT;~rjPY<}bxg#m91hPH6eGT5eYavtrIWhz*eEs0==8A<&R>{vf66_2^ zAuLSZ?Ti+{xz084L;>IVI)mrl60{%lri~NVUx!}?+r@)x#eEeTtY1e36kXoJ_rc%e zBhxP8U6Eb|MaWPDN8#C}5En6fgD2$CP(avbcPQEnf^16$_JHCw8?dGQ97HR$f?N?p zdnoYP)yN!q=q6uZG?P~883jzOOmJq0XaX9Zu6m8I^aU`4Sc)J z7p>d0Ldv}c%D=_z5+v1VFx$Q_zkt+YeuOj-dGESMxiZR8YO_e zEs+WD8-nfFjukXZ8~A^*8Lm#2P`uCyzhqW7MBsSP?*vR3)DvCb9)fLKx1hh-Yv}#% z5Xhz?^726`3z3cfCN@d{dz(|}i2{xU%!Q)G2axU8AC?KyY`G`*ifb(z%;HrAKK=Y# z$Tqx#0)0j*qVQ}5@W@eMu*n4$veuPm=?dB0#=PBle>3Kr-$lUu6$LD2 zuYta7#k*1NfgqEK%cuRHE5J}lY|t7(T+Hcg>7jA`cB+d2Z8LWe&#jn?oj*N=ohFy1m{uQTL6%W$_cZI*!L z%a`xMU%*<@E<{mydI9vW(68|qF#0b|@284@-d@m$?qk9f!sP!kcufaFe?m7xi>DRP zLNCIE(1W7Vm3|%g)4=o7I)>=M#Ho$wOk>-ph`|3B{sU@g|G2#o1}6Xj002ovPDHLk FV1m)ijs^e# literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/cls.png b/user_config/usr/share/solarfm/fileicons/cls.png new file mode 100644 index 0000000000000000000000000000000000000000..4759ad6f01d3aad46c74f210a7fa461cd01610d9 GIT binary patch literal 1763 zcmaKr2{hDu6vxNVixDS1A%%q3qD>`95oeTj456Wt^q!L4NTWiAu`gj%V~uKL?3HCO zqd|~9mYNy#{0=RowxPwIsf~=|KIQa?)`l~=l+9{h)Y5+Nf-nI5z^Ixn}GN6 zjfbBXcUEST?Jv}|3uZW0EJR21qFqLh5s%{rBchv%07Jf0NUl|=7O}%xV*f) zqN1X*vJw=js;b(oQ&Uq@TU%RKS65$O-_X#|*x1QuWR0A47K0pCD02+aTfk8n*p`oE+VPSw$ zL_|bnWMouSRCIK7OiTT6 zDskY@cT-c-(KSREA&rgQyl|laa|9UI9|lkYTz@k%`O_yb`5Qtv^n5pPC9ZF(ZY>1D zyGIwUX{u8lZf5Ab+f4NQasy_nBf@Z^C>?vyOe*$*BFO}fdt%w9?88G#4booDT;W+2 z;6m-;8Rn!X$Y?E%=+pTmxDsxA0x>W6WD+^>W5*C-wsPrRR-SDmd9)Wj6TctQSi4&4 zhu1by%Q94X(DY05k$A3dc`f=cyJ6sPSjJYNO$rX9`;PLATq;vx4PKOwM-AkoaW>muc2b<8S#FC)q8bP>8LvIUNd{L zM^=B*!!<2>N`}ByZKIJwb;1_N5`#;%uz1)<<#?ELDAKO!ly>I1>U)k;p7B%yI?~7M zUW--PvR%^tQ;o$$BFb3IIkoxp3}b!Zw)u)6D(uuzJ?m`Y3pFdA7$bJ2)8WI5Q}5c| z=zF+JzR)DX?ow=LD^8x*5<%A>q`b=yV_yVRNKvJjnk@R8Ej+3{us!#J4TOC}y|naR zhK(r@Om-yW`8n3ue%EuYEQ7pQ_=$O1GA2zmOaRNjB*Ax9UO`QYRyR=AiM;`L7B^G0 zD1R|_fIA|CP(riGTt-~t!D^T_3Cm$GRHIZc&g66{1j@l|J5+voz1p6=OR&7&ZJx_B zC?@0(PKaq>vZdFO?sKbX1@f~g`W5D?b&*#UZm(O_Fo#K!#ACrimCcyzmUkM4o7k#M z{B7j~YzM9J4Ie7O?5nORz9l`1r7TA?+IML`{?$^lCf`11k*+IrV;S$==|?41G7H~l nNbCtA+uj`A`P&hu1s_X;B7Q5&@=|&6#!srNjeuusS!4eMD2>Cb literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/csv.png b/user_config/usr/share/solarfm/fileicons/csv.png new file mode 100644 index 0000000000000000000000000000000000000000..869e3541b739b1c4380b52eb52e7f7307535d51b GIT binary patch literal 4519 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 FV1ml(uT%g4 literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/deb.png b/user_config/usr/share/solarfm/fileicons/deb.png new file mode 100644 index 0000000000000000000000000000000000000000..e5581ad1f784062671ced5047881499c3bc6c461 GIT binary patch literal 2915 zcmV-p3!LcMPX^bY(7z;HL ziWnh3Fa;rz1!72`Tr3jff)SFU&_s#_q_9Mmg)F$c?Ci|+_mSKAZD5t4-4_gH zYenuW`q{S4XfjzX3Mdgc#Ljfmtc!N8n2VlTe5jb2Pr8r$UQ<((R*M2^@O~UfrF--( z;Ps_n=iLv$+SHDCp&yiqR{$G56qR9v02~r>_#jlLj{~Nup)tAek#AtcI#6FM0!nJBVUhID?naHxg30a!NEOw4)Tw`0?r%niFYZ5p^&Y?r7ei);Yzme6lYtE#1gxn6 znP~v_<^WHeMd$5f0B3sui#9^hhWrbEhT`USNI!cw8efvRpO%U?k8;f`q zPf*)s`KPyI@t3cKJx59h_4e7Qf9Dp)hS!HYN^s4{O3uCxEc|E-bB~-B58SRE%ssG< z!O!mn<14c(AjE%bQht2WAOS{Vo(+uk?zX;kb(*Q;eA!w)}$c3Om3s{{u z88WXv427q+gSD~UgIC^y+;?9FW!wnRXHJMtC{R$1M@w8IML$LM4F0b;-S<9({4aJQ z+K#uNM-4&av87-U{F+?=Jvq^?-#UY3_B_sv6*rcsg!BpvU?|g#5cMz(mR@roQ>T5R z^n!Eb6cm5ISEePwrCzlHZEbC_?e?38!QOdLh+ur}1~6az1}H=7;?_`wM~Q0_?S=;C zqf2Q(Z-Vu?Q$#F5;y{u2tG7Tdms0{)UxJXKaq}X1572J@G@8%c2`r^u5-LJq9y(}| zx~IB!rrO*(L2Ei{`t7JL^OP=J_4ME#%nBVgwRNdM+R zVC0#Ys(AkJVjhs?5S&t~hU%-KtAm&vCu)rH#1kPpuh3 z0mf-SuboUB2N}5yKkNo&LX!uVnCGQ23F|~y%43n`AXfzC>Jgw_H?bl!t91=1A#&zX zXHc3NK!Q$?orO6(aUDF_jgkNupGwdeT6t2ri{+k+Xf`OL8Zr7p5<^xlLFOdW`&War zvRPw8FRRahd$d!)A*l~`_W_*mD-F!OmbK%b3bE__l}jGiQ=}$0%A^1V zK)i32W z_dx1PE#CT)yp_+bLS87T%%feR+MB&OWI-xZ4 zD3u>W*9s9hl~PV3asebT`i{(*Bp8WuR8k>YNnSS*52Gk7bN@|@6Ex0a3R~U^RjH$% z<5Hd~(kH}uqQQ@=;TM`%O>Q(U4b*3hht#q;US)zPGf(WJRcAT4C7nqrXtX*X*xxrm z^$M7#smOF%LIP%ga2qIB4MqFtX((*{JH}Kl5-Sl=XvIB~sWu9dN{1U%CaYASv9VDN z(P`=63$X6_dl_ew{ZG8Y$gL(qC6O;BKq&Ct0c=E(n|P6nF007`*=$xzm_;!|#)4+d z{^SmDyFSK?-&}`0O_ZO<&2)63%T^61`Cc7>&%Eo39U62U8^fgb@(@z_wo;@vQ=Tn) zlv%L=vX9-y9HIlg%h!T)-rlbH0?k$FMwkhDX7T;K^-!# zI2F2ARY-q*9yDx!n3r4N;`cW~vF#9Gu5j4^B^T@v%EJ4w;WID#q|>HQ!f~9M6i^fD zn{qPmwUg!bmWj~#mmj0K^m^?7;qTz%ML$D=RxIx0))oKjc^iIm*jOS!a^ZPG&)0!v zS^28D5Vt_w=3J<76)k2NI?H_P^I+aJ53PMiSbo#rKq1bfO&ZO#PmKkl({eOL&(nxq zDNlHTdx3c|F0Df$JijL4p{J+kY%L0?rR2Lxz7n)?%3v;>E>85z=3=w2l+c&U?*3k& zc+5q495`^KrX8BNO}FxgF_uopW$s|=Sf2Kv4}(Athw#n?cU?a7 z3Iwo`$Li?lINaOYTc{-;sDO~XM5HhzF2$vNiTlBeM(ubA+e*Hkqs2eh}hAKtZVS6fR< z%lVqB(|&oq0v__lTeRjAg;ThXw`M)z3G=pve9C6#SF86t@AnVv9y0*+{+tc>F1j2YALfAQ}&{{^bX&waX9Fy;UN N002ovPDHLkV1mTxdba=o literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/djvu.png b/user_config/usr/share/solarfm/fileicons/djvu.png new file mode 100644 index 0000000000000000000000000000000000000000..f3ed05deba51cc39dea3fb3c8ae7284c946c3bfc GIT binary patch literal 4973 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 literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/dmg.png b/user_config/usr/share/solarfm/fileicons/dmg.png new file mode 100644 index 0000000000000000000000000000000000000000..b52c92cda4e20ea86d849748b2010cfa3cdd950f GIT binary patch literal 2523 zcmV<12_*K3P)%|1976&iCJu48x#WnY9L~ z$x-I$=5jf5I-N8$H1r#6>rS3Ld4RLdg79;nv| z%aWB>WpF*w=Xba$*?B&)BfR^3K5_riqepb=)Tx7q4-C115yn_p3C-!fB*hN0|NtjWa3v>u3Tx1 zg_>OhWA@Rk3UZ!ev6xn` z1B)6jB!X-uCt$#nauhYqD8o#D0d6J3BxF$+PFO6g`57)Uc|6vNR$}89z6+}5$)$#h znBl{FMHW~sJEfCxI9a4bDo*pW zabdjA=ckW9{u`dL(cs_!Aft+?FLZ#%XjluG>8;?{W~bj()4tUM#mn|j_w^~#p@lNTj-59wt)AO^!V{(TEB5UuG=8$CaI&NLuext zM%B`zVF#+$0wqC)A}bWh<#Ip*#OaGK?$8GxoL}_)kN!M_cO*J==m_=o^$W}=;yD@{ z8>gPG9xzTp7|t+cuBh%AO%ZboGhi5{>*;hFP|@iJd)}e9-yVelfB-PA8>IydGY-pz z`T2QpeU8?yZKH?d4@uW_a=JVuV@w{y9J-pWtLLuUf$GlrypG{jnhJv@pRd8Jq#cqI+>~?Fpff3~k`SLt+g~gWJ zI9F5y&vsp~OiN2p2r;qJvO1W5N`@<-MFAt2gTgdqK4-`6R48g-F^+0!EH{chpD)y` zwWi5wPPy5QOKQ?8F#hSMH}Ss>M#5%9mlr6S0%-9cV5KO~Hi*37cpMeLi}BCrvc*m+ z-ue8-O;Bi|JddrSR(a0l)PQ9rOUmlh7K;kqzduTeL;|aSi9G-%G6ibHOv+qumDuSL z^oooqLq&U6ai62BQNZh?$FUeC)7f%u)m}R^>_Bx|c{!L!#L0ow|L~pf3neMDNJFPr z(TKRP6<+IF00(wPFN$|Kx||6`mqc-AGFjTRc{6?QyWar`==eV+3cCh`q=p@M=EyUL zR7Im(w{KA(5EKRpr6QH+Haam*iTNa@vKg=rj7712cH_FJ6^mqu>>iBejPiX+@LGr0_|=>js`4-DMT=d;w^(?edrmxljy4;aSc#p9#yH+Le+ zX~A4v+$yre-C4yy1tl-^R3PB5n(Mj1OZ8?zUswaYv92);jn=oXr`fqU8W%hQv#6R1 zxL6?*rYH%yuybcOZQs6)QY`jRs9ccT?6_}m<;UYG+OS~*sVI4*6yLaspk|B|RW4XM@1dBPB%Smp#M|4;c3RQvb$qVTa z4EWH6ErQJyIK&WI4sF)0MxQN8NMJS?4wC_1pPZaPHuALP_17snIZcV=9L>!o02N-} zYT}(Dn8<-f=de4;j=B0}Cn5YL&}h)D$SpkEpkI z7X^bJNQr6SK8mOYY17v`sjJHcgOCZeAd1xE^ij5up*wf}O-bmU*pnxsxVT=XXOn`7 zT3bVvV5zFQ>}Bh+)o7JlqJTr8Af!Zy)~smYIHu1VF99RcDj4_9)0-XB`DD~ zC`dcWXjV4aj%6aUGV?k8{jYx^54x>vlK~$GDPSVMKSX`s`ZgfuBbU7t-R)i7fRept z)|*-bCN5PN@(Fa%j&Hn4H_)IqZrm&~;c(gEfD>?NrxY65y?gh`hhnv1)y1-T7VCm` zIQzAZUJ6HUAq(v^8Ff=U@rb^;yQ}#%u-L}%h{dAN_iF{yOiqr0T@vK?g~2|I|9N!b zETFLtk++Kv08DspmzP{%tI>xeG(CBXEcSIEgie~BnZ&(aukQDOg;476e+vcqFHm5f z-hKB+#L|bAC#y>mze<(Xw0*mKMAxo#aE_|Zx+e+R{4r_(luB9J+_9DV`upqp*0kab z@C07q`u+ER-RvJQ>-tb;d6|s(f8XSDHVS3ANER($n3 z5Q#)q-GP_cu#|T)pz{`voA_gKxv0M?EE(nC^3R!LOWC=U11wH%V*lqVCqXGYR2K^w l<;brV?MUT)(fR);zyRw|0z75LrJ?`;002ovPDHLkV1h<+y`caA literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/doc.png b/user_config/usr/share/solarfm/fileicons/doc.png new file mode 100644 index 0000000000000000000000000000000000000000..8f615d16fb5abf9dd468f56c691403c97ed3e2de GIT binary patch literal 1536 zcmW;LdpOez7zgl;IhovY6i3Cna5@r+BE}+S3Kc51O75jR$+}D`%}(V$k)95+sU5dm z#!$HoD|5fMxy>zRZe!aIqc-Q~e4h7xzt8*k`@FH|T^y7Yv=kr^h>|15&K1m~TP-gO zPTvuD1q1?RpLcS%2iKE6Z&ASor*G37BI=wTwOxoAycj!qDeTSV1fECIoM-Z)Cy^ig zmLD1#dJhy&#eG^DXJlk#lJl~PTi+Gd zkw_#mr7$Pw{rjS73aGr9Qq@nXoz5+-$(Xctw2T4pLcfT~W>XHXYV-7Tu=5!Qlw0eYX1-Y>4NuW#rV zHhc#f8yguc(N_>l&;%L~HV*pwxie*M}STPcx9q*CeT=B8nvdmnSMk#AQ)Ip;Fy9OQWF6O)>ax7f=Uh@Yv^?Y~k!w}W)ha$V{lDc0uRfIAw zm(<;#7G{`DL2YmBwlWXh*Dn*R$zULSKAYawLFDcKB4wlGB@)97|xGMM*19^cufJFlB^= z1cWWGR^n`D5i0XnIGK|sJLHbVqnDyChnV>X=zqcLMaZMHW@Fs_jI`qka;$-{s_Xg` z)V^We)P_kL{m&Zl8<|{cPfrh(s*5IDPTd`hP=?1k$Rb57rG%dLz5~<7bgpT=W3F2) zhseHh!mmBZ!QKeA{qLw}8n}|IHQ`l<%m?PM^c|Yj9YH28Z#kVR{>%*++zm0c_MkF4 zOM*pet>1rSsjgNlqf^YYRJC!dw73~&jt&v3^+wnrNmJ}CAa@V(F z;L|C{rm1qzPkMJa-*T+Uary4jdfW9pSl;g42#l+B#z}&=?tsx$CN!gmcJ$E;!TPcG zR}sAdkz-#=iuumY6gNi;GTQt%jQaVj%)EmAI}rz+ALOwgDHy*i`Tb16Mzwo$pb6kL zzhW_OC@+(dt+E||)Y9s4@crv|BZFFIewqh#&6EL{MD%-QB7#0fhZ;ZG92(LxP+=W8 zi;Fy{I`Yw*=rOS^wZ-(Lj=ZKu(8o*jYK)^U=oDR59t`4I=k4ih9sC$>MpL0%npj&~XGd7Pz48ejLWW0U zbspv)AwA!$b4X8^$zdzZc)p`s`5U)WcU;0Y{-&6H&SO{0S`u`dS_o7u-99d7C~F`S zA@+Ku9(i_#5LhHNijUV$&Sf3POYr^`v!zZ>9tPPkoM11b<0PZV+l(^&JXa37rvSSQ zq}d@S%~fi&aprsi(*V&ISL`WNFNuoO&7<^zByBc1fH&Ipe*7O4OO{H;J-s0?Op7O IZGG|o0m92xYybcN literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/docx.png b/user_config/usr/share/solarfm/fileicons/docx.png new file mode 100644 index 0000000000000000000000000000000000000000..377ecc729ed4659b00b5635b7d3b2041496f0086 GIT binary patch literal 4144 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 uUnHL)01JBKLp!*bvAd$Oy&&0000D literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/dwf.png b/user_config/usr/share/solarfm/fileicons/dwf.png new file mode 100644 index 0000000000000000000000000000000000000000..349610c2718c00cc023ea2f02b30b941d7cbac9a GIT binary patch literal 4319 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 literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/eps.png b/user_config/usr/share/solarfm/fileicons/eps.png new file mode 100644 index 0000000000000000000000000000000000000000..10f19ebf2fa2fc9f09132fa5b405c46f511386f5 GIT binary patch literal 2299 zcmVwKOMeT0VrcK(!sqV;VzKWFf-s0<#dI@4sJQZbt9gEvWm#B% z{P^S94?cMB{Q2|Sy~mF?55OA}6X5fD-9Nr`>0MEj&JiGl1#~ICUnp+G1k=VQSGG#f ztu5{Xs-i%lP=KvuGT+kL`ul~s`L}u^k;K^ZU#=>f^y&~eMFF10`wrYt)hVomE_v;3 zcqF)eQ~kkkXD|ecqH^0jU8M)23CJHj-LrfxX&}(u3(hodf|PC0AIit$z^5{iua^xrfk9C5OkbSit|r}QcdRZK?$yI zp6=ztL-^85--FkGb_MeJJZvTsFm&n^fswbiw*L6x!op9-#>Q5^ap3|?&CHlvAawvO zsu$dvMI}KeIG4-m6u{?^7+*<%+`bG2Sp{#a$i@eG1@>|>yC=asEh4yG4%o-}OzzMU z04g00_V?jXmYJWC6_`+xB>MgS?>-ZYiIbC)6Mug9FRPKBh&ceY3dcyB(e76G(RWTT zq0j_yVR!E%TWd@ojrtqOg({r&1}IlpYy@RuXTgJ!v#yLS@_ML9x!l%P+=%t zlmJelr-Dh65C{auhet+)nd#|?h53bt!z05L@;Ov(L$00Dy*4HQqUeAC3beSAgWp|S zhnYKRaJfY2YiFDZFVW%McnP-LYP zp{Q#hF2;sqzg${ciei+4csyPXKyTVI!S!Rc2&7PuA)hNiI-O^ZO#Sx9X_&da10JMP zht~xTj0jEt!Q)9#$uiD!|~5RY_J_)hCfF5FMcrq7tVIU`c@u3TFtRKimNU?}MFAcA&rqFeyW4@>BU``y_14e z7JULkrGSeB)Nmfxbp=}Bg?JD{BbFaGlPnU^en$Fm>vB!G+-`_OB9U>e`fpm354 zolIZ&0qo?8Qb{c>m61nh0TJoqZ1u1Lge6I`q>c8q!cx$SD6OSoXD<)GK*_^a6&?xY zker=AdE@e>?r>y4Rn=P5WuaK1l<7&RzLNti&s3w1S#`?*U=pQ7`gc-@ojBdyfym1j zUwtJQ>>5xFkzBA4P+yanj!_tw(@C+uB79nbs3;{g+U!?TXj@xbw!OVw@p{{AIa=+E zV70bZ;f=p@)O`XHgu2!e-Kf^*Ny==Eks@XX9y9vX4Zx@gs~75fGMR)MH*Nry$1Ikc z30FXDHfseXD8sH1iW4^+`D&= zmCRN&sdjE|iUsWG!=LdQhWh}uXf#TJ0Pf$vZwaDSK*lIkw*|ORW|4%1+u7M++|OpS z?5GIrROe(?I5;?f<>h4<7#Lt6t3{$}p;S5w|Ml|(>d~V|u)4a+1oQcP(ACw&o>2rM zAiKM}%okFr6zuKo!I?8>s8j}HVyxAn>$ZRy0QNZqBh1Fe1{GS+-`~%K=Vx59h@#c{ zm`7N_M%KA|_byvMG&BUkV2}Z+6~vL8*vNI#=p5GYBn)YkA`TaxI^FBXtSk=%*jfTI zJ3EU7VY=?9)U+^4TJ7uWW1QCe2mhkSd)bEq1VS1o$0jN*E-tdUPuz*kHlfIJ`t<2i z#+9{CBvwH9JPGXg`@xOp0J0$MGdw(8@`-8=eIyDQEy0DNNJELsO8aIY2ml@vVQp=V zMJJx@tjdfdml9S{NS}FI@EpMDR1FA$ASWj9q(pjIF}Lnji6vRpx(Q-@0LdQW~00WdmWtw1Po(Qnt+ z*UOHWl#Qu=BD&RkY;D?srq;s_#r$wM%>09MpZsHRaIh}H8@50#8nXL3L5)CAqwGtR zI(FpkWWxGO7s~W9c6QkH0hI!Kz8%wxK#X69Yp+Zv^AYVwepxU z_1Uv$S^d)p_nU5kM)n~Z9kn@*!vFt;RkrHJ z76@Xhk74ZY$L{_#0AQrb;k!S|X0x|(xLX$hY*eVw|5N@(Yp5nP`dcZ9J;A@I{}(1P Vec_sJe0cx>002ovPDHLkV1iOFU`7A{ literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/epub.png b/user_config/usr/share/solarfm/fileicons/epub.png new file mode 100644 index 0000000000000000000000000000000000000000..6f8a256bc99893e9eccde6f58786e967bc803289 GIT binary patch literal 2772 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 literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/f.png b/user_config/usr/share/solarfm/fileicons/f.png new file mode 100644 index 0000000000000000000000000000000000000000..7cfb90a65a905147f8120d2d18deacd9905dcf00 GIT binary patch literal 2050 zcmV+d2>thoP)0JX^WhsIbT1{ z_nr57-{<}NiVhUlxpU_rFE0;fXJ@mEi;E8lf?$CpUyLyjMG+Pj7U(_meSLkGnwy*d z$j!|a$H&Lx-cP6e(%sz+O-)VUa5%DSYHAMY_4<5zXDUE=JRWelTwt@=-aUEpDPMFre1K=ksas67WOQ0;J$F7!2U|`w76VhK7c3jvYI8`1tYT7s|@Y z`~w36>Hv^t2*6rg^q#b|G)PZRhy48f&HMK4J56&P9UT`+N=p2s%(wxp^+gsY5L&Gk z3JVL%5X9FAf}u4om`o;dZfYEe;VglTU*=3^73-AzrTO| z0wBuLhXBxrz$if+J$m#m{<&ieG>HHZ!tKq- zEZicJg_H?Ts>#S_67m6_d?;cBHcety7MJ#9vhd+bo6zsCHH?OF47v7|l3b`Yl;iaW zAfW$o94Y}r-` zRn@z}U^GGB;~se7e9nPvsi*{tEgOFLu9HVAD~@Mo+~D(iM6u~T1@jk zuaC#=`T041-&R=-PY~FRYhC<%UVb6$tgeCEe_Rg^je&T`g`?ae2$~Rzq>w_9_-Ztn zpt9->c-s3AdhXvvnpxrf4;!JpVh6nS_B-(C-}~V8dSLgS2Hr;YBJC^|8|bkHy7T96 zFpu3ATiPGf%f0%=VX*>Y1(V4gmC2n+M`vf0VW5hiG{&d-!46htAVGm(?oux_u|Q3a2B{^nXI4wF^~`k(MTARU+7x^WeO8KAJ(j(Ot& zS>`H*-MOe!UFN@&B-qF>}>3#*!O_LKhPyoK6Q!ymZ3N>s{R3B&_7T z5>UERBsz~7S)GFaXiWUUC?S0iaj zwi*+IxXi~0R?)2RXChHZRTK){K#(_2)p4&7^xo7^h^64HJwid36bgl!kgb9E%33Jy zJP^)oD=a|uzQ}kFn=INa_YtC8PDjcEs>7(%$k;aJhHb7D9umFyW08+lsCNjQ34!`d z%tB(ZawCe>pMKs7A{_w&3r{n)5=tQRDr_VuC9(B52_Jfl?7H4ES?wMyLXQthwt6H7YYgrR0|)o4}@mD zc!dug6LsqA>v_c*jo)g8Uo{HFB7CH|jEoG(#E3%9E73;{-xF#n)lf+q6K#}-U-1YZ z3-_Z^C|)asdnS{|bC!_sA#=j2Q7GI|cB}~{^qJMF$VhuGm*7nP;ZG2x1{+{%YAP~- z($Z2G92|s+iHXUco}NyR$CJmF-Vl%WV%2Oc4vVsDMn^~g!r>zSm1uf8GJukj5;%D9 zAec<1alG1aL;ZR zEo5PIWdJfTF*7YOGA%JSR53F;H8MIfGb=DKIxsN9beeqt001R)MObugZ)9m^c`amN zbY%cCFflVNF)}SNHdHY)Iy5;tGB_(RFgh?Wr>vZW0000PbVXQnQ*UN;cVTj606}DL gVr3vnZDD6+Qe|Oed2z{QJOBUy07*qoM6N<$g5A2Fn*aa+ literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/f77.png b/user_config/usr/share/solarfm/fileicons/f77.png new file mode 100644 index 0000000000000000000000000000000000000000..752fa8cbd9366a8247699e3608f124e485aa82c8 GIT binary patch literal 2187 zcmV;62z2*}P)9snL(;b5N)SFT)v%*;$!SXfBS&CNZ*aa;;ScnE?3JkP`O@-nTnG%zr5qoJYU zenv(HKRG!W@_ZWkkFKsRsIRXFtJRuXUS57etJP-Fn$ZB^bUMLqw}a7WtUZ7J{8vp) zP3=2&?BEep_y9&nM*+1CE|*J%gTsSva}b3Q7Z(R^x0?X$tE#H{?#!7pC(oWed##|L zz&$iH6dnN53<21RLF>fC#6WCpEM#S6?W(D%xk&d~TU)Q?<>k3anIQw%>OmGJ5Nfp= zva_=b5X83#LO^R=i;s`z7Z(?|HvrN&fgk|rZ;+Fdv+Kx_BNy?W(A?a7-E21VgM)+H z7XVR~4gx?2fsunaefspzCXOiE^cAXB&QgpJV6;7omeVo3c)EhJBPVogRqvnd~7$%i6_XVM5}Wqxf=K@#3yY2*9*t%gxQjv>{)Vv_TdKF$g?}J#3*^5{feEL~EBr3&*K^C=x{qDe+Yo z9}gvE@4)lEr_j^=7-?pJ4?nI0b5Sw8`+hY%`?nibSDmo`Kox5v`;c}iDMrv@4fN>G z-(d;6FJW!Us8Yz=)a1Y#*uAF|cIMShekP9M6ArFViRv>L@B3p0D3C#2Ur?9-VB$=I@9ASbQ>-_ZeEVRgs zR3>`_;}}pimD5|1$i#_A;Dp2^%mF&qgiu}}$8^luy`FbC?5ttY>kXj6=7pk-O6}eM zYVm#YBb8KTMj;Bxor#>xh;@5WaTz@8`I~8-I80g{8ti9zK-?~QaJLhI#X)wi3G>Di zNYBiK@zG&)Xb0rx?_z*%|8^5>lmB5Oqi3oY1R=6f$oFNUgw1A!sn<5Rcc+7yn}ije zR|HCDibPj2Bdb(u7Vqh~o7aEz>_m{p@%-@KZRwr3IGtY#h2#TDXa!iG7$mK5%vY7P zBw0=1y|~P-5v;6PVb3U0D6A;tI{_z69s1x&qn1ux?Bzpr3R{H^+IFvI9)!Eqz{r&xr zk&ywVrKLdMxn^c&*oVBz%1Rg;8-uyIIhdWDh1%L$FqurupEjC*Q`KlN!i(4RIjEqZ z01h8M%ru;zpAQog6VTq?PW$~(R8$1?q2ku9Tg*aAG*SW)5`~l|-)MpE?rx^_Lx&DA z0Q7mtY&HXJi_^0M0|QW8Tnx#{$&60H3Lmr&_-4IegqNSo%gdprriN{sTdh_`qhrU8 zF^Um|=w5Yob-;B(5q`rc6pZlVbv*XOi5NlXOxo@3?PWYqOG{(UOuq{Y3$YJQQPjS_ z>rJ(k!BU9?BF)lb;#U@n#ln0+{J)~2f^C%tqwQe0ACN+!T7gDFLW0+OdcEH3J>q?! zFd4)>Zo??#Z!0_4gyQ?3Kc2tQTlkrCy#yDe4}TmdHrT*|gdzZn9K*xIFf}zb-P6<4 z=5#tUg|!tzF@3O_4aQ+ncGdX!_+NOr!oCt=E>Q%KmzM{}j~^#ToW#+L6Kc1^jx}a& z7drnkWpj#T^6@{}j{^j?{T-?R001R)MObuWa%Ew3Wi4c3bY%cCFflVNF)}SNHdHY) zIyEvnGBYbMFgh?W!*rT`0000bbVXQnZEs|0W_c}SVRU5xGB7bSEip1JF*Z~&GdeUm zIx#sbFfckWFppG_(*OVf8FWQhbW?9;ba!ELWdK2BZ(?O2No`?gWm08fWO;GPWjp`? N002ovPDHLkV1hia_QwDK literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/f90.png b/user_config/usr/share/solarfm/fileicons/f90.png new file mode 100644 index 0000000000000000000000000000000000000000..32c9febbdc66e639396db28d461b65598c369148 GIT binary patch literal 2212 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 literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/flv.png b/user_config/usr/share/solarfm/fileicons/flv.png new file mode 100644 index 0000000000000000000000000000000000000000..c4e35d5ad096994829a6356a7f962e8ff2dc2f62 GIT binary patch literal 3181 zcmV-z43hJSP)(L z9+w+|fq``K;ze4#c=4~6E?t`6-rnAg>sp1~l^^IFf9rHQvfJ&>>-hEK5fTz&Jbd`@ z_q%rO`VQAI568rb6Ul0|(!+-jy(_@an|QJ*ARr(NYpNT3-I0`(WaaAxO-)UUySuwB z?hkwNo;Q!eNP4}Vf`Wov6u_|AY(}%$oER4u_s^P|nz|u*fGxcyA|hgLMn*;fpSNoT z$j{G*Cn9jo$OG5lDOd*r!I}nxK?GqT4hK2@F!elbZ^XhtQYb)jC z(~FHmIvP9lLGwGI2JM~Jw4sT=O(qVa%D?P3$?YiiEua~BO~P=1c6St z4<-8b6QwxZC_X;k#T!uS$&)9dnDTPKjiib}Mu;pwP zi^T>(l&jUEu;!L6Tj=@o=QLu(2;zBESXd~zF>BT=TE2Wa?c2AH-n@B3yLayvr4Af8 zKzR3_J$pnEtugRCgx7jS4H$vLl;+KwCrYeax9;a%U0n@Oe4wF&6(BTEmId68va&K_ zi_r1o$BA7`9IKL42nXDsFku2+zI<7fKq#C$caGSba!Y}|&x?tPnYU!gl0Tk0b!s19Djp~YL>L8c+PG9! zLF~f)2&uHRH0ON8EnW_R;RVD!g8@ZHM^i^f$6J=hT3X>15+Qt2Q&UBGH5gjq{n5OB z(trx61z}Zp58xVp*Q{AX3l=O8p;Q=zhK7ouc7?EClci`x-a$;ru7Sy@Tb zrcIM)6)L$a;2u8O_}A73ZMY4Z1DJ%$>yAP=GBYzJG!QCkZgzIIgv{N$cO|rT?ASpo zR;-}X(o*7LB%V_1X%)a4TB~aXRvytR=L^jVU{~om9Q099Q7#@(12Cy#SA_+ad=XsD zg|(GuFfTYb_^lVg`iOOR%eyP76?!o1ivs24tfpAE#( zy4(|~a}NXUT*P#Ec(@CV@RZE%AvUN2oP;RBI9_?0z%gA9tOuR&rhrxv2L(SKE%H84! zQJ-?aoA5|VZDDao4z1NN9|E_uGz*=;b9TFp4=C-BwSepX{vDFY2qgv4-Vo>i_5g%Y zRKeR&w4kq6=&7->Q3#yXYLf&CG#OdoPzjf|wst`*C}%VUa={8^UewLW93j=#TS)m# zr#%Vp$?95nsv^_VW2F}_UNX9LQYcqagTcZ0Z{nhsP4tyENWTS5up zf_pGZYHARZ!8G<(1HFFzn(rB@y1GW*3r{GcsPOa|g%scM7B=n+!Tl{QtyETCF0mXN z6C;TUHhca0Rc5YtgePG=&}f7gI8$Na=kgm96Dw8v6)Vp<&osP}#*R&M5nk(oL8*kI z2!jo^A!%!CrHWUtNXO8~uM#cJ7(5VXR>2OWnEjbWK6<> zLc!OrT}xkl@f&`|Lb+4(gknc-AgPToS!l`BH-5RQQ3 z;yZVwU1Kol#W;P$&u?fs;4A0gpknCLap4GHbm^en+^G~A5-M1qot;B-^YbZ~6>>Q2 zv~uN2vFhBpb19k=-pUnwIq%ra=1k6&r;IpV?1eZo=#A3JsHv&#uK~ItL8wn2Pzs{> z=vjDO|GqXNB8ujHGM|+QrpJ#T(V;^Jsn@TUrca+q`7HQ{yf@bC4fKHb&3En;%X4vD zm9w(4oqKT52&pez1OwPO@T8W(4l4>FhINc8C}4KBGR~2Jr%(Nrg}f(~xUspJPM-V= z$Nra8uy7GwxpIYm|JCm#S<}+SvcQq-kr>J}XVc`#lXz|PP%YA)Nlr=-#gJ>Ba`Bz( zKxk+P84Y?7rmqoZrN)MO4v8mXL7)%du%^12{&e&xRWdt0{^w)Ty#OOc#|`LH`Q{BZ zH#br(7cW#~$p=;-F)?9K8uLyHn3y7?@L*9RySjmeH)l>^JpZ+H=#aD)t`SH`Fwo~5 z>(#6b1}gF^CUDQ5JxAM+^75uq6{8TsvyPR5anSidu^dVQ4~a@(rQ%t=mCGcqPkrZ0E=mOh&OQ%Xusrjn8p5q{*z(e$FUl&M23 z<>X{bV+@`|`x0v*$5f|q*kJU3p=75?85z{eRQTTg5~0bZr7!7+A1;W3aj{%!87^VP z@UU>%{QT|j-%IlUgt<=yEB(CmDHFmd%Fmx81?~8W6T*$qqC{as9of8j^YAF(zKCj_ zOqnuSHZHe|ipA|Yxw+CB50HI8Uvmk`o)(@;siQ}UBFKYMMkTX(GEJSDBl9BJ;(NaN zhT1vNK~K=0+`4tE3&$E3DxuvesYX?5>S#ufIC}E%R@L+UI>B%lo>CDRq@-`__YhS zK{D6X_4SOPEGS7I6dRq51RMw#ROBJI7Jb+Y`zbE3z8T<9UnwCpXay9)-zqee1=Ytw z#agvOtM3KgdO$o^r{4cADCDoUl@Q literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/gif.png b/user_config/usr/share/solarfm/fileicons/gif.png new file mode 100644 index 0000000000000000000000000000000000000000..7cd9773bc44f74d9107e73449461b2c6393fdc8e GIT binary patch literal 2802 zcmVBEw#V~C9#lQbvIHq4$a_A4Dj927a0UpdV-$4|%4Hy9h=34K2o8&Y;Am9D2@4vK zKp-q3ftUqCcCv(>5V9a-fg~iIbdx<~A&`Cl&3A6`Rb^6b%`|C+U)8Dmb-MfB^FQa@ z`+fIzY=D0;p8F*+b@%tQ z@5=T6Y3+z7pM3KFot>R+y77pOh4l2-oPb9(Y=pG5v`VZ@QBe`&$B+N$;K747g4G2G zefsoKZFqQi7C>ofDYtIjve+ssE2*fcpuD_XIi8o7M@dNu!i*U+zWLP!goTCW=us0V z?#If?%9PV4lSv7?Cfj;0|QlNbjFSy`&svLvW5Ve#Mx#iaW5vch>$Wmdi1Eu2q2lzMXb{^ z@z4p-O$1081qTOHSXhYA)zw8qLj%H~L4(%q*s;Sp0wNmnCNJZ z967?CJ$pEO_%I&yQA`|fWH+jT<*q z3Dwuv%MH)aeL`C#AUZl)c`LOprBiqJF4;Fsw6(S4^X*FnZGM}8A6?k!GM?{0v1j=^ z!`ZlG5;X-0=!B-UH`hQJ0%=-d?Dg5x1Z+ zemGu^M`Q(Sy$xK<0jQ%mC!YF-Hj0W$)cle%)lJG)kSe%u0WmQ#N`Q2JSBrkLR{MGR zd2|60`_{5^^;lNS8H49`S9Gq`!}5JKn&T$E^)JOEq70Ala&||SvNya0pZHRC##hh@ zO6=XbI*i7fs_#1OT1-kuLF*^A*usH@^bw0#X{cZT&AR9h#bYfo$9KGC* zZ|qG%Qme_h4Z8y~v^74OKTSQvi!Xh_rcJ(FPEEw9Wfe7!ioTSp8dDG-dM!XEa8pwg zHMeW&Xl+6$ssUI3Dn8g|)mcIkYA5?e{Hg@oKDCfaRc*wD`j zk0@Zu)Jd#c`97bm{Ftx4bY)I^LM}2FXdux(MrJK-)Pf0c~w<+}5&?nsgpvr6yp_z9P1VR@s{52^HA#ELb3^c%93-M*;^`NcQUy4A`i3*lU92|;nUNV;?#_bvax{^eH? zT3T9E842RUeG7<>kI&X4BBWlvLS2=C)=pUFkO#W*2PnJHuZ4(OjJO z0zrGn;8%*;$BpgUFE3ES$HP zo-A;h%gZmmguZA<7J4n<)TvYY96;|dH#d*Ww95$I=c@3FyUDw6dhq$cwWhdH%Vv*go$-172AalTK0;GJzd1-~Js%lbG6A^4}*E4X^kE{=@;SW_ET-1WFW|RRuv#Ck(e$b zDLIMG<|-;%VRLXk#x4jtk&4~u-?MJ{O5Pgt0wW%OoT-1Df+07Ty*u1kv+N^u-hAzK zMvWN3sue5fXlW+=co3^sEyrjy(Pb?G+H<+SjH(WG(^rb7$a;4 ztLwL?&xFnvx|5Q4fxMjS2q$At@cyj#NVsrT?E!?2jt&Iz;l2fEn~ZF|vz{j884RSS zr6H7+)>4qyPEv{y7w0+Xr7=^RtU9vm^4aG_Nud#c?fRg0?dx*@e?K2mE+vwXaFOgA zH_%Ti1o5G_0wn4^1?U4vgtTn5QedbhK0cRDP%K&C$mzIvglpHX5qvxtpF@W@a>$$1 zOBdT@|cIkIumowu$4DVJX|9odY_mQahhCMb#SnESkx zh&=O4cBE`%p)mJZU4U$6#4KHf{;dGLlq7m0qQ1TX9eK~}=glMUTUUUr!8~AkeK5lY}>XCVZww72y^DlVfE_OczSwD z-9LN+#3}9PReGYGZ34)-r_}SqTSBswO7wM(9Xp0FZQ3*hiM|}%YG?ls?;Oy67$++$ z3!#7i{s}zaGKG#RWlL%fz-6@xh{l zEkQfdR&7E;LJkiY@u8bf|Cs=tl$4Ze9x&pA#s4dznegA5P0-BYp3VM^O&?c$5MPvU zmSo`3d#&WIzh?b@W9u(tclX885)%H)=E0-;U{=6C0c~-~|Hu}9R{#J207*qoM6N<$ Ef@j`bDgXcg literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/gz.png b/user_config/usr/share/solarfm/fileicons/gz.png new file mode 100644 index 0000000000000000000000000000000000000000..987d4f04e8760b54c580ae29313607fd6a7c6a2e GIT binary patch literal 1480 zcmaJ=c{J2}6eq)sHH__K579#=M0K8eS+YI5vLs6=Tb2xF2Cp$l2xA$Qp_uH)o+LG$ zEMqsun(Z-`v2Pg$!|;Cf{(FDC&pF?F&%Nh<&i#D9_j?nqEsgnk&hfCZvGJRl7}^3J z_VYkE0P8-*{eg|`jIQ;c_C`R+&d%=U=9ZY4czSvYg#QmP7|hSlFYpUuVq(I=!vEGQ zDk>To85tND{D%%Cp-`x#q@<^(Cjd_t34z1mNF*{TDJeWWJS;3MDk>^8G!%_S zlSm|h&dtpYlmRSJQBk0UxVX5Kl$5lzw2X|5tgNh@oSeM8yn=!P&{0WANm*H0MMXtT zO-)xU3M!NJkd(aFil+1c6E)z#bE+sDVp*Vot2&(GiA zKOi6=FfcGEC@45M7!VK<5rIG;kP(11BnpKB^u)x(U@(~2*w_~@UZkX?q^71~vDmDv zEF2D(ot=%x<8yO!^YZcv3JM4W!n=3x3JVL1ii%1~N~)`?Yieq0YisN3>OMAnY;0<5 zYHIrY`7@D7{QC84b8~Y`OG|5OYg=0zaAi_^E2)Fj-rnBP(b3u2+11t6-QC^O)6?7A z+t=4eCX@U7`zifo3WYK-FfcecI6OQ&GBPqcI{N+l_p!0D@$vDAiHXU{$?557DwR6> z6LSFO=jRs|7HBlu;^N}c($ezs^2*A}>gwv++S>a1`o_k_=H}+s*4Fm+_KzPwc6N5? zbo%b@&hGB+-rgRA!Pwv12XJt3aCmsgWHOJAj*gFySuEDc$qDdJ?urOJ0!9Uc+gjTF znmGFz4iG077{bdZAbb9jhK7#5f!$pP4^J=ehv=lFoZMXCVTw!2%F4^>R~ke#+1NM) zO$~3^BPMW}`}c;dCF1AXt_N=sKN1Ly!3BipkwG?X>X)8D(;LK+8%pzSYwL@AbBb{f z@DFoulIu~Eit0i$vYYgUC}d{n+XdTLJ8S5LRCBGrdWShfm(+80vd zj>)<$L2juQR_ihghetm~JvrTEt%65JdPOQ>YM>l>N@2#SF}`xPh~yWDI6;1}geV9q zf+#M9_I|g&&CphtgFiiuIvQS~a0`K)&SaL``+!?D;c}fd0hGRInc}A$nPO*ZRB=f& z7v2ilT93-jTy|02#n2!D<+kbfMMLt#^hF9_@}S94Ra(4y`E!m(n!?Vb(xoa5zJyX! zx5Q+@t6wIO6#>cjp?j-w5-oahFlXLwRo>hjm0~EukPruvdCBgbrY%$roh$cEYkSE! zp320c?DbugAcwUW}g_8&mEcR=cx~aos_w3+;mQ5;Z9x^Kr@z z3e+DOaJ*;|3wrQjntZ;(!=n*=r7Ak| z?KN|&O~xN2N;j8O*Y!!L U><0CYIsDuwQzJ{mDt&j%KUT}bivR!s literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/ico.png b/user_config/usr/share/solarfm/fileicons/ico.png new file mode 100644 index 0000000000000000000000000000000000000000..b33287eccc343f2d5a18a99168d720fdc8f53d08 GIT binary patch literal 3130 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 Ud2z{QJOBUy07*qoM6N<$g2|z+ssI20 literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/indd.png b/user_config/usr/share/solarfm/fileicons/indd.png new file mode 100644 index 0000000000000000000000000000000000000000..24389f07af8ebfa69ccafeff012b2f69f110ddde GIT binary patch literal 4031 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 KMNUMnLSTYH@A~`z literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/jpeg.png b/user_config/usr/share/solarfm/fileicons/jpeg.png new file mode 100644 index 0000000000000000000000000000000000000000..b1ba768a42c2638f35dfc02dcca1f6fb8904e703 GIT binary patch literal 3319 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 AZ2$lO literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/m4a.png b/user_config/usr/share/solarfm/fileicons/m4a.png new file mode 100644 index 0000000000000000000000000000000000000000..f8f3ada661ef59a024177b5837d1918727980b98 GIT binary patch literal 3806 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 QJOBUy07*qoM6N<$f;P|n4*&oF literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/midi.png b/user_config/usr/share/solarfm/fileicons/midi.png new file mode 100644 index 0000000000000000000000000000000000000000..85132d5e4f60c2b3600bb70eca188c9a10d85980 GIT binary patch literal 3511 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 Bcz*x@ literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/mov.png b/user_config/usr/share/solarfm/fileicons/mov.png new file mode 100644 index 0000000000000000000000000000000000000000..9799d32eebc606030900a5edd8c0e7500fa19022 GIT binary patch literal 3197 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 literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/mp3.png b/user_config/usr/share/solarfm/fileicons/mp3.png new file mode 100644 index 0000000000000000000000000000000000000000..18394f586bd96471429e54de27c44ed7715d946f GIT binary patch literal 4064 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 N002ovPDHLkV1n3M3Sa;L literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/odp.png b/user_config/usr/share/solarfm/fileicons/odp.png new file mode 100644 index 0000000000000000000000000000000000000000..69f8663bfd82b7035ce89dc78889dbdeffaaa948 GIT binary patch literal 2895 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 BebfK| literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/odt.png b/user_config/usr/share/solarfm/fileicons/odt.png new file mode 100644 index 0000000000000000000000000000000000000000..5e10765a12ad3159d15c061dadf014012635cb00 GIT binary patch literal 3701 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 Tnxaq000000NkvXXu0mjf;O8Ve literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/oga.png b/user_config/usr/share/solarfm/fileicons/oga.png new file mode 100644 index 0000000000000000000000000000000000000000..c2364645338b71577232605d6089e23b002d167f GIT binary patch literal 3163 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 B%B27R literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/ogg.png b/user_config/usr/share/solarfm/fileicons/ogg.png new file mode 100644 index 0000000000000000000000000000000000000000..1f70cb8afefbae212c399919175d53e5ae610b6d GIT binary patch literal 5100 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 D^|m?~ literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/pdf.png b/user_config/usr/share/solarfm/fileicons/pdf.png new file mode 100644 index 0000000000000000000000000000000000000000..867f287ce969701f0798300c6b8dd4b9ce40b22a GIT binary patch literal 3408 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 Q(*OVf07*qoM6N<$f?93ac>n+a literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/ppsx.png b/user_config/usr/share/solarfm/fileicons/ppsx.png new file mode 100644 index 0000000000000000000000000000000000000000..a9fd94ba9592f2e3f7ff6f24c8c0fe2f29858e25 GIT binary patch literal 3306 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 literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/ppt.png b/user_config/usr/share/solarfm/fileicons/ppt.png new file mode 100644 index 0000000000000000000000000000000000000000..0cb28ba7e5c1c2af4c1e1aa7ab26bf5e119ed14f GIT binary patch literal 3390 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 Uc5l9zTmS$707*qoM6N<$g235swEzGB literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/pptx.png b/user_config/usr/share/solarfm/fileicons/pptx.png new file mode 100644 index 0000000000000000000000000000000000000000..d1d7785b2f129395d65296322160c19fcc9f06a1 GIT binary patch literal 3942 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 A&Hw-a literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/psd.png b/user_config/usr/share/solarfm/fileicons/psd.png new file mode 100644 index 0000000000000000000000000000000000000000..568684bc7285fac4a7d61a47217e90dbff09f733 GIT binary patch literal 3898 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 literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/pub.png b/user_config/usr/share/solarfm/fileicons/pub.png new file mode 100644 index 0000000000000000000000000000000000000000..ff2707658c5f7c6e0643e24e1040b3418aa7bbe0 GIT binary patch literal 3822 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 literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/py.png b/user_config/usr/share/solarfm/fileicons/py.png new file mode 100644 index 0000000000000000000000000000000000000000..4fadf04f68fd4128bacb7e69d2b0ef2a86c824df GIT binary patch literal 2431 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 literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/ra.png b/user_config/usr/share/solarfm/fileicons/ra.png new file mode 100644 index 0000000000000000000000000000000000000000..0103372e5ae5bb9d106ce7380c1c80b72727aae2 GIT binary patch literal 3141 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 literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/ram.png b/user_config/usr/share/solarfm/fileicons/ram.png new file mode 100644 index 0000000000000000000000000000000000000000..ffdfa05932dd18ddc6a3d516ea5a5ac1e0212a1f GIT binary patch literal 3269 zcmV;$3_A0PP)B3Ir2ef^BT<8872mJmcAC{^>c_{wHI5#vX$s zwNkHqegEbD=brEE_YUws-V~W$+`D%V$B!S!v17-qDwQgq9wn$H-^!7yu1G#XV9fU6#l=M|&Tcz^BMwb=tGEiF}^IC0{&>gwtj z2}mpzz?6mx>A4{j0htL|3n|~u0^#*~QB+g}tyViz`iRA1kR<7ok&%&~)13R4FJH!y zBS+>1pkA_MiAtx_l@pLMmXO?n!63%R#|1O_`T1f|nIT?`q&7{Op->1!N6cq4;Xad5 zrpeFKf#tO|8qLmn&$8DJtzm?Vz&_I7;w>8CK8 z%|dENj~+!uMTJ=K+O=!Ae*HQQA3iMZ!{IPao;-XU^b*4?e));GlRmHa3R$-+x~?AK#xlcMcaWTo7}ao=lSg$jqk#nZ^(a zk9c}EKi~2;SwL-PXD42M`Q^+SAhij?ys#`SCPMZo7T{~v3`>a3rmwFL!^6X{TCKQp z<%*E{z`y{UPA4v1x`Z`r*5LN-+rnI2E|<_?s_D(tfV@U;Z?6y(ugjg*e3tqzHf`E; ztf{Ffo)G|loEqLzL*8efeJ0F->=JElZGwTTSFfU~stQY&E)@*#*s()^;_&BoyT$w5 z++5LzhK67=nFK?r4A0a6TLIH#9#YN9;c%3bNvTN_838EyBU#FFXKC{@3ZZ!Z`R4^O zW`s@Q;>C*s2uqk9%x1F*d*{$sS67Eqr%qx0`t`yW_&IA>rpq+bnGZi_`V2ZRFHelb zIDea9%Sacdli#vg$PUDkkVhuU#P;mjBc7c-dlp}R{WU073ytu5c5Vj5l5S{dz{ZUm zg(>mK8e%`3kqn1L@w+G9gCD($VYn)1pv)eLXA|iwL0y4<5wFAAc+) zzjyCmy!6sbVoq&stpLFO!1I~Lp+kpIQ&S@}#F2_Mz@3{sPD?~-B2uR0m=*vtMT{$D z0CL;Ee}B@nIFvOvHw&UHNtqG0|JK%4F^|9J#rN&om;7ecs#W57aM244Q$5)z_Hr20z5e>^PL3#>hVk-1hVrV znwTYqEyQrKNsVHo8fG0ONaCQ>qC;7}A-T??Xdn}au0ALJJ?McuB*E^DV!#)NBceno zo`4BK6l)?V)Je!8<}}1OgCI-@#1)wEtI+M$pc9lZDHNBPRH)1$V0Ir$a#VO~St*P< z^(<{XG=Nl9^7TKj_v3Qc2>c2S`X&HJT#Jy(0ISlA?^t|TUZg>Bu93`was`=-a*7FN zC`P8mTJcBX7?}v*?l^GQXGE`0he{*mkx?H8#wPH>W=e=!)ie+f4L~NqCUCiP5bvDp z0!(=rifiE0F7i|4Y_yMx4Rkg8l_JVB1Vq|k&RhYxoij6;gX zu(`1qYa=0C8H?eTpK2hb2|eAzFdO>ugVs7IvQ1?3O<*Zb1R^-`$(I=PQIye;)N=}; zFz2Be53svjLZ!77>SUcm&Mhf%{;N^+jf}(Lp*=w)1}oY4g)bk#HtNQcRR!44R6?M0 zQIr#edDxHhZVM863*PInlPOsR;6-s_l}d@P?vLX0?lI`Bv?J5!L78hofhvqGW~tj%mcmfbbH4xPRObS%#>~fylTU5-nm6Yw(d>gJ3K{W~39E(2)ACe)Rxu z+O>rUY%CnYn#Kwgll1vLSE8xiXwYL^(%|@=IPQ#u5Oz6HtqWl5u9?79XrH`*u>4_jN72}Y5+ zna5;7*X@bo&3^@PzSoDKV-$HlJC1CxKuuYpsI?ZY7UEV@X~l2%Z@_DRy@qT3-H1oX z(F%)jDq=#XJAqxbQEXbG$4~dR;^>}D$(+F8RqE->u!CN-h46s&D6U7NiSd$wWPbiL4T@yHY%LDt63knw{u7WTQZEe8HhNV!Z*#4qu zAhi}^kycffoHTj6dJ-;KDt(Fq!hW6MI&fv3p}Pii;+r)IztcS=B-__X9aOW^CKA0xgf%;9lPl zu67Ngqjwbjt|7Q$8pL!a#0Zcs&w_1Ldgyf)yhY~nUZ)#_HV1UxVLV-yz=7}7qq%Mg zIlop!yG#dPi&l+JBT6G5K&@F}#>%E@{Kz+fQ74(jm=6wL810{rQ7I9D%U_0HUvS}z zzHvmxM$n}7QpH(9xuF*Md3m#TW$8}v7F`1~H#ur5@CphFL|^_GMVA2w{&=PblnwAs zhlX};BPfcwv9m#sA3n801O%>w5|i9#q`mk)ki`!fe0BQLJ#_awpw5dy8w;Sd%77O) z*Pvy26^uq>(p(nhSY@F*vbj#v_`i*6p!fc93(>#?mX+!8|#FX0>+!r``MIPhEx8tW?g3_okBr@LNM6Pn8(;*ta_ZP4F*A~P4|zH7(2$-QvOnbBO5&Bg~FshFEDL(g1D9HD3U9bl$o zW{UaQhR_A&flLlP*LMqjm&tq|3IP8s#EXSOp*a6ow-`Tk&vo#d=A9}W4#yMXm{M9e z0RH7M5D3KD+uKiSwc4AB1r#aS0GjU|pX-zGZZQvYeVdIg6O*-0 zRUqIgDm?Y65~@Ti^`Yd27HD0H2oUUq*s+rs?{U1%c)ZPS&vx(h{|_E(GVxkFD91X! z%iMd;|NZ~>FZUb;a@vOhydhfmW zo}uZLCX;E`GAna0rlzJqS_e&_q^6*bwd1~al}e@1>-7q=*{pCl9L*@b-sBSz)#V59<5djolb|+($bom(vBt9?nE&PFX%5&zjIYR{iP|G}k8moAX-3Ucg63qUrZvGAua%dX1s%5wvBZw@I;>Ndj^j#q!u1VO6Mv!7DN{`_2U%pD9>Y>s97=5zQhLuY#D}KHH3(F6?#JnRtt*IciE5BTn0L&fdpWoi0|}Aq(L;gg2=r&m?{nA zH&1{SpKpa+Yvi8)e-?zIREi;NCL4@p5?t;DBmy%CEgA6LU%p2?*#L!X zf-22G)vP|&q4;A$FaWDNipGvOnx8%lv%_|u_BRrE&rYm1P|{J8bD1Qlse4BfK!jqP zbrh21MIvLvLZBUHeGT-AG?MWoWTm_&wn9*Vf(umoBx;W&aPSEgiYQ6x3+zZllhB#< z4>7#<*W{Tw*iE>0jLYh0p zd`npZ&8H;Pwb@~D7STNezWI6_ZQ5GYA3pFvRLTz^djb%W0vKri77;r|oH+-~UORM4 z9Cakpo(0LU1}n)ZR^p`f5KTbTG3X627nmt*s!>`IL5*PyC97t*>W;%do5rVK^}<}N zfUCm3ULWOJVP7qfl2cTxWMDt6gs*%Sli?{C!UeESh9JdKq-`q5pb|-5CeB<=S2o+~aEz}A%tVJpm*p;wUxKZS(KxkkB*ZZ&H ziAE!yKH3T$IdCiQX8xEnf43UV?{gOZjco=l0fTEbk-T_|-v z=p$wn%u|w@v*Y&nD%hxWn4L+Huc+Zxr|?WmIpv50i0ah%`06#BYjdIbVBaLj!G_0|@|-7b7F>BWD)oI#UDi;h#J6pGCPK)&Yp zHId0wl5sg=u_(di`M{tKQb-Gh%1929fjX&!ZlV@S7nRcm3y?IcBIi~+tyqdlaN3;+ zmU!^-*gU+QUOZD}LH!{cU@fF?fd!PgOCDY#XLBYeUOaQY$eST*POi6ve_yJb%K4vbthq^fr9e6^17z6_PYIe@s_zqi-x68wO9cf5IClIfS|4k|^71k! z#;4HRGX{T;A5pI#X{u#pNha>6s1Bkd(I=T!;83Lz>4+82c9?PeOc{(t#pt^e#f^R+ zj&&SDTYIzkz}blZd$Pc04x}N+bGb^$4_r9f-b&%|aSD~)^TZ; zH^62`qro6LTc-7~!+7pjAxz$zAutmd9Pnd0rpKxCO~m&`XvvB@5q@7Nv>OoFqf)67 z->}@9TMm-LM@7BQew2+aTWNgH6xxgM`2sN*i=}YG5BQxNcf7p57y9f}u-yosp9l6F zM)NuOFofSq;BCw4ToZ3r&SrU;|J!DK-RSvzAIN`ZGZyU1iF04%^~+Q$C05Y_fq+JjWAgYr<^PsWe7^%K zF*Y`aTeoiE;>C;j=%bI&-rf$U(~0ZXuZs*?R#vvYIQz;gub``|O9;sKc=wT?si~>K zlTSX0+qZ9{v$GQ~yzl}gF%Lff{Br?{+o3~;aOB95^{RBcJfPfZ2z59dRP!u>Hyc=E zyjnayJ}wqy*)l9ZB9RaR_xAR#U$gcb8yj)pzyYxX%J(KFCItB5;b9>tuSB=BwD7)w zP}D{x^{zfC^ZCaEaxCYy`@z9Mp*5xygWz4oEPvTjZEbDBQVcfB^PZj_ba!{7wY60M z;#KB~iVE@m>guX^hJRdLqmhO&+Gp8?%fj><5gz{Gdnvgo@46p_zZj}rOlQ;uu+EONm%o2Viv~b zatSN2(;29AV1RsND;+<690LOb!m)XcpXtJ&*k71_9UUDwcI=pNV6WFJXvpKS^Yi$U zB&}OvPoI>zKzLBrJPX1?U$}5#?PErPGiS~SVX_+)4C3XNUl!+eb#(#=ccvka$6H7| z-cwIKCEnq2I0A8FYV9-vZJ3ES$^qGP7RY9^iF4L$_C9Mc|NU&0nR}miU2?D4V)=^V zIW|dhifyCNR!i9%C7P|cx|JffbI|Nud|c~_?EV1fpiM2n^k63=M_Rsf=gwcr(MmFF zGwfu^dndo`^gIWQa%N_xmrfIW%^`PF0KAp?;)^dbM=AICe@_Ey@CY;tAZem*oTC>f z%G~xpMiK-PFU_Zn0000bbVXQnV{&C-bY(4MVRU5xGB7bSEip1JF*Z~&GdeXgIx;gW zFfckWFvE13eEeSaefwW^{L9a%BKPWN%_+AW3auXJt}lVPtu6$z?nM00003&275-*u@e3$=tJ^N~M;Ju@(-89UC@maBtqc8NI!|#RGWz?YAM9%gczMh0$FYAf`g0kaE|q zUB9$gEKk#`R4&GHJ?ERFR4Sp>YM0DOBoZWgj zudhEsK$1B$WFZY`8K6SU78=hqzu%AY@^YwDs%+^anM^_y#d9+=Ge4s_qn9sV#_{9F zmj$3)vu2G#tyb3%kZLBOTrC(3^2G%~;0zET&S*3m<;)OpZW^p-Gbe44NQBSHm|RBF zoDo`^=%PGJCy0I4v|m|;Yu5}Y5yn{U2}+qZ9nfPxV{ zcI+5RN=nev(}SHmccQtu8P~60$IY8Jap1s#oSI;?n3SbVOF=N9Fz`foFFpU!X0yL? z=FFMV&d$z)0OX7*CKU#N^;j&n(AhJS+Qh^Jo_+RNy#D&@=fPF08-m)7b#?0vK*|Ru(`ZSnIH*Va>x4Mpw4nD>} zFggtl4P0ByPjb z#>dAw!)z;@oSZ~^dpn==!3Q6ptE-Ef3VY8$<}(i|^BHE@d%a%2BxaDArkMp4+S%lc zp^O7DNw90CvuD=XI5UeQM~+~0bQD*wUWMIm=MvwxZ5v!J7gzvlY;45X*cb<#O^bC# zhcTW3mkXp+1G&bRXn-MP(${D-Id>tpXj@ww2f);P@Zdq*xN!q!vl(QYsIIQY(W6KC zy~$+aevt8%Y{{{AO9D}j9cj6EvIqckEMi=q0g&C;`&VCmm5;H$Z{I#{E^HxYO6+>* z&>=onT3U*xrY0`^T+++XmySspFP7;=G(8@VNZmFxG-M;mrLxp1qjwfsSeLDtl`NK- z%eG_&GM@&r?+cmQ0sykv)c*bZ-=q7Fa|AIJ?ZLmMG-G$Jlr2D(M1S&iya z?ZP^%qJexM2Jg9Wd(sO}RD|81z(gPgXIzeGDh)j%unKWl)FMiVIe{2wAP5t}DH(zx z1%~_r20%fMVnVfEf!Y!RW)GmEM1gH>mC&h`OSEy{0Hms9_y7F&7%mUaAS4rTF9S_#Edm}auKQ#^_*vq|)aj4;Xd7#^C2K|6-; zZ*PRG*g&@21SZ8`IF3{2u46JlyNp0mFDZk}U_v{_@m!6F+Nw$@7xEl(Zc&a4HyyY) zC`(P*tYmd6wCr$1Zskb}@;*m=V6P178g}QEM$(HVPL7fDstBx#5ceCZ!e{ zElEbD##*@tEma2S^twgrW`KvLTo|@{;PQpwaQk2+hkog)Hat>qAtx2kP-9*=onXFI zAt>>kSQ>p^HDZDex9om=Ph2G*U?d{31k$XKXOgDHn7FMZ$uhGMTC#g-07}xreZx*n zI=p0hRK&+3IQZSiaJajJm}W_ye5sjIuPGIwpluc;9DgLn17e2%g1Jx;`zOi_8qmJL zoiURiL=_rZOU@lv8YAqk5UveR!ygPG5e>s?P@?PGjj&is7oDRmD(LC8%bi9{j6p{4 z+4CIFtKb7{o%QwQsL;va4Tm7c;*e`o7?11Vrdpy(qo$}lx6LY<`B0%VIqilRk8l9% z+Z)jIP$dD?=Jqk-aWa_3CzEli=;9=qFq$e1d{Zc<$(NhJ_T$D{EA~ECjfC5YxX%NT z7O}?!d}0?6Nv6q+)Lat+ssHNDad^C5;v%q)amW<(Svl~xDGhY0I1aZ9sHJT( zNzvG1)Z&@V&17y=C=n04JKH#;kQVcpOX!BY3B2>K2ri5S5OF$S3fOUc&swajw(wkQ z)p8-$*40+wH;1<1jlX<>FUE$Dij$*R%5gTX$ABk|{S66hU8BLV7q{b;7q%|M1O{G_ zRp~JYy=aT#_g{H&W5SP^a~f-t4*ayM2Hl&QAd_Uj51frkk4ZZouE+0wtj3=|?Zd}6 zhT$i3QkPfYTF`_+2c;Co2wKVnG?xmfq&&xp5`aFxaIlikhO(EJeO65C%D4M=k#Cepgv8vNoVBX;(W;G-`_$)tub>(nDkDMnCl zzz8|(d6K%IRzXhJh7u9KeO!xDy$aW6V)#>^7oXkraFeM?x$wfb40!JGc38{jcBz%t z#YN>p+4Vq4i2+?(I?(yZI^4ZCg{y;;=pAuj%smBnQb1CzN0I=kO=fh}X`oS?@h+Lm zhXWo=+MH1Pr}0#E8i$`~LVM#Ha()%x?eZObty(s^h$vl+Mk6+M7_gzW9zO^K;c$^z z%m&~L#L)BQETs~0xI@+W)n{&ebNgR^?b_r4?DB(=_(qMmf-jfA2>k z97J2S20!es$J1L{DVUYb(U^x>?6JxNXQL}Q24v5Fy>K0_nJFCYZo}byooH#SWoP(B zOMN9jS>2Zj74nCaD58oA^mks{haH>S`H6G7!0}-rsedCyeYQlh7iE3_lbh#iVab_M zA;}iw0}qtUEf=A)7ve|gS-%71nw$OIO85U2$ANqfUFf@&j^#7o`vSmznVJ77H5Cel zuo^#f7drUOF{X&cVyQGgrW98WV0Lzv{pgnL>FGJGQmL+`SCFI>18BK1lDv$B*Uxw; z^ldS^lodJen`mh)KGgny`>$*N0eAi`0{L~Spa1{>C3HntbYpU5VRU6JWMOn=05UK! zGA%GMEif}wF)}(cIXW{kD=;uRFfd4rwj2Ne03~!qSafY~WNBu3Eo5PIWdJfTF)}SM zF)c7NR53C-GBP?bF)J`IIxsL@p85X(000?uMObuGZ)S9NVRB^vL1b@YWgtmyVP|Dh XWnpA_ami&o00000NkvXXu0mjfRF>CW literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/rpm.png b/user_config/usr/share/solarfm/fileicons/rpm.png new file mode 100644 index 0000000000000000000000000000000000000000..5f3d6220d0b82f9c52aafb50c96360cfbfa1eb34 GIT binary patch literal 3278 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 M07*qoM6N<$g2U|!I{*Lx literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/rtf.png b/user_config/usr/share/solarfm/fileicons/rtf.png new file mode 100644 index 0000000000000000000000000000000000000000..c26ede1c70e15fc8dc60cd4eabd5df8e4147ebfb GIT binary patch literal 2400 zcmV-m37__fP)FMq59o@2Ji}Ab<;TE0j&6_vo^5x5B@#4j+WRf4|%$ZXQzKH9N$wF`m zh{>~(l4liisfa4vo}Qke$B!R>edo@dzgMqbUG$@(1R5I~&C{n(&BBEXXDnK@=<}7W zt<`>Up-!X`qeT3eV^c!<`t|E0ot>RC@7=p+M6=}y?A^N;6<3P7zTCBISA#st-@bkO zi|Xp?R6YPC26{7n`gAjY{(P(YxCFdZ%8nCj^s?D($@L2arcRw|YHMp7bGh8lQ>IK= zE{}59{-sNoQdh2AF*(ryS_qpk=FUPXS&(}*e-;%JG<){!c|XSU5^FGDM*9UcB56pZ z#QyO!Pv5OtwaN$~JIU3nSFJ>6&YTHZ)7{-|GMS87vSf*Q`}VDQ@#2L+-PQzzkO;%} zis*h@l1NEY3x0$oS#Wf8)OMs;S662`Iyy{cWu<9oXfSBw{{8!=udmNsym-;Pd-u+c z;r{O3yM?jXY(a7fq#soS~Guyu9dae!jQQ7@hek-Wr7$Lj0rZwoL+B3!I$H-Y9t^SV8*X z^4Godox}P8LHH5@V~5eO4a;E9clS6dz;Ij^26$Cfl_gOpV2MtzU%$5GxQ7trZ5Ebq z2+1jVH|lZ=4$gNFB!5)OcYZ)?!QY7p6x8mnNY@rslpO*bNuvc%CP!nGB3uc(BsLlq zEhOox6$D}PjLb_Cq|KNy!#QRzyq(vL=0NB{nWHmzA zg$zzyCB!I00E@(Hs%Hjazd+QHk zCbMSEvRZKEu$_Z2Jy9eIN}PMffq_CXHUT0LAk|K7{C5Rkcu}4KntQG(=YUxiKnR%T zhG32;HZN;qZkSzCCoU`iq36$^Ta^P)O9~c71ANbOvNaAm;0isSA^p7b6}5XB-{SZr zL@Hf+;zU-AfKCJ;z}jdeM6lm6S2E+Ekisu)F(=PuFKdAV3<&~U%@IK`CtxuR%#nBW zu%=>{)8$gRhP}Li7#5)F8$=}RAMivaH94YI+J!;|#~k<#q|q8hAkTakX2a`_3nYm( z@b8g`?MTdmbz4Z@tMo}+>IVSio-5-3jW%qu6GFk~T+!CJLx9O-;&DFU2D1oiN8%(a z03hkKOH`iA6})SbaHk*u=`^fgZV*RHKGRtDwL0+fuJf`I3p1Vup}nRfe~4}(rJawyyk|WtpgU2 zfCU(4#`l;UfuOgy*CsOv^M3a1*@aV&aAKQO7}bIf*#M|oqz0I`;=F`Kz|#P%p%qX_ z8gt@3*KpI^5zY^Zj#bJMaFY)?3HG~Ch{|<;jL9HPt#vdKb#--d!Z%Ksw=TA`s^ z?-QF)-c^UF*LS3__o6XbNN@~eD^}TNoTeogK6MfY_LbDugaq`w^JARz1fv?7)S?e?^al?f zm{X@ty^_FiXzSLkzaKw-eBi)=17%GuQZ1AZACXLw0N|B?O8(KKNBpVsTI!?k+uGWG zJ#peh-{HfD%Yu)T$W;1CHs+<~#e7twkYMK~X45C&f7-lx^U-6+j`bcrdUQhYWeI2< zA0?f~C7_!e0t2VpVm4BJ_aE7?VZ)LA`}g-;yLK%S__734LfStj0iByORRKRJdxBrq zuV4S;?%lh)IsNcroMx9L5G9o-B?0&|XU@En>u>AUt@}=#_JtuAw4N4 z@_^C*DFP>p%*Xt`vv~mLWK32%*F62~*|UZ}w+n~EaWvqh=?NK67UFMC{x?WDhqm{; zcXkVK&s|kB5TYisS4Ys5R{5;lTfkCg=lF(`a0MFI54;%v3VKQXHgqQB=`+vs%>Taq z17t~#a5V7Lj{pDwC3HntbYpU5VRU6JWMOn=05UK!Gc7PREipD!F)%tZHaapkD=;xS zFfg05yM+J%03~!qSafY~WNBu3Eo5PIWdJfTF*7YNH7zkUR4_L>H8eUhFe@-IIxsLp SsBY;10000vgkaGI1j4T%Z4nSzNZ0=23_6KG4Tr1YWT zp;Cg>zR^Abt)O{HDg}@#1zrM$NNq}kQWC(l2{G7>Vmq;8uj5^N@Au2u{{OMp9IyA- z>tLi->S#x2&di*d|NHMV3;d54L2cmr_3Jo({5XyrIa00FYD=jrOJ#mM*XE}n2=Y@o zuAGx=bIoKji^jM_qfz&U4I8{$wroLPUtjS6UV7;zXf&EKTCkkgogW~gB9Vx0=gyr+ z%gf6jqOMFK!V0~s&C%&}Fquq?=A=?7fdG8u_xqnSo6R>iZ{A!yfU2r0&B>D|UubA( zI7mR!1te5X5->5K{EfL!lBD^z8kk%O27_=qozUy``P@f3o#wUPo0^*XInB9o@!~}s zK74pt0J=46)@Tg|LoETRVGb24OjIBckT)_KjVLQCLo60UGMR+kZkOjU0ZgF7;b4gt zT!+7570Ric5;*TIiehhNW#uab;@LfW_S|e~X~DH?*Omsr3y=u2@pxQbB%gzGs)S?5 zj>!a)G_Za9cC@#*ek4VYAur;fEjM-FM%W-x*|~br@tJY4JO+%aSQ6De0y6 zKTJ%xo_*tuH*R!wbu9@%L)@3S!T`7?5{U(s`xpRm1qTlv#GylnaQ5t3ghC;VkB_6j zzh9nn?%X+)mX^xIsI9{Ql&Yv~&l5z_BbpvAEp@y~Qg7n*pLymPbqSEzlsi7=t2FG)Ac9CBSNg%i8$jFGSqWmpa+T&rWzu2~I+mY7R z){H6u{#fbW3IX7;a5#(;Cr-fY_2SV-A4OAB6WCjvK7AUOE?ttYR4b3iQ%fqPBT1+O;E!@#Wj!oZxStln{{8#${`>FC^I38o9UbyUZ@&2^ zdU|?fTTtVEPCb;w=ZNKgi^Z}iW{_LW=A^H>CUY!jK+Fk#Dr>Hf=hW5J;pwNJ#)S(P zWRa8SLT6_u#>U3b-Q8XAdu8P{nD89u3)z5D0)@sGNq`YD_gMwhybw!?31tqm>h$*Z zVqjnZn>KC2-o1NewP|i{mWf=7TP(FgDw)O z^_1*cnBa{YH_9%Rzh8EhK44HxJ;fn1?uBSY`)VCltgkM zWACLVf&BJTsx}W`baXVcZ{NOGsr|N+z_|dYCm;MPN;7uli?!JNN7=Sot+Eu=ROe2l zp_^Oi)?A%0GXd2UzG-je11Q|$LLYk`K@1gQ66FR7B}AM^gfj@jglI-UIHJW+ zP((i{;89Gdu~2XCAa_nuTfkaoI*qpaWf2fGJ3?d zCRA&JxZe>%yHiAk%}i>cCyr}nRpY`0rAf6|Dv@LcQ{gDC1%PWIGe$xN)R{FfxtWPu7l6290J#NN1upiFAqGZ`9ToeFaV0gldKmxGqOfZ^= zGkOEoYJ6y^w!&;NFAz5a9-5rNu*-*;Km=}Y05&q{Cm(FXJq_h#q#~MX9rL#nY+IG0 z4&O>-aoKM`LNsH*6~uR>Rk8tIB9=%YOWevbr)e{0cLi~>%wdB`asHLhy|nS=VGqXL zeo{R;#3y3d|LsjU(9=mo8)(kr13&lFmP*i)n*}i^7)!_jF-QNBxlj@Q6J=H-$S=so zn9C31S|jbHS#W@t)n_JxE5o-D3`dZPM^RzbVfQ!Jp}f2l%6qg02YTwB@n(^b5)i1L z-)CuF1smXf+8dmxGz;)YBajkFXiOQ5CC%{CS)$r#pr}0SW_7GSE*Hjcdm$xbGJy7u zCak}!ih!C5+ZfR}9?s&O@g$|_N#aZt>np9YQz)irmYcx-cwJoudNz?YO^uMS;#>GGfmml?R#NhaO^i7OG(9&m4;4hO#nDt2<=nzpyZZb{L zxZGyK!&{n3-RPi1H0|LFpt@UZpfd)%m0kw{748fj~n)o3y1ftMPp65oNKLG zF2tp=t{T7o!B)KRr;qXR=nyhVGPH6hPA4ts_hqrKDTQroj5zc}H=ccb+k8x5@Y=kS z9vkR|i8y}usUM%;3L@dTjkRevezv<7J)74IK zZ}s~yKH-5OcpDGaWO3mB_2^i)hKye?dpos@uT{%N7c5FozGk_%(~1qP4fuX24EGGF z#dHXsPy(kunWj`C32&qZ$3F1l)0+V#r>D@W4^qZiNwJ{`rFQ$GQ<-w#uT28E_gq^? z2_9d$b8Wwyyvr>Qet&it6b<{#HFH0puQo`yahMR&ak2X{20tGym(vw2=!)VRK)Yn5V+WhuhvH=;U0InZ0@ zt|Ao;qpik>ANDlhp{=bH%*tlhn1fmDwaQn{MhmIbQb7KE{xi%>P2#6LZ8-2q7h2ZU z@g08ATrbpKRTWyw9x{@IYC535{M;khv86-4aaOxJS&r*p%b`A>Bl(+h-M4qk>{(cG zXS5V&i?M;FiUunz`HSXnBwwND{SQ!RZvJ;G_5Ul518N4n)ORZ#Q&ZnN0>J;Y@MiIN zJi~wTVHN)9R(n?&(9J50000bbVXQnV{&C-bY(4MVRU5x zGB7bREif@HFf&v!GCDIkIx{jWFfckWFi4EH8~^|SC3HntbZu{BX=ZsXWMOn=05UK! zGA%GMEif}wF)}(bGCDOiD=;uRFfc8f(-r^#02y>eSaefwW^{L9a%BKPWN%_+AW3au aXJt}lVPtu6$z?nM0000L`_kbnpXE_FIM(gWTl7@in%Jgx7O0uHA}2zpn<7rUd1;liHanm z0`VFkgyAtT4;Z+6|1dau8DK_Q_pJ5vKj!@B?8mqF{?7Ta=^}PC`Kd_1=8<2 z3kwSjR^$0Bsp)jOlD4+COdA`Ua=M0CEC$U7i9~|d)>f$1YVf>{j*hUmx5tnnLwX%g zY-}PLJeURxrOOUuN}Ai++uKDXTosMmNVnhfBXh^7{!8V#JCoh1r|VtGqT%Op}Pj-DSi zbv#jjcpdD6jltL^%Y*O_J3Bi*CXo{RluG405{dp}$fAjw|*6XB`%vvk)H?+#pY^rucK9K5M9Z{lh45-!YhtJ)jqu zV~hkAuK5{5jFekM*AiP>TUMgRTm?kN6yW=ZtqnW|3(N{|o`3K;)q!YE;Zz4+qO-y* z5A^no1s{m;+?O#_S(VYcZi3QEg%?eWRWqPDD zNda=DMQyQ~0TXJ=;%S%?*7;q@t&5518{fmL*W zNh+1@G)VzAf;ln3!Z|rPp`^4NDM<;qnRglQf3yt|;nUFA*x1uAARs`%y&f`*eTEZH zrBdlQ1ICKcgIS7&=RoB1 zro?2dd1)2aMh7Ei!3;bxc>)#(J%W@?@la{n1gLO7eHP-w!NCEC4j;j$*Iq_ZW-8)e z{Rh%AGKIeUJKM(`1$u=}Z$V7>L~&6O*1Yxx($c=i&i7Yi-mJet)1ZXrZareA`oL9r z3!iNNT(G*R`+XkYk%Yhg`|tSV(@*hyWC%9JzKEnLWAV+V4^eQZ2;E%FVo*I`s2oEe z>%@PsHf|j>?P}~^zZ#Jt0nj!z^B~YQ-G!F2(7L75aO-3m(lZZ3O4L}7`38!M_u}HP zXvuT%nZFq4F5kd7dm9XISE06E(G&0{E5I-6)1I*u%a%X<(@*&L^MAqJ%LDJt4}$;L zQ6#Vmlr)LbswpCsXeR(TIN0NZg@O3)%dN<}af>|R00}9On4FAY8Y`@&6~*~iQRCr` z#G94SjhTqY$N38BmK|@h0*pU>aP~&Vq|zZsPx}F1?D`sBW5?hn!nwcCNSd%Kv%o@x z4oxc!pkv%ob=BfAUr(&?AC9d_@#wUa;H%xc(U_Bi_d`N3cK?sCOZglnC(q(g#a+Dd z;RZ+vUiwUBav?LcwBeZ*b8?d6yS=H{xoa23`%T1}XGUX^rz^B1JoJ-wlG(MYd&Z-a ziaawv#2*#=&tmzim9VU;#@3lLAtEKp%F19_T8cB*^YO;sy$JAo0*Z!)etDUwuR_)v zi)762*trASw||DEix(n@1ee)b3VEW(QXHgY`A%J1*L8Q(u{U*{m^*bGt{*>wx26Q+ z5vs;_uU$iRRh6(bn;ajHn7MONS6@FMIgNUN(wwCsbo8_!>7$R3`t3K^oSc9K^XH+e zrWPmnr(zZ<$N5~_-n}|x=ycp){jDWpw3AXBzsth<1v}alxzGw!u>-gOZG5%G#vQLdG6oc6j^t9o)Hn6I(VX zKt}Absj2C{Y}H2Y_oHBT@STE_mkUV&^b|WD$KKW&SMQW#*O7c|oD&6U-W62eyonlu z&K`O{V(V5+BW~2t&6P}9i}cHdSP3U5N20wlVwWl*FSAz+C@(EY+M%J}ramQFY?l#Yn?_akjjp_qifLs@3Gt1DiklqsXJRisRR zl*O!VmF{4Mn5;fx_z1`evl?{^+IbC22M--Mc@oBt^`bsvnyf<*MvOLuN6p7Sv*kE? zz7!j&Sc%V{Lm3gi>Y5sSYb}A5pFcdhVcFjb&#X#J9uOH?VC2Y=$h&fp1lLl~cEy2} z#111}hhg5F2w|6@rMZa;Fp)HSTPj3#xRPIv#M$%VQdoex{CreWu}Y^14_q}J8e4a? zsI|nAj9b}Ye)D2LKM2Rc2ugMJiY8$P!P?r2;8aZ&`D^_8YYys3Sg|E5B&H>zjj$lu zus#VZy~pB7DrU+{mrzE`N2 z@X$~SRSFC74pOS1SdMz)YX^Qhj`5Fr!Pmzd0fGM5vgH$u*NL#y+Y`Ej2T?-8=Te|v zoiG}^Uzv@cbIZ{=#1+mI)LK&3T=?`0A^iYsb*a<|mSPENYwtoPa~AI1Tt>jt#}^9O z2>AHA<7UBi>`BjnxKxhh$eF+|$M6Rcv{EWo86$=u@tp-|R%)^5iUJAovBWgGl7z7s zcvuMCN1<(L4eRXejG8}dXgzC9P_d;BI)S%Xi7oN;)L_U)x*_*?77`;OV3%_VRfUD9 zAl#=CRF=FR1wSu0q-0#jjM>rfavueC7uO#Mgv_eMB10N2qD)rMEA?-&BLQCXa%*NRZ*?9BKI7GX9;wdW$R2ME#ZII&( zSy)cgIc`}9Dog8-dR2kHuUtx0qi3-`&}L!Q=B}X|nL>;jH3|s{iO9{(#aSX_r%#`P zgQF8XMvumXRvA`MJalGepp-mNNQEuk+Z(abVUXHeW5eFx5IQ3YkNS8K=V>y!TO% zqqb6x9n^PB?8|szZY1(cYOp`A0WYjrBG7EkI)m9n{iRW3nTb77Sy_RC8wGgUZvrsh z8_HK>aKJ^1n$R#*l~iEU#w2`k>>7eXBk{yzz5;D9=gW_|h0LP2-8lVHkF(O{+^ZNq z)CuFqxZ&*2hp;{KBxErwv1{#GsFf|)f8YR7>}N11avDLW+2rpG16h%YO&QEk35LSO zix-G#*5KphEjW==fQ8FeVL^11@cBqWYlv4~StDc{zNb*^Sw{?JGkJTRh2Vo1ASaF= zLt|YPo(Y?cq^+OA+sjj+)p{>AlhqBkB+d1Jxmn1<*d8h0TTu*5pB{J)i2=v%jnzT@Qg2d@@BWrX@;jQdGuK>HR8&D$XdTGX^Dk=rk0y3xK&d%F ue4ezg;_qy{{quiPfXlX{L?UrB*5SX1iVe3m=8pIP0000Z} z$3H0O*zw~zd3iwYix)2hMMONjyqH*7S=iZCH8dPuT#`~#=gpu0{oA(>A3gxpsB38@ zCMN?KK#-Z8T~=P+4|MkA$y26HTfSn&)oUQIW9QCUbLPyMI~QpCm8(}DKYsk_(e^aMYwNRT&)&In=k@E?z+hxxVF7xfvbq|?W@Kbw zVq#!sW@KSuW@7_-1Q@nx;y@3Aq`R#H(>*VNR|($X_9&@(hNG&VLhH@C2|va+?c zwzYL|a&mTabMy3!h>8M+e?n3cFwl}xQZll#fbIlFcTq`6RZUGzT^-Q-z{moI@QRfy zckbK?jB}|1hv`7)R+a?$1%sT83K)TaiIa<0KtxnjLPAnfN?J}qQCnZ%*2~8)FeE%8 zCJqGR6VtK_D{32?dMAUy^qF(!%$qlF{-V|EwgFSc&i%)ZpFVx|!ljpQ-oAbR>C4xj ze}LdG5d4FJ;P3g-K#LiZyxm;{A8obW1!O<;ba4!kkUV>^G&(quq5a|UFrGB^vhoCT zU?O;ah1u%vvvaTgU5>nacjQ|B3p23|pW_O?wNJlio*Ave=*6IVK)_Q)N6ou%fyxvX zH!k7lleR5tVc=YM$u4M(PQ^l#pe1@HIscr3PE9Fc%~-}`aH=3cw`I!4#)eCN46XcA zOs0s0Z$HVgRW1K`Ptc@O@s)%c7Nfd3NWfb?vC3!>n}UV zD|Z(#mZzAChKJpr7;7pR+53F;ng-)d8T>s4pKiEba=n_mYvLxgMN#)ATuu76ZT7?& zLhBxGjGuH{USD}LSGVLcq1k)~19LZKUYYnmkb$jV?)%G|OA{mu7Cc+E>#NCajQHyR)X~y5RTUYihV_+co<>71Rj{ zPnoVSBK`ES$mOO_=XWq`yhwc#{^FR!0?qTWKOenW=C<5$z1P(V7w-0^m_Gbby12^1 zde@^slh128cYi97JNxqHoT@c+@rH+&0_>5IZszVmvv4FO#mIkLxunV literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/sql.png b/user_config/usr/share/solarfm/fileicons/sql.png new file mode 100644 index 0000000000000000000000000000000000000000..bf6c1ab4427884ea9f426e1c23a05c2323a7b7e4 GIT binary patch literal 1883 zcmZvc2{2sg7RPVv)^=KVV$FgWL1NNG$O}OPV{6l4v?U}WQzXb(!iW${ENyRs!Ptq8 zUC?%-?OR)7*Y%0WrK7E3 z2;R{@E>K!6G8aP8VPG#YJWWF#mkcMU5)uMJhJ}Uw!$}|zK)mSa=y&hlfdH|wv5AR^;AbB`d;lBi?CcyE7#JHH8y_F1 zQmGRY6Vubvb#-;d#>O}t4qOB?Gc&LSXJ=<`Z*Q;=XazdN$HylmBqSvzB_}7Rq@<*! zrDbMjW@Tk%XJ-?MM9?`WCnq;IH!m+QKR>^qprEj@u&AgAZ29BIk0m7~rKP21Wo0B1 zsl2?rqN1X*va+hGs=B(mrlzL0wic{jUtj;})2D`p1~Qr4*x1;rv$L~vb93|a^9u_Li;Ig(OH0ek%PT7@tE;PPYisN4>l+&zo12?k zTU#_5ZF_tB%a<=ZJ3G6(yI;S4{r2tK-rgRaPT$|(KR7sGFc^o2hu^<{KRP-(K0ZD< zIRVE*LWp7mZnLecp@GpqTmS!)vl#`a1Qd*!nHkQ)%F1?2)$FmIi_6QGf4aYV<>B$# z)5p(`1TNP9sHU!W=?4<1qsVS)~iO}jlXuG>-A_*`4VOIk|!Mb)>$qZn#G z5%bJkT{l-ZI!afORkTKpDdfDTf-l8p@MG;734)VGw;sGo~JDY?_%5zSB zU;gx0hdxR@zEz$^8N&41$P;Fgx0O~>J=a8hiCXS1fufwga|Kq7z)G;3SKYj?p_XaE^rA%KW_fELGE z7d}opU9C_1$uJHO5J7aR-K!`axX3C0;!r=?(ej}G#FJUqQ0+XRAvb)v%xLsZjNX-= zWmH^+n=Hxe>wgUm)0NjXPIZtEGjI<`L5S4z1U_~2+-y+Wf5rky-fI} zOP-_~=7nfbRafTD+6G4$8^is6fX8#e#_jl%cac|pO(iah%^W#MORQZYx7ZXKPY>2D zKDQAQlW>CM`S$e@l>r38*;MNM-%sbHN?^VL?@4uDO7WME=Oe1qWv%7`k#n55$O^ht z-osG&P-&S0^V0b`#);QrH4FM&5`v2t zV!kGXlX=X+@%lVb=awAfShzc)ad7LIT!o30Fv7)zTcTU{{f3;yqXO>BTyAy_*TskN zl6b0%(4$@+xkq+rX5UT;NDys3SBbGRPd${(20{nKf4Wyn*d!?6apuT9+;fUNL(@*( zOhH;D=C00Ksk&OETvwBp)meZG&(lw&ByHH7PEO_UzW>n$mF>9Zwkv((axXW1plF>wSnhZ!QEHBZ5=35 zwKVtSoHH!wv#m_FGD5FJvIz)SqYZVv5kxj1AqzOD$dH|g`r&~NlZM9c-sZ{mnso3? zsuV5qLERP4JRw3XroABKeU{$Y0r%qDLP~l}PLqsA{HVu;1h&?>Dah;U1Gg(2@C5ZH z8sPNDq2luSbh~5`25wI|cGQ!{u^@6rpuPHW>PXoN@6gJ`_}cXTwA1F;)X%Rxj;Tgj z0v^vx;vTm9txyvk@;rn9`mpB!{8!C*6nVxV7rE_bFE&OeAJ@$;zB;1a3f;?qU#}aE z`>qh`X>8qorG-$Cs7i>XaNEbet!|Gk1y)4>M}JstOU6c*3g@hkF}jAXn@d9cIFxiW L4K#|>t=|3{RMe3Y literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/sty.png b/user_config/usr/share/solarfm/fileicons/sty.png new file mode 100644 index 0000000000000000000000000000000000000000..5512ae8f3c70d2a0b60631f1a5d994edcbb7a1e3 GIT binary patch literal 1301 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 literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/tex.png b/user_config/usr/share/solarfm/fileicons/tex.png new file mode 100644 index 0000000000000000000000000000000000000000..36bf00e15d5d6e31d1fc36887c9a75c1ff0db538 GIT binary patch literal 2936 zcmV-;3y1WHP) zY(l~_^@ZDM{RiLLqSVg zd}z&@H8bh)E+p870cdM$8@l)Td;z|?-R`&nFy)v=+-W{^b#g?xX9OdJNpiUk6hJiwV3nRv11?_?Od^9#jd@3_cG8>w135IfsuA0 z*3l!xqlF60ZfK=in}v z%b`-KM0dGd;#o~i%?qnnul`*m$}pWqn{QkIhYlV3`r#dOCPJZTDlt9z%9h|O`vo_< z-HubIPVFU2JWG*}L4+8{NPQv{!cIjvYJpQL_7thJKb1g<`LX(CCgt5n+@mV>X+Sk&!VJVIl&M!LTl)sPy6X?c2?f zh!vLwregP_Gd(Ys0q~rBWr-1)Ka%wLe@rEkF-FEtJ`(<3oj6vVvK4r-41jkx@~(*d zX5q&aBS&^KF8Y5UZVOC>6Gzt)Y=vlv5ur^qNTv|C0TuBX@zIB3l^wZM{Pz^4~`;c?ecR$h)*|MV)#O3I)dEXT*K&?fA}(Z$JF<5N;n z5T=mr@!1GOAMu(OkdGE?7&3(nAG{BA-@ZZu?LVli`V5b*{}Gli{~2!gRHJOZ7B;&R z77}pt3Ja&s0?{DGT0Doo>(^V5k$xKrl>{d~F2JQr8Y)(TxX~=b!;g?PC1H47ThQEC z3ymfP2VQ#*24gX@G9IN_oG2|R!n^-Ciru@OPuvHhDKwGJ0Cnj$y!;0(wEDa8leJ15 zJ@P!}%$kY01()#lVZiT`pnS)RC@cLTwr<;xbN{Qv=hdHJ=kpEpCJ$EJy97;5P59i> zICb^FL?S|DOlGVn5X4q;}ehL;9n0TKR*vg-aUd{yLV1a9^fc45ef}94Mmq*Sh}nUJ}+?e0~Zeb z8S>2Y1ZOacO7)vg^K8~DeS;XmD7_)lmSr6P4K3m(3 zaQp3WIqdLL(&~4(=mkNzJ$^BdIX8#p8@j&g5*a%BSms-H;=$kjbaOL4s(gluk+-2t zmcr!@6Lq|pX7-a412?W^5D*1i13mB#co0kq!bu?b*wGgV!(=p32$rJD){Va2-oZ8S zk&H|>IRLS{K9)>~s7P+bg{x6GuY@WsH3B}~Suc$l!2D9FutG=YEp%9^9*{X9lPlq( z*zfguc+&}#6FR*PDamShNK$unbPRHxk77okkqqBLBU_;I#7DG?6126ou%IC8NKsH| z!^wYU3rGLrx(bW#wo%4Shd$j0M;}RzFo7UTs?}*wYc<3G87$YX@vek?fk6B|Fqv~7 zF6|R|EK#_BClpE{UC+n;ey<@^ixtx!2%L#H*R>u@y^PX)hH~mAP~Ba?yg?6x7l!^stnD#fSg&k9Q2ziQQkA09iF7=Xz%xm+ra(L*%7Kd2@;ynzDJLNoI0X!xQN$3JqD zPe@@hnQ>Ry!`SlkRe0#32gsST5F}^q?{}iU?g~;>6vcu*ELyba5d!e*?+k$O15Fw& z#Di(ZR6OxS7+ZgF25H)K()tVW)Rq;vf8`34m6ah=KlnYOwato8K0b-Y#yYgLwu=uS zs~%WMVOD{>+!+O<)dt@h0J&5_NvfOTcqf)Ee-eLwqlgxLFIi%?c;0R6MMGT!YHMq7 z^=dV4a=a%W*Qe{C*XmJHT8f??JL<01W7)k+k!>^;|8m>bhVKkOa*7-(iW(V)4AMMl zn6qgEEDiNIbm%Z2q0YMNn1zLR%}0Rbgp=sQ8M%{yTfe*sYrB;sdL~)z zsfR5V%lH5`Z{GZkuQc&C{zCgD?P{;BhjzM_64!M~R(+hVg;+CW8IeC@Hr2@<*lyp( zxeMp_t;(Dv}sdPMMZ@G z@XkB$T-mT;!-A>ui@Ea(NM4(fpO=S>>}=@M(nwTDNm>t3q{*YCR*ZWVEre7di_Jlj z(O5B(1%^&pw`|$6t-QQ^nZx1mQ3#tfpF|bMFOkb7YIUk75D11%+1bA>C@RXiNujdl z%9ZN++S>ox?e>}*Z1E)Z1!00000Sz&TQk4){R6vm+L5UjK3JC{dvG}1f8!iHco3YsZQC~BuBxgU92^`J6ciQ~79AZO7Z>O4 z?MwV{3=9no4G#~$eEIU# zt5^Sg;_-L_fdH(3-4EQ~y?Y0)v|+;rZEfvMn>HblNF5y=6bgk#qjhz4_4M?>#efd_ z`uYY21{e$mi^Uon8=IM#Sz20xq-FMR=MWIkYBEG)9AQ69m|A2siz`#I|QAkJ#$TU1WJR%|jNPT{dnAq4@I-Sm7FcK0H z5)%`Xl9EzVQc_b>)6&w?)6YEDjeZfiib_gK*lc!bX(@-pxpxnYd*$Wj4<0~=>gw+9 z?g8}m^!E1l_4W1l_XD*-Ed~b%K{>wG0w^yW{a3NZ#>U3S$0sHx-oAZ1IXMX`GBq{z z{{8#u>FEz2KFk0<&V2m%@zcyF!0hbo+}zy!{QSbg!XiMlC=!X5mX?;6mp_00EEbDb zR#rgAo;dv@=x~S>?nrd{w&f(?l2Xz#2swELMI~jF?k)q2q4^=aBgw^!LZ$im{Ya-X znOT3!nO`vdCGJ|_udzY+F9(1#R;qC~10e`*VP}JLKJxey>8QUtPI;x|kFhe=4emA} zTnev(Q$MpCbwpWd1tyicLvsH(PNg|pKboY3lnh2n;$<0zv!l2qeOr8kJ{lQI7J5fT zdJG~tcL(npYfW;hy2K~r=1xYG@ui?*L$&Ynsp>8d^+IcAlCMglxi&#&*oYAEXh)BR`7No9Ls8=EED>*gFfB+l@ynL!nch!C$p@Ctt#;{I6sr8$*#4(8X%;6lby-Yno-U8OZB8LTqA|YvQrzT&1lPcq)oNR%2dz8>V1AZk( zT9`?=Zy?)ToJdbbD`BU1%4(WtGCX}>q!KR1SWt54dX#HShN&T%E>#SNu#@tRvwXB% zp^VJ#D8*c+mX%THMc%F=2{wapE1PRFDR(*SW@8MN#zoXLm}{$@xOmEs;VL8IESgxx z$b=9I_|8^08Qe{@KUOvtz4nI_ma7EmfF(_6N078V7UVXW;W;C^oH z2M-MDWFRm1U`;|HwJLHq*8O*qLCD+Gp{C+otvlAzm*ujY7vj~7SS8jsp7|XyqCD@g z+;h!`$TBf>I5pBsVOOljQHNU2IHUKMxeyJ$PZnfn9g}8jh;jy!Ikk%c&eb#>naV!CV@tG+C}@ zCk7VXR4@9#Z)tG8_VBk#fqUYcRdeT-J1>VL7ah{z$Go<3&275?5Vp2f>}#$%81j5myJi~$n}TY(f1O(;|?MJi3zDu1f{s8slu zNcqdQn+leu@@j8QNJf3}dJ?DDfbUe&> znL+7Ahqums=R4;+%e|J6c}NEi94I<+*hO7DG53 zMkErENgzqm%%ew-ZXY{#Y>=jjlEjhc&}6x<*6Z~)dsWp@onGHcddFtfe-=Qh;>tN6 z6=49OPzb?bkdI-r*?jl(>C^AN`R1D!rl+Ta`GQbo128r=W{SmQ1{DoSC21wKN~>0@ zwRAa0_+B>AXf#@#PNyYf2%%9(b8jgtD|`3o(W5`4bKN{T6z=2?z=b3P3=_kwCF09QF0}Z=X7KDsK?ken^~`B}L)ING9bofgmq{Vv0f3 ztXsG4`1|j_pBIRnk*6w~P4QG=%QFZTzV`e5ASzXF*s$T9lP6E+3_`V#lKEOc6A1PK zXQjfzLfGy0ntVX0o`>?;;vo~26cC(Z`JB9c}YnLs;a7LHg4Sb&Qh|IW&zWR$>&&_{wW15lP6M( z8V{u+Gv}3}#@C#Q&YnG+e2-~mGMQLvXdHj-G2y=$0L3tswa7dDOFlpT1`OSK7Rao z^o=*(_=Fw>lYPumJdg^4t;%}GV{y?IumuGaK@(}VP?0e0_&gf&BjTS%AUF;GQzuj! zJu0djapUS|xPPx3HhTle(wLl_OvXfNdZzhniBi9zer({xi4)!QD3UV(R$lQ0=Wl8Y zU@kQw7NtflOg)Z&29pE5h(&_%xW-_qtc82H2mQT$(3aY;Ve1a4&8y%Hn&4Qz8eO+< zVrF^@Ep1zc^6VYuJE@)|Agh|2n_s5S_9U7=dC36S8VXPOn$JT4A9}A}z{Ha$aC;^Y z33w4DjvEXFtgKwT8`D{__T>#Itz3m~TqU^E86ogK9Ne}ZufAr$;G>3wmMHt3?WBqi_(*aRLyJDXoSO8rpbb+T?GdMMR=>2y&skUF>x2m}I9Q)Y2{-7uM}(b%>PWi>Bg z&{K-fZ^rP{O^X+qILr%)2&GUmeKbnhiR_@(8E~U#28EOz_ib-LFcc90l)Ay*NUbfL z5OS^qnE>#lkw_GlatBtg{SF*$`_S($!{zpVbbtOw=#53FYT6E!p@z5|MVQ`=2}w zAEdZ33f^-tE8kQ+k(y$c>;a_>VC8A~CnhYmEJNR@7G1ZZaC&0AaDW{3;G6rMC=R>O zQlvqX(Su>%zc8@uHB5#)=zZ`XNWn?0ZnmOjg#{P?eI4$nWBB0D*I+V)@%n2A;hdNt zAcbHjq^9pwujJK@ifSpxG3nLf;;oS2xjG(&M6@WNm16CRR@9L)eGl)Wr{^)6ms>E@ zdkvpo`5PK*3-J2S_n^jBg!BJ6hyK3%7@r)6&Sb%vzjxvGgHcfn!r{=I09Fk6T-F1{ z#l@I%dtfULqLG*p4@aolQm&3tjnEXLZSSw3bF9I1m;zm`#n)Fph4cD{*wt#lq3^9F z>@j?D`5Jtb8VyDrWgiQwYnrjLX%lYUnS!y%Of-rJDkU4hMF3cI5OX%*X3PC_9GjaR z=pFFEAC5z%rT$)0L2qtAtnHT=X#X92m#$#zrZ$*EE{s1^;fn$v%=T8q=)IPfwWO2| z6;=moDjg^-F`}}}fUz+@svR~=OpJ@pR8T09iJn^)q9zN&!^0>y`LL!@hcCMQ(8NOM z`>K=V4q{XHAo-|hsSDBAvJy_+1pa!h7nj<=47K;T==LEK{s>9`LchP?19??jKnKF~wKMYSG4BPfC z7 zrW;;L3}g!CqM{-KqJfGs&z2@Nahf{a5~7z)i+D7I=_wbkws+tU|GbHhE_{Y%4m*B) z@BkI85C;42qO3ShbLh~xq8yD4rPP*8;_<*EkxJ|8>XOz^ij<=VQrn00)M~Ysk;TTa zXGb%>{CX6_Ga9t5pt=y)hM;?tYJ?6`t|>hAQ#F1xg3X)l(CK->Vnq{WfNF;s;h-CX z1A~-(iiG0aN+{n|8c1O@m6SV$GP#iN>GcK}DeZ*+pjg9VChvFgnsN=vZA{BZ^lxL!~y7&&>nMgd7V)LqoKV zHDUh-8+?AC-&KXxFYZE=tUK_{1cG51O>3>#`~7`rY-+^R)D%WWo(P_Erb%xUGZ`#r z0O=E?1L8n4o6T_2imKF7iK`tWlxmIGz5f?jvwjoKefV2!-?a+|e)3cC+mVqG0orc2 zCwm`d{`B+I?3Z&EQnElMOU&-H%c3rDiy_B@SiRDOfyXYIP=(gkRe0&t2c)PZI!zAH z<;$0gT9T@4y45qC=k0-vUYXTE@R%1QysoXM^{rmZ+ar2<9>Vzen6RG1;h3{zO!w$Q zKo%N{rck!nVCC2g9`{pJ+Dh=`$uO;?s2134NuH<9l}YjBwLJSbi1e+T=jwquR7zNi zw+ETyOc$=!oL$n};CU5St`l1}>Cf_NC1wdTe@0?%GgXw}&g89m70*JYyeY&*g!g=8 zS+$^7QZ1e-1*da7JxOFs+j#-tGQN;CxMDh;PO%LlCL|#25kB%7!t3=4<(OK$%fPET zo|A(=o3~Q07XmhaCC0*5;r_j|vlCriT>{Lyb?e~s`9!gDyWQyP>qBK_CDyNBk1JQM z2!+ba%duzA9#I!^wR~P_G<9$f^U5o)h&GtJ$^HBH6lM2Fkm7 z{4Uo7-rZY_@(aB)|B4KY3npdHU)eTKmG16toIihF)C#6ldwV-BUc4v(FD6*?qR>)C zdO#}D*4BpIyLXEiVSqb#?tG>yr`L$34At`jz+EM`3_Q5F@iiYgc=cV>#ZJOw+qP{<3$n$z0*kFevN{ju{GmgK#DXYWo1W=AIRhXGQ_xLOo8Toy zAJ%b{bi>)@S21OLC#5T_sudMN-5;{MBRj zkKg!~1~VW|Uh|Rxzv}4dxJUQg$nd8oX`a~NrILv$o$!=|Z+Eb=VG{O$H*qd67Jvdm zoj6MW2LN(HVhPFU{{I5W3V*!=MJQAN001R)MObuWa%Ew3Wi4c3bY%cCFflVNFf}bP zH&ihhgmoH!b-vl5pQ3($>BDj@U!elbp z+S=OsPn|mDd-v|0`O1|m)dj#Wt<-h$ry2laA_vOj@z~ni+Xv2^IpZ4{88KhIdbK(L zswKGEebg?*lLumfL0H)m$BrHIy?y)EeCg7q>ba0Q5Q_zBF#t-A$OprNt&s)6mgqly z`n2!;`}ZZgleQG$3o*)SAPXg$LaxXbP=i3eq5ArITW4qIz}d5BedFWfB~h%t91B#E z#FkT_&E+H|lFMW^8CC$8o}Q+$u`!CpVzSS{jli}72(uI2yLazTH*emIUc7j*d;s}c zS6m?Ns7f2pvxphV{yzFIc!#HY;cL@Ihc`=X?S>;0)YUR8Mt>@ zfLN_oo73s+-@SWx?C#yWx6hqBm%M%Z_S!A5Lh!7>DF~dD3853q6i=|w3H-(uLL;M7 zFc=_<%}z<2*Xy<9vRg&lHk(b7CdU8^&y#`h4242Ays!W0(W67>&!7Jrmp2TyECBgd zV1ZfiW!6>_LW`zZ&^SewmM9vHl3u5m>y!X6noMMXu%X}r>D>mJ_&kSev}7?GS<_+- zyWK8AD}^6VRsg04AkQ2;c<}EhPMiqbxN)O=7s>^)RFi3nC6f4^=1fYV#gJ$|6bjRu zH*Z9U@3(D(M(yHCyl*t7siwvv;Mld;TBDPG>R6xAJba!@ixsZOzxiH14)g!lw)QbQm*6yxp!}|@TM+ZTsZ^2{gF(1ZEe^1t2|Obq zAU3tNzzT+(_6wEac!m~Jw|DQ}@)KFUC8E)cn zI;}1n_$dGzqeyrW_kjN~b%=xDVuZ$~{6e9qP@$9>*{UZW^pa7-B;HmuPKvb6n#2HpY8#Zi^z+`$HA0HJ#+;tx6+0u(7)j-imB%6gZ z*YmAIV^idx53ERb`K3p(TvXJB)DOyVw}Cuv7pxGWKrkd0$hbn z0VA<1vG%4WCqaV?E3_$Pgj&Pe3^|O942#7|>Fm}7fT!E#B&)?l^9w;*2!>$=y||G^ zGDMotX^G2ljWdFPP9oVQkT(*Ect-PCHe&Nw%?M~vYhYnsXtPk^x2Are&;vQUHhJrj zjpJYvzv}u72&2(N?QQK6QM|Dmsl3|d*s`UFG_6X~&rnri# zQ8kU`k9v=X9JLeV7b`51({7`5oD)x`3^1EbgwmKCPAA!rnURmA?9jtyTqvFW{#p7k zG)j$7#Kw+Za=Ba*PwIqLN+n!YB2!Nj5>0E9m(s|zlhau%K)3`-jnLwvB(=_so2ag? zUJ@94jMoD&mKu8d??J?mpSm`8a>}K_!S^(Rfamg~9PO8Efz?JsLp?c1ClIKM6j%(C z!)hTT5|0Z(&d$VrKpORv$7z>@H2C2YO-@cwN81M4w6Rmb4iAGuLyI&wKQBdLcXwCy zntF8%sF_6BGUKdk_KMl3XXd!|0#)jAe$MrOYf}U0HbVdW@h4gehp2Z;H{zpBN(8QI z+dDc@y%GQfXdcweOq8G0DvE*pes?w3YHsw>$oLd$o3L1+)@lZ`n8;ph6Mp~c`M;!? z>-lbz1SwiYdinA-sCK=S9$Ex;Lt_gqfQ3k-6E`v%HD8UvvYPs0+-ya5ZE0)}0$qZ@ z^~ltAl*xl1hUgzpek3a@*S){{K`8a-Uw)PrlFM&1N^GaYPC8AdOi!gXxvs2GIbU%M zrL5Xy9pEZ_gJ+Td9i)`eG{zU$c&K4ZnO{Ym2x*3$*A)7G}#RTI9=8 zsby*MEfxcXQ71Vaw%m33{9n${SCYgEX~~I%<#ak$d~#6Ea|gGzr;BW$+1a@TavR$kX=R>;Uj2-cz94paexsSjhfc=F_lEUj>M z-n@CUEQ!2+{aOUwzJ0qSuz8dV%sTt_?W2~Kma2Sv&{yLj>=s9k9FfwQn;906wa@#U zZTIirPj~Lz5g>c^>=BDp?c+(c9;B=u=%sDc=6YdepIwPRt3G`AP@4X7KT}m?fpWRI z(BhO@h+VsOi3Qa3`AuD@+<@X$2eo_fzLJfZM!DHn_RG63pnA|{=-N9`EoOC7Jvr%WajD9r*n@VKVJ14TrrB}bq*cQWb93|Z^78ZZfpTkWD?L4(LZMKpR2q%OU@%%*T9`~G zi^XEI*{!XuZEbDs?d=^M9UKk^NPx)Ya)AW6Q!15?jEn%s3=IvftgLKoZ0zmrp-`xk zlM@UE1DLqExp{heA`l2aKR+ZAdFj%nfPjF&z`!e4u7reygocKqP$)DS9TpZA5fOpG zU~b*I1(1x3i%U#Q#Nlu$DJiL`sTmm=cs!m!AOLIu<|GoSprD|zu&}78sJOVeq@<*@ zw6v_Oth~IuqN1X*va+hGs=B%wkW*h@|LWDN#>U2`rY1ldolgHBb^k355XIy1Iy*bN zy1L%Jeaq+b1pqDrM&T3T9OUS3&QSzTRSTU%RSU;p&!)5gZe=H@2Qrq7>01M?$pnI8rQ2p;Y2 z;R6Dxse^az{sN-0Pg7S<-@wr5A10>9zCCVce!}9^8Jq9zpve!ZJ1u3jad)%`d;11u zV{(ARf3U-48F(VNZl)gu0#Cx7&-unlh~4toAzwXai1u$1M`XqMPRoQwRFvBw*~?{5 zf&$rT(QUIg5=}AYWHzjR?e9-D={m+TQsB~VvKJP-Fj!kT_MuEZoRDi5`R8yNIqJ=1 zT1BlQT1p%U$`{5o*f-N^=}Tr9cIjoYSw_*?^Pa;FSk>jyi}ZX(^x6QAcp|Ci4NEyJ zbBx?<^%+}ldgPArJhFu7&BA`RE(+HV+$44^v#<#|(#H>3l=Wb%vMCZYyYRm@4 z;}^;aPSjW8?dbxAKc0fc{Ft&FPwJR^&Uo3jd{y-ObL{$9ckGDa=6E@K?0!FSDQOQY z)A&L7SFipop13(`>8{GXpPr_ zyV}Y%Q~FQw5V0BJn?L_qb5PT|hVsjW$+J}V1e~5G)F=7!ug_Q2T%6O|!qUiu=Jv(1 z;Hu{9gtSRC&4pEaax1C!V1u z9d!0N?xXX;FEd92p*U`D3v%b|b9Q=XQc@*m)copiJ;(fc{I$;a(g#OhXs2+^v+a?# zi0+)?juDBP)UbLQRl)Bd@~^;Od0VPHF*%N6d2SiaW~xKD4yHXq*;0* z)6~_HPrmq_MbL_`dr0w@2g26Q#JX>k;=nQn+d-%TiK~BLz`>)fTDSSDnt5dWv8?^sY7rf&=HZS_ zj|4XkX7>~JY4kim2dV3H>N61*yLp*qq$-`JKG*1o_ory6sY;i}`pl3p*1$!lfio~| zy`mU|hLP?O2#5Ma?ai(wPB>Fo{9@vv_0p z_HW0CE~IBf7vJ>udA}P|?M9`il|r2AsI{g;x%WgIC{W8p%MaY9TXIKUVYMyWVyT38 zPJi_h9H%y#D^;gie|et_PPn8T>;**`B2Cdy@M4z&tDb-h(vP@ZJu}1(SIW-LCVOVZ z+@oBtySfC|6KU&aMJLzSzMjquXpE8E=*nE7MsBPK3JMgouyDq{YWgO%Uiic6wgfZQ sf4bShvIQ%B*e|~+4L*-W8+Vm&t8X^s>$LBZp?CfvILyPj)-m|*Uq|hSn*aa+ literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/vob.png b/user_config/usr/share/solarfm/fileicons/vob.png new file mode 100644 index 0000000000000000000000000000000000000000..70cc91d4567dc19a818b5f3f885015c678b670ea GIT binary patch literal 3184 zcmV-$43G1PP)6qjq) zuKg`1C#Nppe&fas8?Il!uE6iM&+P2%k8t(sRU58cx#E8L@@4nT%uEHI{}IyC(n^bn zOVg%Jzs}H=D_6R#UcI{Xs#U9M0W}S9uI8dei`)+%K5RLE{`_qq$Kc$#b06XC*|UGb znKNfT!s*ke^5_h$bC7`Cym?a|KYlEbkff%j(kT7y(W6I# z`)N3h5O`J3nR^XRoH#+^yx$nF-wkm{OfoVu!uRjrUk%4Db?VglJb}iI8@okBL|C9< z8mK@>W$4hM5*ivRckbL#!v+l+BzUkKJ$h6I3>Y9idi0R4UAxM%Wy{nEcus!&@khO` zZ{NP+?d@%Q9`c8uyq5%cY)J5v=NK7&CkZ+sA~rUzI~O+PM>G z-MY0KSX*e2W0jdRXO6UN*^+u!$s{EuN!zw<1rehmdd;w5!{}`#*r`(|>Q`mdzI}Vj zO0O}`Oh`yD1PlpB*yjh|XDw5vOu<^UYUNI#dGqF)0}L&-Z}f^fE&KNElS-8;$$|w7 zc+B&ZlP2-Og9l~XwrzqOlE}zN8cTU;bSxH&Mk*#IhMKm|0oD4gTeos0VV@hx$;qE5 zz$yd-EM#o|V1DPz(Tz3qw{PDjSmsn7-Me>}o;`cY)~#Fhd|X_d9uFQon3aemXpsFg zDJe$+xe@tt0?;k{fEQ~%Yqul#vIh?ym}Bh&yp|CoX&&?Z{rmTMOwvl|$&)9J&!P-T zfWPy(jzqqVK$R+0Tu~;MKyTO4DX-cC56DI`KRM!_${!>C&Z~M0L0uGiHo_@6)G`j2bmcbK=@{&Nw-G?IFl1F)@kJ zqg+V9F&fSxfT_^>?AbE`=qV&&CXI&|^@)gp*e}5$~cI;RgGGvI3 z93w`IWIjM%XBjmRPl5N7F;B%KKN2vWoTB6$Nw5hzj{qaU#G`iiA7DZz(0dr{269@Z zOxUbwEHHuuIP~elX8U$e2@4Cybv>m+hYqrL?>;2;0SOC_fRqkXQmjmL5MfA=K;DxW zW3cu4^XE)l^?$=T5Ez>q)wSkB*Ryk1|udUaM9jteA!$`q%gh-r$3gdu>Y z`t1a;J3s=M=dY+{{r#<6Cu_Aqm7-C`FlwGviq}F;hf#UXF*mRroua?Hxw&byN|h?b zrKb`I508W-V?iRulwU)F(`()c;ARq_gb?EM61g`@cI@zmw~}Sak|k22LAnw@&_`1{sO^To4KF~zGcf+sa(0TE{>{ItELf{H*X#dl$|^MWgnu7ykO+)XDsIs zKqDdn=>H57b)We7`eF68OMd=&yv?YpRjW!LY^9`UWMJudLHhOUC%9jlG-(3;CqfEo zGJX0CCa@C2G8S^1g?iqOmANl?FrP`rSrlre4XnpH8Pk4U5{DNFrvu2&_*%OLVd<;n( zqXf+6njwMl+c^Z7m^6$p3JBOG{sBS2E(zWpf)_`UfO>f5%wO<(AgdLJH^r`?U|FHQ16T09|O`SbT*2Jj6zOD(RhQr6R2Cau8qL!*RQp;$HX3#z+Jn=&wrN$ z1&1&i*ytLivcTSr$_Dqz@)av36^3oux=p@s*ifa^t5+}XU?@>9Z(n*I63IYcNd?J0 ztU~t7P$hw_p1c#l+Lr{hktL#whJ;3lFAVpCCp|W9;`X8%Sg&3^!SYl}mMkeh{O}Li z6B34Pmd;3SH|7XSF1xah7aKe_QjVDSk8>u%^ZdD1c0K{#YzPqO37W`OYY=q4gTost z%ob?~FVo2K<;zR4V#PGM*RS7*jjr!mVzq46tXWgHV6bh^X2uXspSg;_ZR2N>}46nCf{nrv( zqeczxFR=ro8Agkjm#@xKo)$0eOmo(Cn6B!xmpZlF=n})R?$-fsn*naZsuM4>WrUcB)Wk_H>o_7M!ruFUH zw}t?v#OYC!I1)N$Cyh@?;LEuC0Ew&t)`_=|A0o3ErF0oLZrPPlRnj$rQPD9lekY_8 zCceJD(z|zWS-)We_e5DN*o8@eiz6ifr{tZ0Jqda|4J$EgU@(k`kR4t=vhcS>=)enQ z#mcpm7fUUS3s5Qj_OHcQD=cT}RniuVH>XoJD{eV*&7k>%4J>X+a?D^Plt8(1<(xyn z5sXA;<|S4e8piTULM({{B!jK;Jz-4J50VQbA&}d_DO5eo21!tU+!!uWq==3sR;!ig zof83g$olTxJ4XTr)HT<_Fg6qh`m?%dvkE{2`00al^tg}DPOVzku_}A{@}IQ?jJ!BP{bSC#OJnT~H^&B)Cc*Pc z02>NUB|wAeE$Wl4-t1s=F~Zqh7c{Z4u_%*KXjFSSae(m-E literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/wav.png b/user_config/usr/share/solarfm/fileicons/wav.png new file mode 100644 index 0000000000000000000000000000000000000000..1dfa3209381ff1cd1c1fc3a7e405a105a96a91b3 GIT binary patch literal 3169 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 Hu0mjfEkpBQ literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/wmv.png b/user_config/usr/share/solarfm/fileicons/wmv.png new file mode 100644 index 0000000000000000000000000000000000000000..c12050865fe6233efc2fbfac71556ebc304a707c GIT binary patch literal 3205 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 literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/xls.png b/user_config/usr/share/solarfm/fileicons/xls.png new file mode 100644 index 0000000000000000000000000000000000000000..cf5a2d0070a26ba97f289494d66241e435083b27 GIT binary patch literal 4439 zcmV-d5vcBoP)*0h4kVC_gd$^eSdJ}Wz@!SwRfJ&24t59zn<9!$Rm$Z4{7(bM50l z*>=r|)5r1IC->NnO-|V%zuzELQK?ue5S$}T)5xhfr2G6+9_Bb@DwS=vlyqGuUDKzy zAonN+ZTi3m=i6>!(5{s5w5cF)#VAcNRL#krGao3IDz7UuIe^G?SOv3t*WMRs=*SQ@ z_v5-A>Aqzh6=8h$dRB<&s9?ONI5cPb^C*#Z1rRbWsk5TQsYipc7ev$C}E z%GLDOcYJ|9|HS8au;ln zzx`Kq$D^C6x4oCvF20URwmo%2+`}--=CgEW?DUj%dUTiyRuRTj9w zsj@D~LQ-tNZwat?_*)N9G#I0GgB$3fCq7F@&m5I&vSy~{w@lQkE@*v&1{&xEN&pbiz8@wLbDPCv^RS=*e zvR1_3>KA8{iQ_`d@Lm(?B&}S!n!fSKx9PTxchTsp-pQ)N`L;@rzq@anun(tpFEX%Y-R(5cHIIfzPSTROgY>PRK2DX& z#aL#BGfR0{^)C)g$tl*ZX6JdYl}lF9)8F~IWZJ%9!;kD<5X&5HqU<3v3BiF7%sDkJ6 z_v}bG5XD}O7|s2GDVA%1i`egKlr2oS0WDbg^qmh2oWWwk!(lxO>HeUGZWE?b`o>Fp-Ot`ydTgf$N*&Ep&YJP5Sj4 zKZZkL3i!f^NPt@7-Lz!j8p(=8nVmpK=EacVOt6!RuF!PaW~SHHty@37ef#!*K6L2N z`3%I`LiXxU%(h!^tRxGmOhY`Bpj3K{lGZ2%i~u53qNYF-g`>csPSo8okLXaDp5Ol@ z8h?aLvj7-q^vMrBM#~pmCyj_DL$Pepi$go<%*1I#HbQ=6&F}>UhOMy0D-*{%zkgbY%39 zIKyOQI~B?nv(yITKm6WXrB;ShXXv{xK8`H<$j}1h*9;+#kUxwJ`>C(Fk5&w<6vx=; zk&$y@{O-g$TXTSGqHH;cV#OuH^(d_*5!7r29k~EziArQDE*Ckl8fc(fR@_V9{MiF! zqWwqEt%t{s)Ax3MjXrSA7W&m&zo2A3ML`%+!DDUI20w3b)-t+z@K2~A)Ku;0g$(G_ zlxJt144z#e7~rFF)bTdA=jeFEz`1?NA*c=_SERbSYv9m%jl3AR3|bKgAygL z36aPwo91;5(uO6s(+_^N4KWVFi736acP9;JPf}ms0E`ccAteNZ*=Egq*3-HxHUdfp zzK5Ps{gkybtSv}IggH}?H?jw3%n3Nu7-}S4mFQD?B3+zFrA?v8>T)kDCCYH1=ve5G zXf~kNc&K3#kb*ugY+lzd-a<>~UI)f9tFr=>>>)_{P!-rc$EF8lF z%e9S}y^v@aWYCfYYj8~n5OkzpupHR;#vT!TosHe}u@!d#IxEC*&Zb)@F4(w4xIrX0 zGZ=SbhExt+Iu8vNteYn?nY2~F%e&pZd?)s5z%EyvjiMn%&FmfE8aXvepSj@?3j3pYUg1L34rdCq7Ob^w&PvfjEG=?{ z90(*QwK1L@qyDyj?>@+ibQ&dK@q?Uc9jIl1BZlH@j5i}vetee~oa$hUvFs^&Y3LVV zk)vP;gW6krC^<1sM~@xBqZMJS*ABc&uN>G-YnN=mHItH}A|xQ^QF0x6i9d2#-e=ac z;k>Q|3C5eyLZulqAT{8Y1wLFrE%7$Fp2r6+yrDC%(#!8Wk0@n@*O|N+yZycMXgoPi zW8+B}3|2v1JhtOevb6%lUnlPNi^l3qw1e}5xIafbU;7bNK(Ab5+=WBv7xLTBD?~A5 zU-P6xGL_^Elsx>?v<@&_O7PrZ2NG2WxTt>z$Q(HJTTz*+YKR1IxOKq^4aGWV_It3= zZ-jMNADujRg1)?cE3LZvYQV%6$n>Hvcx!gx^<%FBG8xDczhs1G0ZmRSR8V!8?p|{@ zdO?4cMgbqxS;>P?Wrhsc5FX=X7(UFqjaB}KW@$kaS~0MJ%p!1VrKK@3c6oMSjh{#) zsH?k&_8r)V$Z9A~olcw>2E>lhUu^y~E$&(7Wgn8dn3b%G{(;fl12_;4&kIn$qMelU zJd;d~=j)7@LE|zGC>T^GXgGNU`hI|7frLbwo6>^zL2qJmMU1f!W45w{HQ)?jv%G%| zJ?np(UVZa-lrv4Kon5Enmk5jn6P>X6RKMp5yBG370u3L4)l^MekYBhA-3%W*T-pqQ&J2Tb*@8Lt! z>1&xI29<#SV`FcNRtbSM3=|>1*SLdfKCcNmVjMff(d%m;s6B7}QWeoQ<)8pmKozwl zU3PKbVzL0eTmcgr!;r^JWR&5^S@8RO9veB(L1Wo^*x! z6`uFOKph3_2Ln|d3DAPhtEjhW08>_)&O*qA^sxMmqgz#&sx&zsky#v;Fw9)pa}_P@ zTLg^+-Q~^1>(z#X$O+C;6OJiaGurK(iQ#fGV^ z2!<-VNfv;JSOL`7M_O}$PNnwK&-eTYn}^V)gQ%O3P-7I$jDL3~x~K;VlUb}#HW3I= zE1cxHA|3?$#Or$i3rZ74`uKFCoXKX)di>AeKpC>Q3q0H)b(67@qEAHDx>cO382ucj zdWO^(ZkFt(^GR%4lmd4^k7c*oBlt5*gq0Y2=CsYHd^t=1^72!tgA_GITR{|2?3I8M zaf(5@N7(5ALp31H(=@G8DwWE=O9xybsm{heG@LN-Z&K16sZClgRcexhzhkKIj0@3B z6NQw_tX%laC>le+bHc&zhH#@mln;uBlfyKUIY%ePPlBSWB95WP=ds)zZyCIauD`N6 zAeC+0VHNT<4xY{dc@PSyJ26Wl&G=*$$`h3%)LB)ItOmF!V<<+33cpXAsVZ(=m(KrG zXJV#Fo*Vd?d8k~jg#e(&hh-Jtq>~j9g?wV6dMcFJE^p@SiHY>P%K)caM-)06d*pgf z;nf2-NGG;Hd(8LQVn(!lGB--i(RR5$k6yqI$g#Y`ClKDhA-IaXSCE;A5tjY)C{*q( zY$P`!8^Z;`LLEs(`@D7QUI*sPaF?+}e z3LC2EKP;tlxqPusFkk4v`E)V2-#B;&K0q~s?s(v4TysWc*uam=5E52WWF~cFBZUT9 z7s!d4E4Z(AYK9;;tb%P(A{0lHG=(DPw#`LhTcU}qQbA6#

    ^7(xAbD9%Ol1OQn)I zi~^U=fIp~t9|DHzcj@`j?}HT@D4)&I*f3bdv@vufsH3qnd0~y=7vT=>5}J9 z>cKu#H3TlmK~zMG$haw|Z*Yhmnb$dw=6B7PVm6B=GL%8)CWMtnGh^ZyXMp9rj>qS+ z(uM8%S)k^?(U(Uh0hO|Q(#jNnxx2hy7k!ES^U0#Wn6-Ts2b_u6`c+*C8et_CN+=DH zMu@u>r8VB+w#7QBv#rbM>gtHMx3@(T&GAT3535z@mUGUxL$XLc}VD3eo_~`BtNqaGG?;AUk*#!Nq;XREV&FZSEu$sa& zq8O+Roymxk=G{B_o2}FW!GJ#=kA-5fSfs7JB{64qf77g4vzq(+``TyC>TB!lX=`b2 zYHo~#BL;eh_wQp>R^+^K?_o7ssnN9SO@I~3du!s)Y4|rYX{4tBmAM zTu?WvXRuzk*bxIft@Ao}cEoq+;9=jcmv+~#)igC2iAE#c-JK1+y**8RvwAyb!?E7J z?)ILZ?vB=$)>u5A2*=}*5UABxy-o2TZ%{f@qtME`&_dI`CLdzc`9nfwG7ELiO=gIf zj)3Uti8L?mjRS8fb@v!SyjohDCBkj(#U2WE0^y3A=8)SKoNaq;)Xftj2u*L}9? d9GgO;{|($f8XLB=&RGBe002ovPDHLkV1lC$r0xI! literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/xlsx.png b/user_config/usr/share/solarfm/fileicons/xlsx.png new file mode 100644 index 0000000000000000000000000000000000000000..454fd5dc332da2b86b1e38ff2ee10242bdf36d43 GIT binary patch literal 4560 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 literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/xml.png b/user_config/usr/share/solarfm/fileicons/xml.png new file mode 100644 index 0000000000000000000000000000000000000000..609f131eaccfdb66df127047c36408aa435d291b GIT binary patch literal 1596 zcmV-C2E+M@P)Q>xt2Sv&8qrvq6xuXx zDb>~5BEgD$z)B+vf?*#hlx2m2>#|VFbIWTtTiK>mJ*Vf4%ZBlG)65JwCT{Z2y)$=a z?)}|!?m2L0o_m&lF{H$(sHhjCqobEo`LY_(Gv>9N^a3e%$p_AZUb%8*Zc$N@slL9x zt-Zb7L+Wp9Yh!tC-MZzudGn^HwY8PiXSdtKve|5stXAv1+!l+)-O|$HE-o&%B_t%I zQ6KS3HTby1#Kbqs%F3?z{r>w?Q&Z^mdSQHg97$5Cq)zu@NH8`wh6I6U&z}8t*|KFz z0udb_AD>cJSJyE!GlTO3p^jl;skS~kIw}Elb#;``N1@Le+`Wu|m`QUdg=xgfwPYcl6KrI-}T!904;dz@9$>_=KhG6dFt8~vr@HIMRE=17_1_uX$Ab=ni z)t%z<^Yf4Rd_I}!{ns1_?^nXysjEE0!UFxX(u(lj1(>oIOtrZrfR2t14yg>mmWbp|3=yXoAU8LcEf6;O_$V#NFUlR*P3eW zmD*;@uM2L+*Up}9lG=l1jr=SI0Efc?larG`5I_(S-6<(4=}itSiU4-*+$p#ab&SC; z>H&A3!+U3%d0^q0o-J!cK0jse5iCPc2)oZg@T&&t86hxqI-N-CfCVI>PFWthySs7I zrcDQUT`B_Dv15k_z+y{$b`4>d2?ABNN9Tq|;o;uz@&5iwZkGWBE?5wEnIv#(6I|>6 z5*`H-%+Ag>D0QQ_?c28>4!MvZ38Lg@2!4J|I-Y6h1bF8(+&j}GTpRpUx{mJQW5r?s z$jZu64zO+8HU@||TRfoY=G%{wS49i_{o!StaJq#Vq-*$E;k*(HWM*b62iUrGt5E<@ z|F@7PfQ*a`Y`9;uD{lC1%XLWdLs zY~H-txG<5?!?Coq6iZ46MD>c`}HtI`ef%WUxD+i$DAp@Y;EJ1LbURY|WM{1+Kv;%PLeHs!Ok73=qb;z>Hi3$y^}6-KoK(8^~3-4hv)K|O%}7GMDN7NE^sofDaM0NoQAk74cFwaNiF z_R^Mz?um@OV9lB}2UP(~Oiak~0LAE~mXHT{-)PmURmv^Een%TlLMA4GtkhZ;k7b#d zgsWGtHYf&QzoVOVPh`B^NRxR~4S<2R`*aVZZnvA)fI$_&(9n?TL`FN$nj_Hg@UXN` znwpxrkGB?;0vKttRWA#0iG6*2aOcjQhsnvwnf%U95dfLZI504v3qWY_$g^KlQ{$oW zyvBo44B+v2BE6tn3bD)Zj6-KzkW3BrY}^73!n=&OL&`Q3nGfV8xurwH_EK@5Fbu=n`!lP*%<6O#Mbv5%Yu1@B&7u_9#;e@mh1JBpZ? um_+`lgY@bX(knD^FYya@Dds;bzkdO=d+G_h@D&&U0000Px#24YJ`L;(K){{a7>y{D4^000SaNLh0L01ejw01ejxLMWSf00007bV*G`2iyY~ z5h4;Y%8h#f01!J#L_t(&-sPElkR4T>$3MT*-S;u?%w&d;_X84wk|^&43NS7TwX%SV zEUT-#%9d;t1Ou|xt{}2%0Z}1}h^6bYTP_N&@(2{f7=n_71PGCk1Og$CNivzqOzu4H zy>lPkeSZ5#_kCnY5Gb~`wzjLfZr|>6&hK%)zvqFsbNz75V*S?2zf=IJyj;5aDm^&5 znPQM-q|hIIxc%wNb_&zsDHR zQ0Oqn;chvwS_5ZP+>~HkNYcnlPjCB{`Q_gXn|*8b3RUI)i^}86VTrj5C;szMnYn+> z9lAU7vfgmtg?iyNwfCle(Aoc3d)}H`!B5s-$AXhT#1lWLy_fn4=iZpU^we|T|C^99 zdq=h*2nt|9EzBK%b~NF04@}d+@&Sws#sM+WFA@)IS;Q@W_M}uPoN=8u#Yp&d$8jH< zbS4iO2X_4-cr8<~r+@xaS7e?a_>Iv3#-%1zSrVWXn1B8^8lDz$8R*XKc=4z9l8@fn zxn)Tgv1(M~bQV?$W-;h$B< zIY*k{&FVY3sneY^TpHl!k3K9%rLg>^r*zZ7ACe0W5L%zm`V@maC4UsPF85urM_g4! zMEK-GpJZL{Hz)-;4i&n|`@=v4d9|Q6f~;AvL@vH(-4}Ioa+l4P4$_@{iBTQKcwjtm zTxc(xQ0mzf>;aH-{>j_kF-K;L3yuTg)OH!7)n8dzb~fLO1u$XB?n)<`aE${J!2$=2 zTbtaNc0ON4>)P*z7wmd&_>~9$x*(3@AOc+tQ`xejJn!{Y;rx7h{A{^Ir-VeDu#hxOVBok&0B6TOauJz(kaSfm~sbtlw8t(Sk2KJ5D=i-mIZg zFQfhtViaQxI6`R47$cFD$eWd_3ZgKrWj?DOf4(D`B(rYmm`xT#c&N}#AsDGHq+$^T z;|OBn#VfzWW3+aACJh0(b>EM^pLk2eOO>CC?oH{mV`q_0G_tpM7uiFlplMpkMKrQ+ ziELZM>a_NgBBb~?U-+6F4MlR+67SL}3!Psp zHYT7nWRB-&S8j7+7a#zqUBl9O7mmL+R~!Z#^K6A?ualo%x>wvWkoABh;Pq|ugDDFV zSPc=4yj0Va5a3*(FTxA0Q)e9W*?ciWgVTnWNRf~Rtg2=P285bMs{|Y%5$}x)G{)~- zYAOwYA`r$5HHjfrQ0LH>VSXMNdY_TMw%>%X`{pWF3>?j3wpU*7)*y?5nT^nzPD zR=?F;=j{;KyWVo|kH4EecEThrhXonTA7HrDPa^4Y((F^G9~!sb*}cYJ{jR;~giq1d z(3oOp|4ZUHUbNF1Q1%OC%Ng~OiOXR0=66k?_dgkW{M=tKX-1cZ8cO8&B6mHr{e!Z6 z=&_F-ZLWE{`|#-MHHo>MXG>rU1h$B8iv(Kc?w{ZB`o?AK96P>}cOEq!oN+BTFTVU6 zw=^Z&1QC*6noWI=QV6nfzzGP#GADMOr^RAH{Gfmz`UJMf>)o&B@4ISi(_0ew&ZA4z zYfW#gF-Mwrtk(K zA*x{%A~hr*3^7tTNGUADNpEbsasCVN>Y?Rgl!mw5s--)s)jLgQEvQqqQA?mIViPw{cR)^>Kj+$#JFLDfO5Pu2sN9P& zCJH>i|F+fZnwA|N4fb!~E*GF}q9Z#qFVd5J4V4fvg6nt^ zYAK3rB2_?-^}j|T)R5h;^zPjFea<-ZI(T@=K^87CDNx+g{=szQgDidd-H6mep>M-D zL<+_n&TXXhifVw+vVY%!M*!B}713VzJzE5DOQmGC;18p!)i`BDs>G^MAXZJ4NL{ur zMrdeAw?XO6mc04j3gu!pz;hC`CdZR@n^4jCt`RVGgvLr^jKlO9(>`1EzE{(+;0AYf z6-9%)!_DJPX`6RU^GqG_56bZCA>*etGOC9TTSy7%Y@9-S!$kbB#IDRARm2fN$hB_Q ztA2Tc7VsFn!|W_TNrCRgNi?vvFYq)`EtX?)Bl0=a%X5M|BmK-P@t2BU?>Z8WK%xUY>d1 z#vk)qlp`X5f5RgSb)P;DR!}R56|A*%uMb{Zf0vyzHmis$9IUlkQ@KVvYKw9_st4+} zK4+a0;u{jCfuz?+!fB#V$^r*MqEhKF*PV#(WEccEs>Ewk@JDsr)YQXJI2{-PyoqOU zzNCHJAcberIFdlrpjcFOKT(zUrug6ijlaJ$d-DfAowzmOdVnAzq67dPz$lRJnrl9@ zbzOJlhsdFfpsEz|Wq-pm{Q@!g#xCx~=t{@ZRMb`(TLQ}Qb2?7c>I0XJzh~my=>PU5 z@AZNUuJ?HMyFT-;A?=qKKdGzjqEsUN7e>q!g=16|MGsmH_7{tV$1+2?U5p`D9AtO@tNQTo|FwUA&))gZ z{UEC8-|pqU*8-R%t8e(^(g`~UHtD`xN*v?ih$BWEL=>xvswJ>KK^WD%1%HGEr=Huj z@|oqUVR-KUssQC6l!TLJsL&_AEn(FXmGHG{(Gn6`3z0j)a}r|8P1sQHf2Z`E_ga9S z4cxumedxgS&i7w5v1=-sLQk9k`h@BeYMDR-R0DilVshIYMg~X2$DaPxO>35xzP0d% zBzxC||MNR=(qcXoD)UuI#aTu58ZG-RL)n3y)6J}zWm`Zn*tmV6Xq<#9g3#VLk>Blj zJX;QEJ!vst&E-ov?+b4>QUU82c5J+xhv@$d3%>myJbUN6FyfQ1VcNp^i*{dm{=E!l zi&_XrCD2j)Fh@De64)#`zn}iXA1Q?W5V4Ha5RLI40ZJoysbPHbeWcuWT$92vDO{7r zktCip;KUKkh@*k*mZvt|$1B^nd~?J1xanQ(Kubfz$s)#w{&Re+K$;s*tMTSid^i+i!QGG9>}t%xe24GC?4YABPN7jnXJ^Vqs=%g5me z0#y3BW3T%6s>)zt57~U4Fs=b3?-DZ{6Sit823fMD48i5Tcym>BSlcDF)l=nMNvgnbxzlrlC-V#_Vs{kY3pbvR~jTFMC!b!`{i!9 zb1IyNkKSD9gRSQCi z3ziC(M?0lBO#51ETQ(-zXvrTZt5>Z1`m;;zt@YDSy^cjTp0+;pvgFfVmXT+6e)$Hh%s++#1#&!?N|=4+LT(grgg%Y zBSA>&G+${8#D!`;pUV_;QS$yLQx|}ZKe8FWs9rfN5Qafjb5%GDsro-2Q<*157_}KDOzgTO-9qD8*Lz20{G98&!%=zq67OTp<5)g^W|{`C zMcW!CNK3MvP(wyaeL7U?6+bLv#1K+aO8_EL(Y7%)7*!xAQU~^W;K#=RT6xx5mj*RD zt@R{%ZBq}24jsBXEQj}7qcf8U=SIgf^TtnZW7o@lyEoirXVi{JuT8OGS&;?TJJZ|* ztD3r;Sq&X7p65^)w&V^5L3uQMvYfYHa#QBw$+O$57O%>7$tPMOjA)cF@&G;L$wauq~Si4EUMU~RNJw$?YYVF-w= zSk++>X7x=zM=0|R;Jo~lbpWv;j*ZiWiU+A2H)(w4)aZ23)7@L{-&wr6=MhX?+ot2b zfAxOl-|6n#H~h72TeoXRdXiYJ^o?9xlO*Xhkak;WN{pi^F`ksyOlU*m^8^5K-=a!$ z#uVHJuzo>PR&8{0s~p}8jCGRr579PZYNBIW;vOh}E-vKjnw`;_Zi;lWN&aQV2@_=7 z!8MwYlsG1dCkY(!a7_|d5;&3ogyF(Lg3zyT;Th*B0+<+HUVSeFXdT&a8(OA>A8GAu zY)m#J@tg!nH$}ork@6ZzB^pV(XO>i)L^ zoj^L#Ku7u*8D~Sa_=t_uBC9}%3KFR3NZ}B>;}(b?ukRwXq*|FIhE{=u8VrIMCvsrM zASi+pxmO!d%Jt|DUpH)QhWv>pLuF1F d-$CX1zX3|-F1^7xgS7wv002ovPDHLkV1nA9I9vb# literal 0 HcmV?d00001 diff --git a/user_config/usr/share/solarfm/fileicons/zip.png b/user_config/usr/share/solarfm/fileicons/zip.png new file mode 100644 index 0000000000000000000000000000000000000000..8caadb22b3b828541e0f02ae396845bf367bad2d GIT binary patch literal 2099 zcmV-32+a41P)4ah{otaWfvB`D7XqfPC4vp70-(xo&*ur+lq*g6;`9s zRrAi6cjnQ(|5r2B^G{FDbk8F;G(FwZkE;5<`l_lsY}+=u7sD`k+t$|hLwkGs_whvB zBG+m9b*Z1J{ZJ^Bc=zsI=jYF#+tqGNt&xU?hB0-Yzrx{gYg{ihlj-GT3%j%d#k$wAc>p*Vfk3rAwD+XlRIb?b<~zU%sS) zfdRUF`SMvaWYYN9_|TIlPkvX+Z?m(r(^Te?&jCcH-m>l5cp^b6T5PBwqdOo@h=a*w zl1$U2nVA`CX=$NNn>Nv>PoIDcpQBLt?!bWqzt+^$G~K>^`@+J)!u(=6P^BJN1>?lP zd??7GpEox*M~W)+?AbHkk3=H8g>%Eh!?b75o^JKqx^d&i1r?xJDRlCqkRkaXk$2~b z8wDJLfpU$CHd9LRo__;{>gsC#@56@=bmYj99{j#`?b-!(eXNoQkZ!z}1Nu2I9)`<` zV?sL^olb75s;c4@&VXXESd6;6yL%Lk=+2!xKdWozD(ZmWB96gmK>@$9z)%;I5CLdvYU2GKwWN^;? zK}4JoN9ybA>D8-O^!)jArV<*asK%DICXV(o$c@M|M%g;hrMMZAoA!o z+}&ImhMAMZKqC~&L?2NG-|-)|P%L={e&c-Ph6;kID)wR3JN>EZ#$ZV*B+%=^>CK`PKXDd$ z1hh0R+a(pUzy;07oC{<0UPz~;jliU%Eh<s)U?ypU={(wJU-Mt5bksDlh_G)bU!-S+=&e z@+%cJT~XJMU9@G(79Q*|;OAUxXXmI;OAsk52MQVOg@VZE$jAt*501^x&2!tlbm>y6 ztEjYHN$l=>+(^oA znG4Du>fJZaQ4kL*K|-6Go9W}nkGv25FIlpL*FyC1aw140S+tD$)d3&?{v+a}qocHZ z`EpvldUe+86+a3V^Ij@kO2XziX4_efI*e~?Yh(S-indM88w@`xRs7XrzCtF`yy5y! z?{l2<(?!dRI(~twsEqp6gnUz5!3YB#!zP1_$~)i}nfz}k4Bys6=44hK7sb9Eg>w3L z%KBO(1C2gz%02XMRAvd8kM$2Jm7fRD3TrpU!=XBm&nh(v*45Qgb4xQtC!(}+)E_{ zGd+I%n7?4U3#3ePl=BLC5ptkVE-TPbJtbykY+oo^0 zAovSPy#^*HCr61#97-00fSS+e{!#0HX!DCuwn{Duwa%z@ieH!(i2z`Ws&%qRX|CiV dkSFs|=zkai`WJMK9 Date: Fri, 25 Nov 2022 23:12:43 -0600 Subject: [PATCH 24/30] Added IO ui to stop move or copy, enhanced favorites plugin --- plugins/disk_usage/plugin.py | 15 +--- plugins/favorites/favorites.glade | 6 +- plugins/favorites/plugin.py | 42 +++++------ .../solarfm/core/mixins/show_hide_mixin.py | 3 + .../mixins/ui/widget_file_action_mixin.py | 70 ++++++++++++++++++- .../usr/share/solarfm/Main_Window.glade | 54 +++++++++++--- 6 files changed, 139 insertions(+), 51 deletions(-) diff --git a/plugins/disk_usage/plugin.py b/plugins/disk_usage/plugin.py index 790caa2..285c288 100644 --- a/plugins/disk_usage/plugin.py +++ b/plugins/disk_usage/plugin.py @@ -1,5 +1,5 @@ # Python imports -import os, threading, subprocess, time, inspect +import os, subprocess, time, inspect # Lib imports import gi @@ -10,19 +10,6 @@ from gi.repository import Gtk from plugins.plugin_base import PluginBase -# NOTE: Threads WILL NOT die with parent's destruction. -def threaded(fn): - def wrapper(*args, **kwargs): - threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() - return wrapper - -# NOTE: Threads WILL die with parent's destruction. -def daemon_threaded(fn): - def wrapper(*args, **kwargs): - threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() - return wrapper - - class Plugin(PluginBase): diff --git a/plugins/favorites/favorites.glade b/plugins/favorites/favorites.glade index 6c213db..ae1449d 100644 --- a/plugins/favorites/favorites.glade +++ b/plugins/favorites/favorites.glade @@ -1,15 +1,17 @@ - + + + - 320 + 420 450 False True diff --git a/plugins/favorites/plugin.py b/plugins/favorites/plugin.py index 9dc40ed..ce068de 100644 --- a/plugins/favorites/plugin.py +++ b/plugins/favorites/plugin.py @@ -1,5 +1,5 @@ # Python imports -import os, threading, subprocess, time, inspect, json +import os, inspect, json # Lib imports import gi @@ -10,19 +10,6 @@ from gi.repository import Gtk from plugins.plugin_base import PluginBase -# NOTE: Threads WILL NOT die with parent's destruction. -def threaded(fn): - def wrapper(*args, **kwargs): - threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() - return wrapper - -# NOTE: Threads WILL die with parent's destruction. -def daemon_threaded(fn): - def wrapper(*args, **kwargs): - threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() - return wrapper - - class Plugin(PluginBase): @@ -65,7 +52,8 @@ class Plugin(PluginBase): with open(self._FAVORITES_FILE) as f: self._favorites = json.load(f) for favorite in self._favorites: - self._favorites_store.append([favorite]) + display, path = favorite + self._favorites_store.append([display, path]) else: with open(self._FAVORITES_FILE, 'a') as f: f.write('[]') @@ -78,25 +66,29 @@ class Plugin(PluginBase): button.connect("button-release-event", self._show_favorites_menu) return button - @threaded def _get_state(self, widget=None, eve=None): self._event_system.emit("get_current_state") - - @threaded def _set_current_dir_lbl(self, widget=None, eve=None): self._current_dir_lbl.set_label(f"Current Directory:\n{self._fm_state.tab.get_current_directory()}") def _add_to_favorite(self, state): - current_directory = self._fm_state.tab.get_current_directory() - self._favorites_store.append([current_directory]) - self._favorites.append(current_directory) + path = self._fm_state.tab.get_current_directory() + parts = path.split("/") + display = '/'.join(parts[-3:]) if len(parts) > 3 else path + + self._favorites_store.append([display, path]) + self._favorites.append([display, path]) self._save_favorites() def _remove_from_favorite(self, state): - path = self._favorites_store.get_value(self._selected, 0) + path = self._favorites_store.get_value(self._selected, 1) self._favorites_store.remove(self._selected) - self._favorites.remove(path) + + for i, f in enumerate(self._favorites): + if f[1] == path: + self._favorites.remove( self._favorites[i] ) + self._save_favorites() def _save_favorites(self): @@ -104,7 +96,7 @@ class Plugin(PluginBase): json.dump(self._favorites, outfile, separators=(',', ':'), indent=4) def _set_selected_path(self, widget=None, eve=None): - path = self._favorites_store.get_value(self._selected, 0) + path = self._favorites_store.get_value(self._selected, 1) self._ui_objects[0].set_text(path) self._set_current_dir_lbl() @@ -119,5 +111,5 @@ class Plugin(PluginBase): def _set_selected(self, user_data): selected = user_data.get_selected()[1] - if selected: + if selected and not self._selected == selected: self._selected = selected 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 239b155..f968121 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 @@ -115,6 +115,9 @@ class ShowHideMixin: if response == Gtk.ResponseType.CANCEL: self.cancel_edit = True + def show_io_popup(self, widget=None, eve=None): + self.builder.get_object("io_popup").popup() + def hide_edit_file_menu(self, widget=None, eve=None): self.builder.get_object("edit_file_menu").hide() 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 007008a..572ec19 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 @@ -320,16 +320,82 @@ class WidgetFileActionMixin: if action == "move" or action == "rename": tab.move_file(fPath, tPath) else: + # if action == "copy": + # file.copy(target, flags=Gio.FileCopyFlags.BACKUP, cancellable=None) + # if action == "move" or action == "rename": + # file.move(target, flags=Gio.FileCopyFlags.BACKUP, cancellable=None) + if action == "copy": - file.copy(target, flags=Gio.FileCopyFlags.BACKUP, cancellable=None) + container, cancle_eve, update_progress, finish_callback = self.create_io_widget(action, file) + file.copy_async(destination=target, flags=Gio.FileCopyFlags.BACKUP, + io_priority=98, cancellable=cancle_eve, + progress_callback=update_progress, callback=finish_callback) + self.builder.get_object("io_list").add(container) if action == "move" or action == "rename": - file.move(target, flags=Gio.FileCopyFlags.BACKUP, cancellable=None) + container, cancle_eve, update_progress, finish_callback = self.create_io_widget(action, file) + file.move(destination=target, flags=Gio.FileCopyFlags.BACKUP, + cancellable=cancle_eve, progress_callback=update_progress) + self.builder.get_object("io_list").add(container) + except GObject.GError as e: raise OSError(e) self.exists_file_rename_bttn.set_sensitive(False) + # NOTE: There is something not right about the way we are doing this. + # Calling cancel results in an error getting thrown to finish_callback + # and checking for task.had_error() is True and task.get_completed() is False + def create_io_widget(self, action, file): + cancle_eve = Gio.Cancellable.new() + container = Gtk.Box() + stats = Gtk.Box() + label = Gtk.Label() + progress = Gtk.ProgressBar() + cncl_button = Gtk.Button(label="Cancel") + del_button = Gtk.Button(label="Delete") + io_list = self.builder.get_object("io_list") + label.set_label(file.get_basename()) + + progress.set_show_text(True) + progress.set_text(f"{action.upper()}ING") + + + def do_cancel(widget, container, eve): + print(f"Canceling: [{action}] of {file.get_basename()} ...") + eve.cancel() + + def update_progress(current, total, eve=None): + progress.set_fraction(current/total) + + def finish_callback(file, task=None, eve=None): + io_list.remove(container) + # if not task.had_error(): + # self.builder.get_object("io_list").remove(container) + # else: + # print(f"{action} of {file.get_basename()} failed...") + + def delete_container(widget, eve): + io_list.remove(container) + + + if not action == "move": + stats.pack_end(cncl_button, False, False, 5) + cncl_button.connect("clicked", do_cancel, *(container, cancle_eve)) + else: + stats.pack_end(del_button, False, False, 5) + del_button.connect("clicked", delete_container, ()) + + container.set_orientation(1) + stats.set_orientation(0) + stats.add(progress) + + container.add(label) + container.add(stats) + container.show_all() + + return container, cancle_eve, update_progress, finish_callback + def setup_exists_data(self, from_file, to_file): from_info = from_file.query_info("standard::*,time::modified", 0, cancellable=None) diff --git a/user_config/usr/share/solarfm/Main_Window.glade b/user_config/usr/share/solarfm/Main_Window.glade index de3b02a..3910294 100644 --- a/user_config/usr/share/solarfm/Main_Window.glade +++ b/user_config/usr/share/solarfm/Main_Window.glade @@ -1,5 +1,5 @@ - + @@ -482,6 +482,11 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False gtk-execute + + True + False + gtk-stop + True @@ -824,7 +829,23 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe 5 start - + + I/O + True + True + True + io_img + True + + + + True + True + 0 + + + + Plugins True True @@ -834,7 +855,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe True True - 0 + 1 @@ -850,7 +871,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe True True - 1 + 2 @@ -866,7 +887,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe True True - 2 + 3 @@ -882,7 +903,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe True True - 3 + 4 @@ -898,7 +919,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe True True - 4 + 5 @@ -1832,6 +1853,23 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe + + False + io_button + bottom + + + 320 + 320 + True + False + vertical + + + + + + 320 False @@ -2099,7 +2137,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False - plugins_buttoin + plugins_button From 8a3146fd0305f953941a29565791b4c1f8599e69 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sat, 26 Nov 2022 14:44:38 -0600 Subject: [PATCH 25/30] Added multiselect dnd with just left click --- .../SolarFM/solarfm/core/controller_data.py | 1 + .../solarfm/core/mixins/ui/window_mixin.py | 26 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py index 6011cf7..be65729 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller_data.py @@ -71,6 +71,7 @@ class Controller_Data: self.to_copy_files = [] self.to_cut_files = [] self.soft_update_lock = {} + self.dnd_left_primed = 0 self.single_click_open = False self.is_pane1_hidden = False diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py index 5b3d9b1..503cdd8 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py @@ -158,7 +158,30 @@ class WindowMixin(TabMixin): tab = self.get_fm_window(wid).get_tab_by_id(tid) path_entry.set_text(tab.get_current_directory()) + # NOTE: If selected multiple with box select and then reselecting the same + # with box select, self.dnd_left_primed does not get deprimed if already primed. + # Ctrl does help in this context but the abocve is not desired def grid_set_selected_items(self, icons_grid): + items = icons_grid.get_selected_items() + size = len(items) + + if size == 1: + # NOTE: If already in selection, likely dnd else not so wont readd + if items[0] in self.selected_files: + self.dnd_left_primed += 1 + # NOTE: If in selection but trying to just select an already selected item. + if self.dnd_left_primed > 1: + self.dnd_left_primed = 0 + self.selected_files.clear() + return + + # NOTE: Likely trying dnd, just readd to selection the former set. + # Prevents losing highlighting of grid selected. + for path in self.selected_files: + icons_grid.select_path(path) + + return + self.selected_files = icons_grid.get_selected_items() def grid_icon_single_click(self, icons_grid, eve): @@ -170,6 +193,9 @@ class WindowMixin(TabMixin): self.set_window_title() if eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 1: # l-click + if self.ctrl_down: + self.dnd_left_primed = 0 + if self.single_click_open: # FIXME: need to find a way to pass the model index self.grid_icon_double_click(icons_grid) elif eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 3: # r-click From 95f790a7a49152f2a477cac9222b4ce5e0a6c416 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sat, 26 Nov 2022 16:06:39 -0600 Subject: [PATCH 26/30] Fix ctrl+a and box select all l-click dnd --- .../SolarFM/solarfm/core/mixins/ui/window_mixin.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py index 503cdd8..af76f29 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py @@ -158,9 +158,6 @@ class WindowMixin(TabMixin): tab = self.get_fm_window(wid).get_tab_by_id(tid) path_entry.set_text(tab.get_current_directory()) - # NOTE: If selected multiple with box select and then reselecting the same - # with box select, self.dnd_left_primed does not get deprimed if already primed. - # Ctrl does help in this context but the abocve is not desired def grid_set_selected_items(self, icons_grid): items = icons_grid.get_selected_items() size = len(items) @@ -182,7 +179,11 @@ class WindowMixin(TabMixin): return - self.selected_files = icons_grid.get_selected_items() + if size > 0: + self.selected_files = icons_grid.get_selected_items() + else: + self.dnd_left_primed = 0 + self.selected_files.clear() def grid_icon_single_click(self, icons_grid, eve): try: From da9a8c024b82e0f6e6b9cd73181036154ba2eaed Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sun, 27 Nov 2022 16:38:39 -0600 Subject: [PATCH 27/30] Fixing move async, small cleanup --- .../SolarFM/solarfm/core/mixins/ui/grid_mixin.py | 7 +++++-- .../core/mixins/ui/widget_file_action_mixin.py | 14 +++++--------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py index c93a3ca..486b579 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py @@ -86,9 +86,12 @@ class GridMixin: info = gio_file.query_info('standard::icon' , 0, None) icon = info.get_icon().get_names()[0] icon_path = self.icon_theme.lookup_icon(icon , size , 0).get_filename() + return GdkPixbuf.Pixbuf.new_from_file(icon_path) - except Exception as e: - return None + except Exception: + ... + + return None def create_tab_widget(self, tab): 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 572ec19..c98984d 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 @@ -320,11 +320,6 @@ class WidgetFileActionMixin: if action == "move" or action == "rename": tab.move_file(fPath, tPath) else: - # if action == "copy": - # file.copy(target, flags=Gio.FileCopyFlags.BACKUP, cancellable=None) - # if action == "move" or action == "rename": - # file.move(target, flags=Gio.FileCopyFlags.BACKUP, cancellable=None) - if action == "copy": container, cancle_eve, update_progress, finish_callback = self.create_io_widget(action, file) file.copy_async(destination=target, flags=Gio.FileCopyFlags.BACKUP, @@ -333,8 +328,9 @@ class WidgetFileActionMixin: self.builder.get_object("io_list").add(container) if action == "move" or action == "rename": container, cancle_eve, update_progress, finish_callback = self.create_io_widget(action, file) - file.move(destination=target, flags=Gio.FileCopyFlags.BACKUP, - cancellable=cancle_eve, progress_callback=update_progress) + file.move_async(destination=target, flags=Gio.FileCopyFlags.BACKUP, + io_priority=100, cancellable=cancle_eve, + progress_callback=None) # NOTE: progress_callback causes seg fault when set self.builder.get_object("io_list").add(container) @@ -353,7 +349,7 @@ class WidgetFileActionMixin: label = Gtk.Label() progress = Gtk.ProgressBar() cncl_button = Gtk.Button(label="Cancel") - del_button = Gtk.Button(label="Delete") + del_button = Gtk.Button(label="Clear") io_list = self.builder.get_object("io_list") label.set_label(file.get_basename()) @@ -379,7 +375,7 @@ class WidgetFileActionMixin: io_list.remove(container) - if not action == "move": + if not action in ("create", "rename"): stats.pack_end(cncl_button, False, False, 5) cncl_button.connect("clicked", do_cancel, *(container, cancle_eve)) else: From dfce2f01256aa8994afaec223b86abbb7418b7e1 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Sun, 27 Nov 2022 17:26:49 -0600 Subject: [PATCH 28/30] Fixed status check and button show logic --- .../mixins/ui/widget_file_action_mixin.py | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) 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 c98984d..735ef2e 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 @@ -329,8 +329,8 @@ class WidgetFileActionMixin: if action == "move" or action == "rename": container, cancle_eve, update_progress, finish_callback = self.create_io_widget(action, file) file.move_async(destination=target, flags=Gio.FileCopyFlags.BACKUP, - io_priority=100, cancellable=cancle_eve, - progress_callback=None) # NOTE: progress_callback causes seg fault when set + io_priority=98, cancellable=cancle_eve, + progress_callback=None, callback=finish_callback) # NOTE: progress_callback causes seg fault when set self.builder.get_object("io_list").add(container) @@ -339,9 +339,6 @@ class WidgetFileActionMixin: self.exists_file_rename_bttn.set_sensitive(False) - # NOTE: There is something not right about the way we are doing this. - # Calling cancel results in an error getting thrown to finish_callback - # and checking for task.had_error() is True and task.get_completed() is False def create_io_widget(self, action, file): cancle_eve = Gio.Cancellable.new() container = Gtk.Box() @@ -365,22 +362,26 @@ class WidgetFileActionMixin: progress.set_fraction(current/total) def finish_callback(file, task=None, eve=None): - io_list.remove(container) - # if not task.had_error(): - # self.builder.get_object("io_list").remove(container) - # else: - # print(f"{action} of {file.get_basename()} failed...") + if action == "move": + status = file.move_finish(task) + if action == "copy": + status = file.copy_finish(task) + + if status: + self.builder.get_object("io_list").remove(container) + else: + print(f"{action} of {file.get_basename()} failed...") def delete_container(widget, eve): io_list.remove(container) + stats.pack_end(del_button, False, False, 5) + del_button.connect("clicked", delete_container, ()) + if not action in ("create", "rename"): stats.pack_end(cncl_button, False, False, 5) cncl_button.connect("clicked", do_cancel, *(container, cancle_eve)) - else: - stats.pack_end(del_button, False, False, 5) - del_button.connect("clicked", delete_container, ()) container.set_orientation(1) stats.set_orientation(0) From 9bd5697677000cedac44e68753c7da2cf81b3699 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Mon, 28 Nov 2022 17:09:00 -0600 Subject: [PATCH 29/30] Moved code to widgets --- .../SolarFM/solarfm/core/controller.py | 4 +- .../solarfm/core/mixins/ui/grid_mixin.py | 140 ++++-------------- .../mixins/ui/widget_file_action_mixin.py | 90 +++-------- .../SolarFM/solarfm/widgets/__init__.py | 3 + .../context_menu_widget.py} | 6 +- .../solarfm/widgets/icon_grid_widget.py | 72 +++++++++ .../solarfm/widgets/icon_tree_widget.py | 79 ++++++++++ .../SolarFM/solarfm/widgets/io_widget.py | 78 ++++++++++ .../SolarFM/solarfm/widgets/tab_header.py | 48 ++++++ .../usr/share/solarfm/Main_Window.glade | 40 ++--- 10 files changed, 360 insertions(+), 200 deletions(-) create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/__init__.py rename src/versions/solarfm-0.0.1/SolarFM/solarfm/{core/context_menu.py => widgets/context_menu_widget.py} (94%) create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/icon_grid_widget.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/icon_tree_widget.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/io_widget.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/tab_header.py diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py index 661f72e..c8d817e 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py @@ -7,12 +7,12 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk, GLib # Application imports +from widgets.context_menu_widget import ContextMenuWidget from .mixins.exception_hook_mixin import ExceptionHookMixin from .mixins.ui_mixin import UIMixin from .signals.ipc_signals_mixin import IPCSignalsMixin from .signals.keyboard_signals_mixin import KeyboardSignalsMixin from .controller_data import Controller_Data -from .context_menu import ContextMenu @@ -23,7 +23,7 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi self.setup_controller_data() self.generate_windows(self.fm_controller_data) - cm = ContextMenu() + cm = ContextMenuWidget() cm.build_context_menu() if args.no_plugins == "false": diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py index 486b579..57513cb 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py @@ -9,8 +9,9 @@ gi.require_version('Gdk', '3.0') from gi.repository import Gtk, Gdk, GLib, Gio, GdkPixbuf # Application imports - - +from widgets.tab_header import TabHeader +from widgets.icon_grid_widget import IconGridWidget +from widgets.icon_tree_widget import IconTreeWidget # NOTE: Consider trying to use Gtk.TreeView with css that turns it into a grid... @@ -47,7 +48,7 @@ class Icon(Gtk.HBox): class GridMixin: - """docstring for WidgetMixin""" + """docstring for GridMixin""" def load_store(self, tab, store, save_state=False): store.clear() @@ -95,127 +96,50 @@ class GridMixin: def create_tab_widget(self, tab): - tab_widget = Gtk.ButtonBox() - label = Gtk.Label() - tid = Gtk.Label() - close = Gtk.Button() - icon = Gtk.Image(stock=Gtk.STOCK_CLOSE) - - label.set_label(f"{tab.get_end_of_path()}") - label.set_width_chars(len(tab.get_end_of_path())) - label.set_xalign(0.0) - tid.set_label(f"{tab.get_id()}") - - close.add(icon) - tab_widget.add(label) - tab_widget.add(close) - tab_widget.add(tid) - - close.connect("released", self.close_tab) - tab_widget.show_all() - tid.hide() - return tab_widget + return TabHeader(tab, self.close_tab) def create_scroll_and_store(self, tab, wid, use_tree_view=False): + scroll = Gtk.ScrolledWindow() + if not use_tree_view: - scroll, store = self.create_icon_grid_widget(tab, wid) + grid = self.create_icon_grid_widget() else: # TODO: Fix global logic to make the below work too - scroll, store = self.create_icon_tree_widget(tab, wid) + grid = self.create_icon_tree_widget() - return scroll, store - - def create_icon_grid_widget(self, tab, wid): - scroll = Gtk.ScrolledWindow() - grid = Gtk.IconView() - store = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str or None) - - grid.set_model(store) - grid.set_pixbuf_column(0) - grid.set_text_column(1) - - grid.set_item_orientation(1) - grid.set_selection_mode(3) - grid.set_item_width(96) - grid.set_item_padding(8) - grid.set_margin(12) - grid.set_row_spacing(18) - grid.set_columns(-1) - grid.set_spacing(12) - grid.set_column_spacing(18) - - grid.connect("button_release_event", self.grid_icon_single_click) - grid.connect("item-activated", self.grid_icon_double_click) - grid.connect("selection-changed", self.grid_set_selected_items) - grid.connect("drag-data-get", self.grid_on_drag_set) - grid.connect("drag-data-received", self.grid_on_drag_data_received) - grid.connect("drag-motion", self.grid_on_drag_motion) - - URI_TARGET_TYPE = 80 - uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE) - targets = [ uri_target ] - action = Gdk.DragAction.COPY - grid.enable_model_drag_dest(targets, action) - grid.enable_model_drag_source(0, targets, action) - - grid.show_all() scroll.add(grid) - grid.set_name(f"{wid}|{tab.get_id()}") scroll.set_name(f"{wid}|{tab.get_id()}") + grid.set_name(f"{wid}|{tab.get_id()}") self.builder.expose_object(f"{wid}|{tab.get_id()}|icon_grid", grid) self.builder.expose_object(f"{wid}|{tab.get_id()}", scroll) - return scroll, store - def create_icon_tree_widget(self, tab, wid): - scroll = Gtk.ScrolledWindow() - grid = Gtk.TreeView() - store = Gtk.TreeStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str or None) - column = Gtk.TreeViewColumn("Icons") - icon = Gtk.CellRendererPixbuf() - name = Gtk.CellRendererText() - selec = grid.get_selection() + return scroll, grid.get_store() - grid.set_model(store) - selec.set_mode(3) - column.pack_start(icon, False) - column.pack_start(name, True) - column.add_attribute(icon, "pixbuf", 0) - column.add_attribute(name, "text", 1) - column.set_expand(False) - column.set_sizing(2) - column.set_min_width(120) - column.set_max_width(74) + def create_icon_grid_widget(self): + grid = IconGridWidget() + grid._setup_additional_signals( + self.grid_icon_single_click, + self.grid_icon_double_click, + self.grid_set_selected_items, + self.grid_on_drag_set, + self.grid_on_drag_data_received, + self.grid_on_drag_motion + ) - grid.append_column(column) - grid.set_search_column(1) - grid.set_rubber_banding(True) - grid.set_headers_visible(False) - grid.set_enable_tree_lines(False) + return grid - grid.connect("button_release_event", self.grid_icon_single_click) - grid.connect("row-activated", self.grid_icon_double_click) - grid.connect("drag-data-get", self.grid_on_drag_set) - grid.connect("drag-data-received", self.grid_on_drag_data_received) - grid.connect("drag-motion", self.grid_on_drag_motion) + def create_icon_tree_widget(self): + grid = IconTreeWidget() + grid._setup_additional_signals( + self.grid_icon_single_click, + self.grid_icon_double_click, + self.grid_on_drag_set, + self.grid_on_drag_data_received, + self.grid_on_drag_motion + ) - URI_TARGET_TYPE = 80 - uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE) - targets = [ uri_target ] - action = Gdk.DragAction.COPY - grid.enable_model_drag_dest(targets, action) - grid.enable_model_drag_source(0, targets, action) - - grid.show_all() - scroll.add(grid) - grid.set_name(f"{wid}|{tab.get_id()}") - scroll.set_name(f"{wid}|{tab.get_id()}") - self.builder.expose_object(f"{wid}|{tab.get_id()}|icon_grid", grid) - self.builder.expose_object(f"{wid}|{tab.get_id()}", scroll) grid.columns_autosize() - - self.builder.expose_object(f"{wid}|{tab.get_id()}", scroll) - return scroll, store - + return grid def get_store_and_label_from_notebook(self, notebook, _name): icon_grid = 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 735ef2e..62109b8 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 @@ -7,6 +7,7 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk, GObject, GLib, Gio # Application imports +from widgets.io_widget import IOWidget @@ -203,7 +204,7 @@ class WidgetFileActionMixin: self.show_new_file_menu(fname_field) if self.cancel_creation: - self.cancel_creation = False + self.cancel_creation = False return file_name = fname_field.get_text().strip() @@ -306,7 +307,6 @@ class WidgetFileActionMixin: file.make_directory(cancellable=None) continue - type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE) if type == Gio.FileType.DIRECTORY: wid, tid = self.fm_controller.get_active_wid_and_tid() @@ -320,79 +320,33 @@ class WidgetFileActionMixin: if action == "move" or action == "rename": tab.move_file(fPath, tPath) else: - if action == "copy": - container, cancle_eve, update_progress, finish_callback = self.create_io_widget(action, file) - file.copy_async(destination=target, flags=Gio.FileCopyFlags.BACKUP, - io_priority=98, cancellable=cancle_eve, - progress_callback=update_progress, callback=finish_callback) - self.builder.get_object("io_list").add(container) - if action == "move" or action == "rename": - container, cancle_eve, update_progress, finish_callback = self.create_io_widget(action, file) - file.move_async(destination=target, flags=Gio.FileCopyFlags.BACKUP, - io_priority=98, cancellable=cancle_eve, - progress_callback=None, callback=finish_callback) # NOTE: progress_callback causes seg fault when set - self.builder.get_object("io_list").add(container) + io_widget = IOWidget(action, file) + if action == "copy": + file.copy_async(destination=target, + flags=Gio.FileCopyFlags.BACKUP, + io_priority=98, + cancellable=io_widget.cancle_eve, + progress_callback=io_widget.update_progress, + callback=io_widget.finish_callback) + + self.builder.get_object("io_list").add(io_widget) + if action == "move" or action == "rename": + file.move_async(destination=target, + flags=Gio.FileCopyFlags.BACKUP, + io_priority=98, + cancellable=io_widget.cancle_eve, + progress_callback=None, + # NOTE: progress_callback here causes seg fault when set + callback=io_widget.finish_callback) + + self.builder.get_object("io_list").add(io_widget) except GObject.GError as e: raise OSError(e) self.exists_file_rename_bttn.set_sensitive(False) - def create_io_widget(self, action, file): - cancle_eve = Gio.Cancellable.new() - container = Gtk.Box() - stats = Gtk.Box() - label = Gtk.Label() - progress = Gtk.ProgressBar() - cncl_button = Gtk.Button(label="Cancel") - del_button = Gtk.Button(label="Clear") - io_list = self.builder.get_object("io_list") - label.set_label(file.get_basename()) - - progress.set_show_text(True) - progress.set_text(f"{action.upper()}ING") - - - def do_cancel(widget, container, eve): - print(f"Canceling: [{action}] of {file.get_basename()} ...") - eve.cancel() - - def update_progress(current, total, eve=None): - progress.set_fraction(current/total) - - def finish_callback(file, task=None, eve=None): - if action == "move": - status = file.move_finish(task) - if action == "copy": - status = file.copy_finish(task) - - if status: - self.builder.get_object("io_list").remove(container) - else: - print(f"{action} of {file.get_basename()} failed...") - - def delete_container(widget, eve): - io_list.remove(container) - - - stats.pack_end(del_button, False, False, 5) - del_button.connect("clicked", delete_container, ()) - - if not action in ("create", "rename"): - stats.pack_end(cncl_button, False, False, 5) - cncl_button.connect("clicked", do_cancel, *(container, cancle_eve)) - - container.set_orientation(1) - stats.set_orientation(0) - stats.add(progress) - - container.add(label) - container.add(stats) - container.show_all() - - return container, cancle_eve, update_progress, finish_callback - def setup_exists_data(self, from_file, to_file): from_info = from_file.query_info("standard::*,time::modified", 0, cancellable=None) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/__init__.py new file mode 100644 index 0000000..1678cd7 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/__init__.py @@ -0,0 +1,3 @@ +""" + Widgets module +""" diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/context_menu.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/context_menu_widget.py similarity index 94% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/core/context_menu.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/context_menu_widget.py index f6f6cec..875dbfc 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/context_menu.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/context_menu_widget.py @@ -9,9 +9,11 @@ from gi.repository import GLib # Application imports -class ContextMenu(Gtk.Menu): +class ContextMenuWidget(Gtk.Menu): + """docstring for ContextMenuWidget""" + def __init__(self): - super(ContextMenu, self).__init__() + super(ContextMenuWidget, self).__init__() self._builder = settings.get_builder() self._context_menu_data = settings.get_context_menu_data() self._window = settings.get_main_window() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/icon_grid_widget.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/icon_grid_widget.py new file mode 100644 index 0000000..47056ad --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/icon_grid_widget.py @@ -0,0 +1,72 @@ +# Python imports + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +gi.require_version('Gdk', '3.0') +from gi.repository import Gtk, Gdk, GdkPixbuf + + +# Application imports + + +class IconGridWidget(Gtk.IconView): + """docstring for IconGridWidget""" + + def __init__(self): + super(IconGridWidget, self).__init__() + + self._store = None + + self._setup_styling() + self._setup_signals() + self._set_up_dnd() + self._load_widgets() + + self.show_all() + + def get_store(self): + return self._store + + def _setup_styling(self): + self.set_pixbuf_column(0) + self.set_text_column(1) + + self.set_item_orientation(1) + self.set_selection_mode(3) + self.set_item_width(96) + self.set_item_padding(8) + self.set_margin(12) + self.set_row_spacing(18) + self.set_columns(-1) + self.set_spacing(12) + self.set_column_spacing(18) + + def _setup_signals(self): + ... + + def _setup_additional_signals(self, grid_icon_single_click, + grid_icon_double_click, + grid_set_selected_items, + grid_on_drag_set, + grid_on_drag_data_received, + grid_on_drag_motion): + + self.connect("button_release_event", grid_icon_single_click) + self.connect("item-activated", grid_icon_double_click) + self.connect("selection-changed", grid_set_selected_items) + self.connect("drag-data-get", grid_on_drag_set) + self.connect("drag-data-received", grid_on_drag_data_received) + self.connect("drag-motion", grid_on_drag_motion) + + def _load_widgets(self): + self._store = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str or None) + self.set_model(self._store) + + def _set_up_dnd(self): + URI_TARGET_TYPE = 80 + uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE) + targets = [ uri_target ] + action = Gdk.DragAction.COPY + self.enable_model_drag_dest(targets, action) + self.enable_model_drag_source(0, targets, action) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/icon_tree_widget.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/icon_tree_widget.py new file mode 100644 index 0000000..597dcd6 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/icon_tree_widget.py @@ -0,0 +1,79 @@ +# Python imports + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +gi.require_version('Gdk', '3.0') +from gi.repository import Gtk, Gdk, GdkPixbuf + +# Application imports + + +class IconTreeWidget(Gtk.TreeView): + """docstring for IconTreeWidget""" + + def __init__(self): + super(IconTreeWidget, self).__init__() + + self._store = None + + self._setup_styling() + self._setup_signals() + self._set_up_dnd() + self._load_widgets() + + self.show_all() + + def get_store(self): + return self._store + + def _setup_styling(self): + self.set_search_column(1) + self.set_rubber_banding(True) + self.set_headers_visible(False) + self.set_enable_tree_lines(False) + + def _setup_signals(self): + ... + + def _setup_additional_signals(self, grid_icon_single_click, + grid_icon_double_click, + grid_on_drag_set, + grid_on_drag_data_received, + grid_on_drag_motion): + + self.connect("button_release_event", self.grid_icon_single_click) + self.connect("row-activated", self.grid_icon_double_click) + self.connect("drag-data-get", self.grid_on_drag_set) + self.connect("drag-data-received", self.grid_on_drag_data_received) + self.connect("drag-motion", self.grid_on_drag_motion) + + def _load_widgets(self): + self._store = Gtk.TreeStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str or None) + column = Gtk.TreeViewColumn("Icons") + icon = Gtk.CellRendererPixbuf() + name = Gtk.CellRendererText() + selec = self.get_selection() + + self.set_model(store) + selec.set_mode(3) + + column.pack_start(icon, False) + column.pack_start(name, True) + column.add_attribute(icon, "pixbuf", 0) + column.add_attribute(name, "text", 1) + column.set_expand(False) + column.set_sizing(2) + column.set_min_width(120) + column.set_max_width(74) + + self.append_column(column) + + + def _set_up_dnd(self): + URI_TARGET_TYPE = 80 + uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE) + targets = [ uri_target ] + action = Gdk.DragAction.COPY + self.enable_model_drag_dest(targets, action) + self.enable_model_drag_source(0, targets, action) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/io_widget.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/io_widget.py new file mode 100644 index 0000000..08b320d --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/io_widget.py @@ -0,0 +1,78 @@ +# Python imports + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk, Gio + +# Application imports + + +class IOWidget(Gtk.Box): + """docstring for IOWidget""" + + def __init__(self, action, file): + super(IOWidget, self).__init__() + self._action = action + self._file = file + self._basename = self._file.get_basename() + + self.cancle_eve = Gio.Cancellable.new() + self.progress = None + + self._setup_styling() + self._setup_signals() + self._load_widgets() + + self.show_all() + + + def _setup_styling(self): + self.set_orientation(1) + + def _setup_signals(self): + ... + + def _load_widgets(self): + stats = Gtk.Box() + label = Gtk.Label() + cncl_button = Gtk.Button(label="Cancel") + del_button = Gtk.Button(label="Clear") + self.progress = Gtk.ProgressBar() + + label.set_label(self._basename) + self.progress.set_show_text(True) + self.progress.set_text(f"{self._action.upper()}ING") + stats.set_orientation(0) + + stats.pack_end(del_button, False, False, 5) + del_button.connect("clicked", self.delete_self, ()) + + if not self._action in ("create", "rename"): + stats.pack_end(cncl_button, False, False, 5) + cncl_button.connect("clicked", self.do_cancel, *(self, self.cancle_eve)) + + stats.add(self.progress) + self.add(label) + self.add(stats) + + def do_cancel(self, widget, container, eve): + print(f"Canceling: [{self._action}] of {self._basename} ...") + eve.cancel() + + def update_progress(self, current, total, eve=None): + self.progress.set_fraction(current/total) + + def finish_callback(self, file, task=None, eve=None): + if self._action == "move" and self._action == "rename": + status = self._file.move_finish(task) + if self._action == "copy": + status = self._file.copy_finish(task) + + if status: + self.delete_self() + else: + print(f"{self._action} of {self._basename} failed...") + + def delete_self(self, widget=None, eve=None): + self.get_parent().remove(self) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/tab_header.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/tab_header.py new file mode 100644 index 0000000..2e1cbe0 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/tab_header.py @@ -0,0 +1,48 @@ +# Python imports + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk, Gio + +# Application imports + + +class TabHeader(Gtk.ButtonBox): + """docstring for TabHeader""" + + def __init__(self, tab, close_tab): + super(TabHeader, self).__init__() + self._tab = tab + self._close_tab = close_tab # NOTE: Close method in tab_mixin + + self._setup_styling() + self._setup_signals() + self._load_widgets() + + def _setup_styling(self): + self.set_orientation(0) + + def _setup_signals(self): + ... + + def _load_widgets(self): + label = Gtk.Label() + tid = Gtk.Label() + close = Gtk.Button() + icon = Gtk.Image(stock=Gtk.STOCK_CLOSE) + + label.set_label(f"{self._tab.get_end_of_path()}") + label.set_width_chars(len(self._tab.get_end_of_path())) + label.set_xalign(0.0) + tid.set_label(f"{self._tab.get_id()}") + + close.connect("released", self._close_tab) + + close.add(icon) + self.add(label) + self.add(close) + self.add(tid) + + self.show_all() + tid.hide() diff --git a/user_config/usr/share/solarfm/Main_Window.glade b/user_config/usr/share/solarfm/Main_Window.glade index 3910294..80b33a2 100644 --- a/user_config/usr/share/solarfm/Main_Window.glade +++ b/user_config/usr/share/solarfm/Main_Window.glade @@ -828,22 +828,6 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False 5 start - - - I/O - True - True - True - io_img - True - - - - True - True - 0 - - Plugins @@ -855,7 +839,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe True True - 1 + 0 @@ -871,7 +855,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe True True - 2 + 1 @@ -887,7 +871,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe True True - 3 + 2 @@ -903,7 +887,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe True True - 4 + 3 @@ -916,6 +900,22 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe True + + True + True + 4 + + + + + I/O + True + True + True + io_img + True + + True True From f2090a7d460cd0b1467e9c937d9b6a1bdbfde59a Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Mon, 28 Nov 2022 22:34:13 -0600 Subject: [PATCH 30/30] Changing to pythonic imports, restructured mixins layout --- plugins/archiver/plugin.py | 6 ++- plugins/disk_usage/plugin.py | 5 ++- plugins/favorites/plugin.py | 4 +- plugins/file_properties/plugin.py | 11 ++++- plugins/movie_tv_info/plugin.py | 11 ++++- plugins/searcher/mixins/file_search_mixin.py | 9 +++- plugins/searcher/mixins/grep_search_mixin.py | 10 ++++- plugins/searcher/plugin.py | 8 +++- plugins/searcher/utils/search.py | 9 +++- .../searcher/widgets/grep_preview_widget.py | 3 +- plugins/template/plugin.py | 5 ++- plugins/trasher/plugin.py | 9 +++- plugins/vod_thumbnailer/plugin.py | 12 +++++- .../solarfm-0.0.1/SolarFM/solarfm/__main__.py | 4 +- .../solarfm-0.0.1/SolarFM/solarfm/app.py | 3 +- .../SolarFM/solarfm/core/controller.py | 14 +++---- .../SolarFM/solarfm/core/mixins/__init__.py | 2 +- .../core/mixins/exception_hook_mixin.py | 6 ++- .../solarfm/core/mixins/show_hide_mixin.py | 3 +- .../solarfm/core/mixins/signals/__init__.py | 3 ++ .../file_action_signals_mixin.py} | 15 ++++--- .../{ => mixins}/signals/ipc_signals_mixin.py | 3 +- .../signals/keyboard_signals_mixin.py | 5 ++- .../solarfm/core/mixins/signals_mixins.py | 15 +++++++ .../solarfm/core/mixins/ui/__init__.py | 2 +- .../solarfm/core/mixins/ui/grid_mixin.py | 41 ++++--------------- .../solarfm/core/mixins/ui/window_mixin.py | 3 +- .../SolarFM/solarfm/core/mixins/ui_mixin.py | 14 ------- .../SolarFM/solarfm/core/signals/__init__.py | 3 -- .../solarfm-0.0.1/SolarFM/solarfm/core/ui.py | 12 ++++++ .../SolarFM/solarfm/plugins/manifest.py | 3 +- .../SolarFM/solarfm/plugins/plugin_base.py | 3 +- .../solarfm/plugins/plugins_controller.py | 14 +++++-- .../SolarFM/solarfm/utils/ipc_server.py | 3 +- .../SolarFM/solarfm/utils/logger.py | 3 +- .../SolarFM/solarfm/utils/settings.py | 5 +-- .../solarfm/widgets/icon_grid_widget.py | 4 +- .../solarfm/widgets/icon_tree_widget.py | 4 +- .../SolarFM/solarfm/widgets/io_widget.py | 5 ++- .../{tab_header.py => tab_header_widget.py} | 9 ++-- 40 files changed, 190 insertions(+), 113 deletions(-) create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/signals/__init__.py rename src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/{ui/widget_file_action_mixin.py => signals/file_action_signals_mixin.py} (98%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/core/{ => mixins}/signals/ipc_signals_mixin.py (97%) rename src/versions/solarfm-0.0.1/SolarFM/solarfm/core/{ => mixins}/signals/keyboard_signals_mixin.py (97%) create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/signals_mixins.py delete mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui_mixin.py delete mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/__init__.py create mode 100644 src/versions/solarfm-0.0.1/SolarFM/solarfm/core/ui.py rename src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/{tab_header.py => tab_header_widget.py} (84%) diff --git a/plugins/archiver/plugin.py b/plugins/archiver/plugin.py index 393de1f..6b9b6b7 100644 --- a/plugins/archiver/plugin.py +++ b/plugins/archiver/plugin.py @@ -1,5 +1,9 @@ # Python imports -import os, threading, subprocess, inspect, shlex +import os +import threading +import subprocess +import inspect +import shlex # Lib imports import gi diff --git a/plugins/disk_usage/plugin.py b/plugins/disk_usage/plugin.py index 285c288..ec1c39d 100644 --- a/plugins/disk_usage/plugin.py +++ b/plugins/disk_usage/plugin.py @@ -1,5 +1,8 @@ # Python imports -import os, subprocess, time, inspect +import os +import subprocess +import time +import inspect # Lib imports import gi diff --git a/plugins/favorites/plugin.py b/plugins/favorites/plugin.py index ce068de..585f8b9 100644 --- a/plugins/favorites/plugin.py +++ b/plugins/favorites/plugin.py @@ -1,5 +1,7 @@ # Python imports -import os, inspect, json +import os +import inspect +import json # Lib imports import gi diff --git a/plugins/file_properties/plugin.py b/plugins/file_properties/plugin.py index e28d1e3..bb1be45 100644 --- a/plugins/file_properties/plugin.py +++ b/plugins/file_properties/plugin.py @@ -1,11 +1,18 @@ # Python imports -import os, threading, subprocess, time, pwd, grp +import os +import threading +import subprocess +import time +import pwd +import grp from datetime import datetime # Lib imports import gi gi.require_version('Gtk', '3.0') -from gi.repository import Gtk, GLib, Gio +from gi.repository import Gtk +from gi.repository import GLib +from gi.repository import Gio # Application imports from plugins.plugin_base import PluginBase diff --git a/plugins/movie_tv_info/plugin.py b/plugins/movie_tv_info/plugin.py index f16f282..0856437 100644 --- a/plugins/movie_tv_info/plugin.py +++ b/plugins/movie_tv_info/plugin.py @@ -1,11 +1,18 @@ # Python imports -import os, threading, subprocess, inspect, requests, shutil +import os +import threading +import subprocess +import inspect +import requests +import shutil # Lib imports import gi gi.require_version('Gtk', '3.0') gi.require_version('GdkPixbuf', '2.0') -from gi.repository import Gtk, GLib, GdkPixbuf +from gi.repository import Gtk +from gi.repository import GLib +from gi.repository import GdkPixbuf # Application imports from plugins.plugin_base import PluginBase diff --git a/plugins/searcher/mixins/file_search_mixin.py b/plugins/searcher/mixins/file_search_mixin.py index 6f17f08..a72be13 100644 --- a/plugins/searcher/mixins/file_search_mixin.py +++ b/plugins/searcher/mixins/file_search_mixin.py @@ -1,10 +1,15 @@ # Python imports -import threading, subprocess, signal, json, shlex +import threading +import subprocess +import signal +import json +import shlex # Lib imports import gi gi.require_version('Gtk', '3.0') -from gi.repository import Gtk, GLib +from gi.repository import Gtk +from gi.repository import GLib # Application imports from ..widgets.file_preview_widget import FilePreviewWidget diff --git a/plugins/searcher/mixins/grep_search_mixin.py b/plugins/searcher/mixins/grep_search_mixin.py index a7590c1..9f7f1d9 100644 --- a/plugins/searcher/mixins/grep_search_mixin.py +++ b/plugins/searcher/mixins/grep_search_mixin.py @@ -1,11 +1,17 @@ # Python imports -import ctypes, threading, subprocess, signal, json, shlex +import ctypes +import threading +import subprocess +import signal +import json +import shlex libgcc_s = ctypes.CDLL('libgcc_s.so.1') # Lib imports import gi gi.require_version('Gtk', '3.0') -from gi.repository import Gtk, GLib +from gi.repository import Gtk +from gi.repository import GLib # Application imports from ..widgets.grep_preview_widget import GrepPreviewWidget diff --git a/plugins/searcher/plugin.py b/plugins/searcher/plugin.py index a154805..774a113 100644 --- a/plugins/searcher/plugin.py +++ b/plugins/searcher/plugin.py @@ -1,10 +1,14 @@ # Python imports -import os, threading, inspect, time +import os +import threading +import inspect +import time # Lib imports import gi gi.require_version('Gtk', '3.0') -from gi.repository import Gtk, GLib +from gi.repository import Gtk +from gi.repository import GLib # Application imports from plugins.plugin_base import PluginBase diff --git a/plugins/searcher/utils/search.py b/plugins/searcher/utils/search.py index be0b31c..5d833be 100755 --- a/plugins/searcher/utils/search.py +++ b/plugins/searcher/utils/search.py @@ -2,7 +2,14 @@ # Python imports -import os, traceback, argparse, threading, json, base64, time, pickle +import os +import traceback +import argparse +import threading +import json +import base64 +import time +import pickle from setproctitle import setproctitle from multiprocessing.connection import Client diff --git a/plugins/searcher/widgets/grep_preview_widget.py b/plugins/searcher/widgets/grep_preview_widget.py index 4e6b799..79a34d6 100644 --- a/plugins/searcher/widgets/grep_preview_widget.py +++ b/plugins/searcher/widgets/grep_preview_widget.py @@ -1,5 +1,6 @@ # Python imports -import base64, re +import base64 +import re # Lib imports import gi diff --git a/plugins/template/plugin.py b/plugins/template/plugin.py index 5535f4d..89822ef 100644 --- a/plugins/template/plugin.py +++ b/plugins/template/plugin.py @@ -1,5 +1,8 @@ # Python imports -import os, threading, subprocess, time +import os +import threading +import subprocess +import ime # Lib imports import gi diff --git a/plugins/trasher/plugin.py b/plugins/trasher/plugin.py index 69618d9..c0e4ca9 100644 --- a/plugins/trasher/plugin.py +++ b/plugins/trasher/plugin.py @@ -1,10 +1,15 @@ # Python imports -import os, threading, subprocess, inspect +import os +import threading +import subprocess +import inspect # Lib imports import gi gi.require_version('Gtk', '3.0') -from gi.repository import Gtk, GLib, Gio +from gi.repository import Gtk +from gi.repository import GLib +from gi.repository import Gio # Application imports from plugins.plugin_base import PluginBase diff --git a/plugins/vod_thumbnailer/plugin.py b/plugins/vod_thumbnailer/plugin.py index 45e08e2..9e02c14 100644 --- a/plugins/vod_thumbnailer/plugin.py +++ b/plugins/vod_thumbnailer/plugin.py @@ -1,12 +1,20 @@ # Python imports -import os, threading, subprocess, time, inspect, hashlib +import os +import threading +import subprocess +import time +import inspect +import hashlib from datetime import datetime # Gtk imports import gi gi.require_version('Gtk', '3.0') gi.require_version('GdkPixbuf', '2.0') -from gi.repository import Gtk, GLib, Gio, GdkPixbuf +from gi.repository import Gtk +from gi.repository import GLib +from gi.repository import Gio +from gi.repository import GdkPixbuf # Application imports from plugins.plugin_base import PluginBase diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py index 91e1fd0..2ac63e7 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__main__.py @@ -1,7 +1,9 @@ #!/usr/bin/python3 # Python imports -import argparse, faulthandler, traceback +import argparse +import faulthandler +import traceback from setproctitle import setproctitle import tracemalloc 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 82436d4..19eb2c6 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py @@ -1,5 +1,6 @@ # Python imports -import os, inspect +import os +import inspect # Lib imports diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py index c8d817e..eca52ad 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py @@ -7,24 +7,22 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk, GLib # Application imports -from widgets.context_menu_widget import ContextMenuWidget -from .mixins.exception_hook_mixin import ExceptionHookMixin -from .mixins.ui_mixin import UIMixin -from .signals.ipc_signals_mixin import IPCSignalsMixin -from .signals.keyboard_signals_mixin import KeyboardSignalsMixin from .controller_data import Controller_Data +from .mixins.signals_mixins import SignalsMixins +from .ui import UI +from widgets.context_menu_widget import ContextMenuWidget -class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMixin, Controller_Data): + +class Controller(UI, SignalsMixins, Controller_Data): """ Controller coordinates the mixins and is somewhat the root hub of it all. """ def __init__(self, args, unknownargs): self._subscribe_to_events() self.setup_controller_data() self.generate_windows(self.fm_controller_data) - cm = ContextMenuWidget() - cm.build_context_menu() + ContextMenuWidget().build_context_menu() if args.no_plugins == "false": self.plugins.launch_plugins() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/__init__.py index ded9abc..636992f 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/__init__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/__init__.py @@ -1,3 +1,3 @@ """ -Mixins module + Mixins module """ diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/exception_hook_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/exception_hook_mixin.py index 4ec0b0a..b05dc50 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/exception_hook_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/exception_hook_mixin.py @@ -1,10 +1,12 @@ # Python imports -import traceback, time +import traceback +import time # Lib imports import gi gi.require_version('Gtk', '3.0') -from gi.repository import Gtk, GLib +from gi.repository import Gtk +from gi.repository import GLib # Application imports 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 f968121..389b783 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 @@ -4,7 +4,8 @@ import gi gi.require_version('Gtk', '3.0') gi.require_version('Gdk', '3.0') -from gi.repository import Gtk, Gdk +from gi.repository import Gtk +from gi.repository import Gdk # Application imports diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/signals/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/signals/__init__.py new file mode 100644 index 0000000..03c3ec2 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/signals/__init__.py @@ -0,0 +1,3 @@ +""" + Signals module +""" 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/signals/file_action_signals_mixin.py similarity index 98% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/widget_file_action_mixin.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/signals/file_action_signals_mixin.py index 62109b8..2d27ce4 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/signals/file_action_signals_mixin.py @@ -1,19 +1,24 @@ # Python imports -import os, time, shlex +import os +import time +import shlex # Lib imports import gi gi.require_version('Gtk', '3.0') -from gi.repository import Gtk, GObject, GLib, Gio +from gi.repository import Gtk +from gi.repository import GObject +from gi.repository import GLib +from gi.repository import Gio + # Application imports from widgets.io_widget import IOWidget - -class WidgetFileActionMixin: - """docstring for WidgetFileActionMixin""" +class FileActionSignalsMixin: + """docstring for FileActionSignalsMixin""" def sizeof_fmt(self, num, suffix="B"): for unit in ["", "K", "M", "G", "T", "Pi", "Ei", "Zi"]: diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/ipc_signals_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/signals/ipc_signals_mixin.py similarity index 97% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/ipc_signals_mixin.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/signals/ipc_signals_mixin.py index 3aeb267..1d97b20 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/ipc_signals_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/signals/ipc_signals_mixin.py @@ -5,11 +5,12 @@ # Application imports + + class IPCSignalsMixin: """ IPCSignalsMixin handle messages from another starting solarfm process. """ def print_to_console(self, message=None): - print(self) print(message) def handle_file_from_ipc(self, path): 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/mixins/signals/keyboard_signals_mixin.py similarity index 97% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/keyboard_signals_mixin.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/signals/keyboard_signals_mixin.py index 00062a6..9f8222c 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/mixins/signals/keyboard_signals_mixin.py @@ -5,7 +5,8 @@ import re import gi gi.require_version('Gtk', '3.0') gi.require_version('Gdk', '3.0') -from gi.repository import Gtk, Gdk +from gi.repository import Gtk +from gi.repository import Gdk # Application imports @@ -13,6 +14,8 @@ from gi.repository import Gtk, Gdk valid_keyvalue_pat = re.compile(r"[a-z0-9A-Z-_\[\]\(\)\| ]") + + class KeyboardSignalsMixin: """ KeyboardSignalsMixin keyboard hooks controller. """ diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/signals_mixins.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/signals_mixins.py new file mode 100644 index 0000000..d2de706 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/signals_mixins.py @@ -0,0 +1,15 @@ +# Python imports + +# Lib imports +from .exception_hook_mixin import ExceptionHookMixin +from .signals.file_action_signals_mixin import FileActionSignalsMixin +from .signals.ipc_signals_mixin import IPCSignalsMixin +from .signals.keyboard_signals_mixin import KeyboardSignalsMixin + +# Application imports + + + + +class SignalsMixins(FileActionSignalsMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMixin): + ... diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/__init__.py index 991f9a2..afa061a 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/__init__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/__init__.py @@ -1,3 +1,3 @@ """ -UI module + UI module """ diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py index 57513cb..7b1295f 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/grid_mixin.py @@ -6,45 +6,18 @@ import gi gi.require_version("Gtk", "3.0") gi.require_version('Gdk', '3.0') -from gi.repository import Gtk, Gdk, GLib, Gio, GdkPixbuf +from gi.repository import Gtk +from gi.repository import Gdk +from gi.repository import GLib +from gi.repository import Gio +from gi.repository import GdkPixbuf # Application imports -from widgets.tab_header import TabHeader +from widgets.tab_header_widget import TabHeaderWidget from widgets.icon_grid_widget import IconGridWidget from widgets.icon_tree_widget import IconTreeWidget -# NOTE: Consider trying to use Gtk.TreeView with css that turns it into a grid... -# Can possibly use this to dynamicly load icons instead... -class Icon(Gtk.HBox): - def __init__(self, tab, dir, file): - super(Icon, self).__init__() - - self.load_icon(tab, dir, file) - - @threaded - def load_icon(self, tab, dir, file): - icon = tab.create_icon(dir, file) - - if not icon: - path = f"{dir}/{file}" - icon = self.get_system_thumbnail(path, tab.sys_icon_wh[0]) - - if not icon: - icon = GdkPixbuf.Pixbuf.new_from_file(tab.DEFAULT_ICON) - - self.add(Gtk.Image.new_from_pixbuf(icon)) - self.show_all() - - def get_system_thumbnail(self, file, size): - try: - gio_file = Gio.File.new_for_path(file) - info = gio_file.query_info('standard::icon' , 0, None) - icon = info.get_icon().get_names()[0] - icon_path = self.icon_theme.lookup_icon(icon , size , 0).get_filename() - return GdkPixbuf.Pixbuf.new_from_file(icon_path) - except Exception as e: - return None class GridMixin: @@ -96,7 +69,7 @@ class GridMixin: def create_tab_widget(self, tab): - return TabHeader(tab, self.close_tab) + return TabHeaderWidget(tab, self.close_tab) def create_scroll_and_store(self, tab, wid, use_tree_view=False): scroll = Gtk.ScrolledWindow() diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py index af76f29..2db0fd9 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py @@ -6,7 +6,8 @@ from os.path import isdir # Lib imports import gi gi.require_version('Gdk', '3.0') -from gi.repository import Gdk, Gio +from gi.repository import Gdk +from gi.repository import Gio # Application imports from .tab_mixin import TabMixin diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui_mixin.py deleted file mode 100644 index 6d61e3e..0000000 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui_mixin.py +++ /dev/null @@ -1,14 +0,0 @@ -# Python imports - -# Gtk imports - -# Application imports -from .show_hide_mixin import ShowHideMixin -from .ui.widget_file_action_mixin import WidgetFileActionMixin -from .ui.pane_mixin import PaneMixin -from .ui.window_mixin import WindowMixin -from .show_hide_mixin import ShowHideMixin - - -class UIMixin(WidgetFileActionMixin, PaneMixin, WindowMixin, ShowHideMixin): - ... diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/__init__.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/__init__.py deleted file mode 100644 index c2a7368..0000000 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/signals/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -Signals module -""" diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/ui.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/ui.py new file mode 100644 index 0000000..fe3b33b --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/ui.py @@ -0,0 +1,12 @@ +# Python imports + +# Gtk imports + +# Application imports +from .mixins.show_hide_mixin import ShowHideMixin +from .mixins.ui.pane_mixin import PaneMixin +from .mixins.ui.window_mixin import WindowMixin + + +class UI(PaneMixin, WindowMixin, ShowHideMixin): + ... diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/manifest.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/manifest.py index 3d23ecf..3caf564 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/manifest.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/manifest.py @@ -1,5 +1,6 @@ # Python imports -import os, json +import os +import json from os.path import join # Lib imports diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugin_base.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugin_base.py index 44aec62..e00c65a 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugin_base.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugin_base.py @@ -1,5 +1,6 @@ # Python imports -import os, time +import os +import time # Lib imports diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins_controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins_controller.py index 41adcef..f8678b8 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins_controller.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins_controller.py @@ -1,14 +1,20 @@ # Python imports -import os, sys, importlib, traceback -from os.path import join, isdir +import os +import sys +import importlib +import traceback +from os.path import join +from os.path import isdir # Lib imports import gi gi.require_version('Gtk', '3.0') -from gi.repository import Gtk, Gio +from gi.repository import Gtk +from gi.repository import Gio # Application imports -from .manifest import PluginInfo, ManifestProcessor +from .manifest import PluginInfo +from .manifest import ManifestProcessor diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py index f1dbd4f..c64b468 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/ipc_server.py @@ -1,6 +1,7 @@ # Python imports import os, threading, time -from multiprocessing.connection import Listener, Client +from multiprocessing.connection import Client +from multiprocessing.connection import Listener # Lib imports diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/logger.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/logger.py index c33444f..436a511 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/logger.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/utils/logger.py @@ -1,5 +1,6 @@ # Python imports -import os, logging +import os +import logging # Application imports 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 80b8dac..d4229a8 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 @@ -1,16 +1,15 @@ # Python imports -import os, json +import os +import json from os import path # Gtk imports import gi, cairo gi.require_version('Gtk', '3.0') gi.require_version('Gdk', '3.0') - from gi.repository import Gtk from gi.repository import Gdk - # Application imports from .logger import Logger from .keybindings import Keybindings diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/icon_grid_widget.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/icon_grid_widget.py index 47056ad..9000a02 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/icon_grid_widget.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/icon_grid_widget.py @@ -4,7 +4,9 @@ import gi gi.require_version('Gtk', '3.0') gi.require_version('Gdk', '3.0') -from gi.repository import Gtk, Gdk, GdkPixbuf +from gi.repository import Gtk +from gi.repository import Gdk +from gi.repository import GdkPixbuf # Application imports diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/icon_tree_widget.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/icon_tree_widget.py index 597dcd6..416796e 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/icon_tree_widget.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/icon_tree_widget.py @@ -4,7 +4,9 @@ import gi gi.require_version('Gtk', '3.0') gi.require_version('Gdk', '3.0') -from gi.repository import Gtk, Gdk, GdkPixbuf +from gi.repository import Gtk +from gi.repository import Gdk +from gi.repository import GdkPixbuf # Application imports diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/io_widget.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/io_widget.py index 08b320d..0b54e35 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/io_widget.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/io_widget.py @@ -3,7 +3,8 @@ # Lib imports import gi gi.require_version('Gtk', '3.0') -from gi.repository import Gtk, Gio +from gi.repository import Gtk +from gi.repository import Gio # Application imports @@ -64,7 +65,7 @@ class IOWidget(Gtk.Box): self.progress.set_fraction(current/total) def finish_callback(self, file, task=None, eve=None): - if self._action == "move" and self._action == "rename": + if self._action == "move" or self._action == "rename": status = self._file.move_finish(task) if self._action == "copy": status = self._file.copy_finish(task) diff --git a/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/tab_header.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/tab_header_widget.py similarity index 84% rename from src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/tab_header.py rename to src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/tab_header_widget.py index 2e1cbe0..42be39f 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/tab_header.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/tab_header_widget.py @@ -3,16 +3,17 @@ # Lib imports import gi gi.require_version('Gtk', '3.0') -from gi.repository import Gtk, Gio +from gi.repository import Gtk +from gi.repository import Gio # Application imports -class TabHeader(Gtk.ButtonBox): - """docstring for TabHeader""" +class TabHeaderWidget(Gtk.ButtonBox): + """docstring for TabHeaderWidget""" def __init__(self, tab, close_tab): - super(TabHeader, self).__init__() + super(TabHeaderWidget, self).__init__() self._tab = tab self._close_tab = close_tab # NOTE: Close method in tab_mixin