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/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/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..5d9fc1b --- /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_plugins", + "pass_fm_events": "true" + } + } +} diff --git a/plugins/archiver/plugin.py b/plugins/archiver/plugin.py new file mode 100644 index 0000000..6b9b6b7 --- /dev/null +++ b/plugins/archiver/plugin.py @@ -0,0 +1,129 @@ +# Python imports +import os +import threading +import subprocess +import inspect +import 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") + + 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 + + + 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/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..ec1c39d --- /dev/null +++ b/plugins/disk_usage/plugin.py @@ -0,0 +1,94 @@ +# Python imports +import os +import subprocess +import time +import inspect + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk + +# Application imports +from plugins.plugin_base import PluginBase + + + + +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/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/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 e586de7..585f8b9 100644 --- a/plugins/favorites/plugin.py +++ b/plugins/favorites/plugin.py @@ -1,5 +1,7 @@ # Python imports -import os, threading, subprocess, time, inspect, json +import os +import inspect +import json # Lib imports import gi @@ -10,19 +12,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): @@ -30,7 +19,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" @@ -38,18 +27,10 @@ class Plugin(PluginBase): self._favorites_dialog = None self._favorites_store = None self._favorites = None - self._state = None 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._module_event_observer() - self._builder = Gtk.Builder() self._builder.add_from_file(self._GLADE_FILE) @@ -73,35 +54,43 @@ 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('[]') + self._event_system.subscribe("show_favorites_menu", self._show_favorites_menu) + + + 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.push_gui_event([self.name, "get_current_state", ()]) - self.wait_for_fm_message() + self._event_system.emit("get_current_state") - 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() - 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): @@ -109,13 +98,12 @@ 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() 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() @@ -125,9 +113,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 - - 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..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 @@ -83,14 +90,7 @@ 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._module_event_observer() - self._builder = Gtk.Builder() self._builder.add_from_file(self._GLADE_FILE) @@ -105,14 +105,19 @@ 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): + 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 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.emit("get_current_state") - 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/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 3a4a1c5..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 @@ -39,22 +46,13 @@ 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 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._module_event_observer() - self._builder = Gtk.Builder() self._builder.add_from_file(self._GLADE_FILE) @@ -78,21 +76,27 @@ 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): + 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): - self._event_system.push_gui_event([self.name, "get_current_state", ()]) - self.wait_for_fm_message() + self._event_system.emit("get_current_state") - 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,10 +115,11 @@ 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 ] + trailer = None try: title = _title.split("(")[0].strip() @@ -140,7 +145,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/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/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/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..a72be13 --- /dev/null +++ b/plugins/searcher/mixins/file_search_mixin.py @@ -0,0 +1,79 @@ +# Python imports +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 +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) + + # 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 + self.pause_fifo_update = True + self.search_query = "" + + # NOTE: Kill the former process + if self._list_proc: + if self._list_proc.poll() == None: + self._list_proc.terminate() + while self._list_proc.poll() == None: + ... + + self._list_proc = None + + # NOTE: Clear children from ui and make sure ui thread redraws + GLib.idle_add(self.reset_file_list_box) + + # NOTE: If query create new process and do all new loop. + 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): + 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): + Gtk.main_iteration() + + if not data in ("", None): + 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..9f7f1d9 --- /dev/null +++ b/plugins/searcher/mixins/grep_search_mixin.py @@ -0,0 +1,83 @@ +# Python imports +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 +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) + + # 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 + self.pause_fifo_update = True + self.grep_query = "" + + # NOTE: Kill the former process + if self._grep_proc: + if self._grep_proc.poll() == None: + self._grep_proc.terminate() + while self._grep_proc.poll() == None: + ... + + self._grep_proc = None + + # NOTE: Clear children from ui and make sure ui thread redraws + GLib.idle_add(self.reset_grep_box) + + # NOTE: If query create new process and do all new loop. + if query: + self.pause_fifo_update = False + GLib.idle_add(self._exec_grep_query, query) + + def _exec_grep_query(self, widget=None, eve=None): + query = widget.get_text() + if not query.strip() 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): + Gtk.main_iteration() + + 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, self.grep_query) + self._grep_list.add(widget) diff --git a/plugins/searcher/plugin.py b/plugins/searcher/plugin.py index 3ace6ba..774a113 100644 --- a/plugins/searcher/plugin.py +++ b/plugins/searcher/plugin.py @@ -1,14 +1,21 @@ # Python imports -import os, multiprocessing, threading, subprocess, inspect, time, json -from multiprocessing import Manager, Process +import os +import threading +import inspect +import time # Lib imports import gi gi.require_version('Gtk', '3.0') -from gi.repository import Gtk, GLib, GObject +from gi.repository import Gtk +from gi.repository import GLib # Application imports from plugins.plugin_base import PluginBase +from .mixins.file_search_mixin import FileSearchMixin +from .mixins.grep_search_mixin import GrepSearchMixin +from .utils.ipc_server import IPCServer + # NOTE: Threads WILL NOT die with parent's destruction. @@ -26,56 +33,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" - - - _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] - 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() - - - -manager = Manager() -grep_result_set = manager.dict() -file_result_set = manager.list() - - -class Plugin(PluginBase): +class Plugin(IPCServer, FileSearchMixin, GrepSearchMixin, PluginBase): def __init__(self): super().__init__() @@ -86,20 +44,19 @@ class Plugin(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 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): - button = Gtk.Button(label=self.name) - button.connect("button-release-event", self._show_grep_list_page) - return button - def run(self): - self._module_event_observer() - self._builder = Gtk.Builder() self._builder.add_from_file(self._GLADE_FILE) @@ -116,116 +73,65 @@ class Plugin(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") - 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.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) + self._event_system.subscribe("show_search_page", self._show_page) - @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.create_ipc_listener() - state = self._event_message + def generate_reference_ui_element(self): + 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): + 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) - - query = widget.get_text() - if query: - self._list_proc = multiprocessing.Process(self._do_list_search(self._active_path, query)) - self._list_proc.start() - - 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)) - - def _load_file_ui(self, parent=None, widget=None): - self._file_list.add(widget) - - def _file_traverse_path(self, path, query): + # TODO: Merge the below methods into some unified logic + def reset_grep_box(self) -> None: 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...") + child = self.grep_list_parent.get_children()[0] + self._grep_list = None + self.grep_list_parent.remove(child) + except Exception: + ... + 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 _run_grep_query(self, widget=None, eve=None): - if self._grep_proc: - self._grep_proc.terminate() - self._grep_proc = None - time.sleep(.2) + time.sleep(0.05) + Gtk.main_iteration() - grep_result_set.clear() - self.clear_children(self._grep_list) - - query = widget.get_text() - if query: - self._grep_proc = multiprocessing.Process(self._do_grep_search(self._active_path, query)) - self._grep_proc.start() - - def _do_grep_search(self, path, query): - self._grep_traverse_path(path, query) - - 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)) - - def _load_grep_ui(self, parent=None, widget=None): - self._grep_list.add(widget) - - def _grep_traverse_path(self, path, query): + def reset_file_list_box(self) -> None: 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...") + child = self.file_list_parent.get_children()[0] + self._file_list = None + self.file_list_parent.remove(child) + except Exception: + ... - 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...") + 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() + + time.sleep(0.05) + Gtk.main_iteration() diff --git a/plugins/searcher/search_dialog.glade b/plugins/searcher/search_dialog.glade index 47fc1bc..8578e51 100644 --- a/plugins/searcher/search_dialog.glade +++ b/plugins/searcher/search_dialog.glade @@ -74,15 +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 @@ -102,7 +128,7 @@ True False - + True False vertical @@ -140,15 +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 @@ -168,12 +220,10 @@ True False - + True False vertical - 5 - top diff --git a/plugins/searcher/utils/ipc_server.py b/plugins/searcher/utils/ipc_server.py new file mode 100644 index 0000000..e4ca2aa --- /dev/null +++ b/plugins/searcher/utils/ipc_server.py @@ -0,0 +1,90 @@ +# Python imports +import os, threading, pickle +from multiprocessing.connection import Listener, Client + +# Lib imports +from gi.repository import 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() + + if not self.pause_fifo_update: + self.handle_message(conn) + else: + conn.close() + + listener.close() + + def handle_message(self, conn) -> None: + while True: + msg = conn.recv() + + if "SEARCH|" in msg: + file = msg.split("SEARCH|")[1].strip() + if 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, priority=GLib.PRIORITY_LOW) + + + 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/utils/search.py b/plugins/searcher/utils/search.py new file mode 100755 index 0000000..5d833be --- /dev/null +++ b/plugins/searcher/utils/search.py @@ -0,0 +1,152 @@ +#!/usr/bin/python3 + + +# Python imports +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 + +# Lib imports + +# Application imports + + + + +_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") + + +# 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: + 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.05) + + +def file_search(path, query): + try: + 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() + +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, '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. + 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 + + 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: + 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: + ... + + + +@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): + target = os.path.join(path, file) + if os.path.isdir(target): + grep_search(target, query) + else: + if target.lower().endswith(filter): + size = os.path.getsize(target) + if not 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.lower()) + + if args.type == "grep_search": + grep_search(args.dir, args.query.lower().encode("utf-8")) + + +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/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..79a34d6 --- /dev/null +++ b/plugins/searcher/widgets/grep_preview_widget.py @@ -0,0 +1,60 @@ +# Python imports +import base64 +import re + +# 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, _grep_query): + super(GrepPreviewWidget, self).__init__() + + 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() + 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(line_color, key) + itr = buffer.get_end_iter() + buffer.insert_markup(itr, line_num, len(line_num)) + + 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) + 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") + + 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/plugins/template/plugin.py b/plugins/template/plugin.py index 08c2b72..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 @@ -33,14 +36,14 @@ 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 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.emit("display_message", ("warning", message, None)) 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..0657d65 --- /dev/null +++ b/plugins/trasher/manifest.json @@ -0,0 +1,16 @@ +{ + "manifest": { + "name": "Trasher", + "author": "ITDominator", + "version": "0.0.1", + "support": "", + "requests": { + "ui_target": "context_menu", + "pass_fm_events": "true", + "bind_keys": [ + "Trasher||delete_files:Delete", + "Trasher||trash_files:d" + ] + } + } +} diff --git a/plugins/trasher/plugin.py b/plugins/trasher/plugin.py new file mode 100644 index 0000000..c0e4ca9 --- /dev/null +++ b/plugins/trasher/plugin.py @@ -0,0 +1,143 @@ +# Python imports +import os +import threading +import subprocess +import inspect + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk +from gi.repository import GLib +from gi.repository import Gio + +# Application imports +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 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): + trash_a = Gtk.MenuItem("Trash Actions") + trash_menu = Gtk.Menu() + + self.restore = Gtk.MenuItem("Restore From Trash") + self.restore.connect("activate", self.restore_trash_files) + + self.empty = Gtk.MenuItem("Empty Trash") + self.empty.connect("activate", self.empty_trash) + + 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) + + 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.restore.show() + self.empty.show() + + def _hide_trash_buttons(self): + self.restore.hide() + self.empty.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 + 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("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/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/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/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 4a8abad..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 @@ -42,17 +50,9 @@ class Plugin(PluginBase): self._file_name = None self._file_location = None self._file_hash = None - self._state = 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._module_event_observer() - self._builder = Gtk.Builder() self._builder.add_from_file(self._GLADE_FILE) @@ -75,23 +75,32 @@ 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): + 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(self.name) + + item.set_image( icon ) + item.connect("activate", self._show_thumbnailer_page) + item.set_always_show_image(True) + return item + @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.emit("get_current_state") - 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 +112,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..1b4ce50 100644 --- a/plugins/youtube_download/plugin.py +++ b/plugins/youtube_download/plugin.py @@ -33,21 +33,21 @@ 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 def run(self): - self._module_event_observer() + ... + def _do_download(self, widget=None, eve=None): + self._event_system.emit("get_current_state") + + 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..a96c67f 100644 --- a/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/__builtins__.py @@ -5,7 +5,8 @@ import builtins, threading # Application imports from utils.event_system import EventSystem - +from utils.endpoint_registry import EndpointRegistry +from utils.settings import Settings @@ -23,32 +24,14 @@ 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" +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..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,8 +1,9 @@ #!/usr/bin/python3 - # Python imports -import argparse, faulthandler, traceback +import argparse +import faulthandler +import traceback from setproctitle import setproctitle import tracemalloc @@ -15,25 +16,43 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk # Application imports +from __builtins__ import * from app import Application -if __name__ == "__main__": - """ Set process title, get arguments, and create GTK main thread. """ + +def run(): try: - setproctitle('SolarFM') + setproctitle(f"{app_name}") faulthandler.enable() # For better debug info 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("--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() + 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: 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/app.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/app.py index cd89d84..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,52 +1,51 @@ # Python imports -import os, inspect, time +import os +import inspect # Lib imports # Application imports -from __builtins__ import * + from utils.ipc_server import IPCServer -from utils.settings import Settings from core.controller import Controller -class App_Launch_Exception(Exception): +class AppLaunchException(Exception): ... -class Controller_Start_Exceptio(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__() + self.args, self.unknownargs = args, unknownargs - if not trace_debug: - self.create_ipc_listener() - time.sleep(0.05) + if not settings.is_trace_debug(): + 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 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"{app_name} IPC Server Exists: Will send path(s) to it and close...") - settings = Settings() settings.create_window() + self._load_controller_and_builder() - controller = Controller(args, unknownargs, settings) + def _load_controller_and_builder(self): + controller = Controller(self.args, self.unknownargs) if not controller: - raise Controller_Start_Exceptio("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. @@ -57,7 +56,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/controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/controller.py index e844325..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,80 +7,58 @@ gi.require_version('Gtk', '3.0') from gi.repository import Gtk, GLib # Application imports -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, _settings): - self.setup_controller_data(_settings) - self.window.show() - + def __init__(self, args, unknownargs): + self._subscribe_to_events() + self.setup_controller_data() self.generate_windows(self.fm_controller_data) - self.plugins.launch_plugins() - if debug: - self.window.set_interactive_debugging(True) + ContextMenuWidget().build_context_menu() - if not trace_debug: - self.gui_event_observer() + if args.no_plugins == "false": + self.plugins.launch_plugins() - if unknownargs: - for arg in unknownargs: - if os.path.isdir(arg): - message = f"FILE|{arg}" - event_system.send_ipc_message(message) + for arg in unknownargs + [args.new_tab,]: + 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}" - event_system.send_ipc_message(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) + 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): - self.fm_controller.save_state() + if not settings.is_trace_debug(): + self.fm_controller.save_state() + + settings.clear_pid() 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) 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) @@ -101,13 +79,13 @@ 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() 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 @@ -120,8 +98,12 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMi gc.collect() - def do_action_from_menu_controls(self, widget, event_button): - action = widget.get_name() + def do_action_from_menu_controls(self, widget, eve = None): + 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() @@ -142,18 +124,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 == "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"]: @@ -162,7 +132,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() @@ -189,3 +158,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 6181d1f..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 @@ -4,12 +4,13 @@ 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 -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) @@ -22,25 +23,22 @@ class State: selected_files: [] = None to_copy_files: [] = None to_cut_files: [] = None + warning_alert: type = None 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.trashman = XDGTrash() self.fm_controller = WindowController() - self.plugins = Plugins(_settings) + self.plugins = PluginsController() self.fm_controller_data = self.fm_controller.get_state_from_file() - self.trashman.regenerate() - 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") @@ -66,39 +64,14 @@ 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() - - # 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.icon_theme = settings.get_icon_theme() self.notebooks = [self.window1, self.window2, self.window3, self.window4] self.selected_files = [] 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 @@ -107,9 +80,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 @@ -118,14 +88,18 @@ 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) 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: ''' @@ -142,7 +116,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() if selected_files: @@ -154,6 +128,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.emit("update_state_info_plugins", state) return state 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 660b9b6..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 @@ -51,8 +53,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..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 @@ -55,26 +56,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") @@ -103,10 +84,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: @@ -135,6 +116,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/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 79% 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 9072990..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,18 +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"]: @@ -40,8 +46,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 +65,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]) @@ -102,24 +108,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): @@ -143,19 +152,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") @@ -208,51 +204,12 @@ 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) if self.cancel_creation: - self.cancel_creation = False + self.cancel_creation = False return file_name = fname_field.get_text().strip() @@ -355,7 +312,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() @@ -369,10 +325,27 @@ class WidgetFileActionMixin: if action == "move" or action == "rename": tab.move_file(fPath, tPath) else: + io_widget = IOWidget(action, file) + if action == "copy": - file.copy(target, flags=Gio.FileCopyFlags.BACKUP, cancellable=None) + 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(target, flags=Gio.FileCopyFlags.BACKUP, cancellable=None) + 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) @@ -428,11 +401,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/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 72% 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 e5f70e8..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. """ @@ -21,7 +24,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() @@ -52,30 +54,18 @@ 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: + if settings.is_debug(): print(f"on_global_key_release_controller > key > {keyname}") if self.ctrl_down: 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) def keyboard_close_tab(self): wid, tid = self.fm_controller.get_active_wid_and_tid() @@ -88,5 +78,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/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 2d3afeb..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,48 +6,22 @@ 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_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: - """docstring for WidgetMixin""" + """docstring for GridMixin""" def load_store(self, tab, store, save_state=False): store.clear() @@ -61,7 +35,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 @@ -86,133 +60,59 @@ 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): - 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 TabHeaderWidget(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/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..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 @@ -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: @@ -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()) @@ -63,9 +64,14 @@ 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() + # 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) @@ -80,7 +86,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 +122,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 +135,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/window_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui/window_mixin.py index 1d22380..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,12 +6,17 @@ 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 +class WindowException(Exception): + ... + + class WindowMixin(TabMixin): """docstring for WindowMixin""" @@ -20,8 +25,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) @@ -46,7 +51,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: @@ -88,12 +93,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}") @@ -108,8 +112,8 @@ class WindowMixin(TabMixin): cancellable=None) file_size = file_info.get_size() combined_size += file_size - except Exception as e: - if debug: + except WindowException as e: + if settings.is_debug(): print(repr(e)) @@ -156,10 +160,31 @@ class WindowMixin(TabMixin): path_entry.set_text(tab.get_current_directory()) def grid_set_selected_items(self, icons_grid): - self.selected_files = icons_grid.get_selected_items() + items = icons_grid.get_selected_items() + size = len(items) - def grid_cursor_toggled(self, icons_grid): - print("wat...") + 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 + + 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: @@ -169,14 +194,16 @@ 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.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 self.show_context_menu() - except Exception as e: + except WindowException as e: print(repr(e)) self.display_message(self.error_color, f"{repr(e)}") @@ -205,7 +232,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/mixins/ui_mixin.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/core/mixins/ui_mixin.py deleted file mode 100644 index d127b28..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): - pass 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 e53c0c5..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 @@ -56,7 +57,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/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..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,11 +1,16 @@ # Python imports -import os, time +import os +import time # Lib imports # 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 @@ -13,43 +18,48 @@ 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): + """ + 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 wait_for_fm_message(self): - while not self._event_message: - pass 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) - @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 + + 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.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/plugins/plugins_controller.py similarity index 75% 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..f8678b8 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 @@ -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 @@ -17,17 +23,16 @@ class InvalidPluginException(Exception): ... -class Plugins: - """Plugins controller""" +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 = [] @@ -72,20 +77,30 @@ class Plugins: 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() 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: @@ -93,6 +108,7 @@ class Plugins: 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/shellfm/windows/controller.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/controller.py index abec786..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 @@ -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: @@ -176,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/icons/icon.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/shellfm/windows/tabs/icons/icon.py index 3a18857..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 @@ -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 @@ -35,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" @@ -54,24 +64,41 @@ 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 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) + 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 + 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: @@ -79,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/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..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 != "": @@ -50,13 +60,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/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/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/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/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}" 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/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..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 @@ -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 emit(self, event_type, data = None): + if event_type in self.subscribers: + for fn in self.subscribers[event_type]: + if data: + if hasattr(data, '__iter__') and not type(data) is str: + 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..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 @@ -29,12 +30,16 @@ 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: 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: @@ -44,23 +49,27 @@ 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 debug: + if settings.is_debug(): print(msg) 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.emit("handle_file_from_ipc", file) conn.close() break 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 3826e3d..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 @@ -31,6 +30,8 @@ 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() if not os.path.exists(self._CONFIG_PATH): @@ -40,6 +41,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): @@ -58,11 +61,54 @@ 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() self._builder.add_from_file(self._GLADE_FILE) + self._trace_debug = False + self._debug = False + self._dirty_start = False + + + def do_dirty_start_check(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(f"{app_name} is starting dirty...") + self._dirty_start = True + self._write_new_pid() + return + + print("PID is alive... Let downstream errors (sans debug args) handle app closure propigation.") + + 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: # Get window and connect signals @@ -101,6 +147,8 @@ 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 @@ -111,3 +159,15 @@ 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 is_dirty_start(self) -> bool: return self._dirty_start + def clear_pid(self): self._clean_pid() + + + def set_trace_debug(self, trace_debug): + self._trace_debug = trace_debug + + def set_debug(self, debug): + self._debug = debug 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/widgets/context_menu_widget.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/context_menu_widget.py new file mode 100644 index 0000000..875dbfc --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/context_menu_widget.py @@ -0,0 +1,68 @@ +# 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 ContextMenuWidget(Gtk.Menu): + """docstring for ContextMenuWidget""" + + def __init__(self): + super(ContextMenuWidget, 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/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..9000a02 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/icon_grid_widget.py @@ -0,0 +1,74 @@ +# Python imports + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +gi.require_version('Gdk', '3.0') +from gi.repository import Gtk +from gi.repository import Gdk +from gi.repository import 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..416796e --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/icon_tree_widget.py @@ -0,0 +1,81 @@ +# Python imports + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +gi.require_version('Gdk', '3.0') +from gi.repository import Gtk +from gi.repository import Gdk +from gi.repository import 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..0b54e35 --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/io_widget.py @@ -0,0 +1,79 @@ +# Python imports + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk +from gi.repository import 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" or 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_widget.py b/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/tab_header_widget.py new file mode 100644 index 0000000..42be39f --- /dev/null +++ b/src/versions/solarfm-0.0.1/SolarFM/solarfm/widgets/tab_header_widget.py @@ -0,0 +1,49 @@ +# Python imports + +# Lib imports +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gtk +from gi.repository import Gio + +# Application imports + + +class TabHeaderWidget(Gtk.ButtonBox): + """docstring for TabHeaderWidget""" + + def __init__(self, tab, close_tab): + super(TabHeaderWidget, 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 2bf3bd8..80b33a2 100644 --- a/user_config/usr/share/solarfm/Main_Window.glade +++ b/user_config/usr/share/solarfm/Main_Window.glade @@ -1,5 +1,5 @@ - + @@ -452,180 +452,11 @@ 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 gtk-new - - True - False - gtk-execute - True False @@ -651,23 +482,18 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False gtk-execute - - + True False - gtk-open + gtk-stop + True False gtk-edit 3 - - True - False - gtk-edit - False dialog @@ -961,17 +787,6 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe - - - gtk-delete - delete - True - False - True - True - - - @@ -1014,7 +829,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe 5 start - + Plugins True True @@ -1091,6 +906,22 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe 4 + + + I/O + True + True + True + io_img + True + + + + True + True + 5 + + False @@ -1243,6 +1074,8 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe 5 False True + sfm_windows + @@ -1263,6 +1096,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 @@ -1281,6 +1129,8 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe 5 False True + sfm_windows + @@ -1301,6 +1151,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 @@ -1333,6 +1198,8 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe 5 False True + sfm_windows + @@ -1353,6 +1220,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 @@ -1370,6 +1252,8 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe 5 False True + sfm_windows + @@ -1390,6 +1274,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 @@ -1954,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 @@ -2221,7 +2137,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe False - plugins_buttoin + plugins_button @@ -2235,503 +2151,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 - - - - - - - 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 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": {} +} 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 0000000..cccf50a Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/3g2.png differ diff --git a/user_config/usr/share/solarfm/fileicons/3gp.png b/user_config/usr/share/solarfm/fileicons/3gp.png new file mode 100644 index 0000000..b3fb117 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/3gp.png differ 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 0000000..ddb172f Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/ai.png differ 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 0000000..076f08e Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/air.png differ 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 0000000..b700cf4 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/asf.png differ 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 0000000..f4436f7 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/avi.png differ diff --git a/user_config/usr/share/solarfm/fileicons/bib.png b/user_config/usr/share/solarfm/fileicons/bib.png new file mode 100644 index 0000000..2789ca5 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/bib.png differ 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 0000000..4759ad6 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/cls.png differ 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 0000000..869e354 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/csv.png differ 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 0000000..e5581ad Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/deb.png differ 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 0000000..f3ed05d Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/djvu.png differ 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 0000000..b52c92c Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/dmg.png differ 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 0000000..8f615d1 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/doc.png differ 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 0000000..377ecc7 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/docx.png differ 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 0000000..349610c Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/dwf.png differ diff --git a/user_config/usr/share/solarfm/fileicons/dwg.png b/user_config/usr/share/solarfm/fileicons/dwg.png new file mode 100644 index 0000000..5398b08 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/dwg.png differ 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 0000000..10f19eb Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/eps.png differ 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 0000000..6f8a256 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/epub.png differ diff --git a/user_config/usr/share/solarfm/fileicons/exe.png b/user_config/usr/share/solarfm/fileicons/exe.png new file mode 100644 index 0000000..0910322 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/exe.png differ 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 0000000..7cfb90a Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/f.png differ 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 0000000..752fa8c Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/f77.png differ 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 0000000..32c9feb Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/f90.png differ diff --git a/user_config/usr/share/solarfm/fileicons/flac.png b/user_config/usr/share/solarfm/fileicons/flac.png new file mode 100644 index 0000000..b529135 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/flac.png differ 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 0000000..c4e35d5 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/flv.png differ 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 0000000..7cd9773 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/gif.png differ 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 0000000..987d4f0 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/gz.png differ 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 0000000..b33287e Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/ico.png differ 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 0000000..24389f0 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/indd.png differ diff --git a/user_config/usr/share/solarfm/fileicons/iso.png b/user_config/usr/share/solarfm/fileicons/iso.png new file mode 100644 index 0000000..de2a19f Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/iso.png differ 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 0000000..b1ba768 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/jpeg.png differ diff --git a/user_config/usr/share/solarfm/fileicons/jpg.png b/user_config/usr/share/solarfm/fileicons/jpg.png new file mode 100644 index 0000000..b1ba768 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/jpg.png differ diff --git a/user_config/usr/share/solarfm/fileicons/log.png b/user_config/usr/share/solarfm/fileicons/log.png new file mode 100644 index 0000000..c1acea1 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/log.png differ 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 0000000..f8f3ada Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/m4a.png differ diff --git a/user_config/usr/share/solarfm/fileicons/m4v.png b/user_config/usr/share/solarfm/fileicons/m4v.png new file mode 100644 index 0000000..fef795b Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/m4v.png differ 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 0000000..85132d5 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/midi.png differ diff --git a/user_config/usr/share/solarfm/fileicons/mkv.png b/user_config/usr/share/solarfm/fileicons/mkv.png new file mode 100644 index 0000000..b0b1f92 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/mkv.png differ 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 0000000..9799d32 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/mov.png differ 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 0000000..18394f5 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/mp3.png differ diff --git a/user_config/usr/share/solarfm/fileicons/mp4.png b/user_config/usr/share/solarfm/fileicons/mp4.png new file mode 100644 index 0000000..b34c7d5 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/mp4.png differ diff --git a/user_config/usr/share/solarfm/fileicons/mpeg.png b/user_config/usr/share/solarfm/fileicons/mpeg.png new file mode 100644 index 0000000..eb58ef5 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/mpeg.png differ diff --git a/user_config/usr/share/solarfm/fileicons/mpg.png b/user_config/usr/share/solarfm/fileicons/mpg.png new file mode 100644 index 0000000..eb58ef5 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/mpg.png differ diff --git a/user_config/usr/share/solarfm/fileicons/msi.png b/user_config/usr/share/solarfm/fileicons/msi.png new file mode 100644 index 0000000..95fe7d7 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/msi.png differ 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 0000000..69f8663 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/odp.png differ diff --git a/user_config/usr/share/solarfm/fileicons/ods.png b/user_config/usr/share/solarfm/fileicons/ods.png new file mode 100644 index 0000000..8f415a9 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/ods.png differ 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 0000000..5e10765 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/odt.png differ 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 0000000..c236464 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/oga.png differ 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 0000000..1f70cb8 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/ogg.png differ diff --git a/user_config/usr/share/solarfm/fileicons/ogv.png b/user_config/usr/share/solarfm/fileicons/ogv.png new file mode 100644 index 0000000..027dfe9 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/ogv.png differ 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 0000000..867f287 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/pdf.png differ diff --git a/user_config/usr/share/solarfm/fileicons/png.png b/user_config/usr/share/solarfm/fileicons/png.png new file mode 100644 index 0000000..9433c35 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/png.png differ diff --git a/user_config/usr/share/solarfm/fileicons/pps.png b/user_config/usr/share/solarfm/fileicons/pps.png new file mode 100644 index 0000000..f75d9b6 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/pps.png differ 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 0000000..a9fd94b Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/ppsx.png differ 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 0000000..0cb28ba Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/ppt.png differ 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 0000000..d1d7785 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/pptx.png differ 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 0000000..568684b Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/psd.png differ 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 0000000..ff27076 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/pub.png differ 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 0000000..4fadf04 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/py.png differ diff --git a/user_config/usr/share/solarfm/fileicons/qt.png b/user_config/usr/share/solarfm/fileicons/qt.png new file mode 100644 index 0000000..839742f Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/qt.png differ 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 0000000..0103372 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/ra.png differ 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 0000000..ffdfa05 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/ram.png differ diff --git a/user_config/usr/share/solarfm/fileicons/rar.png b/user_config/usr/share/solarfm/fileicons/rar.png new file mode 100644 index 0000000..96a5cd0 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/rar.png differ diff --git a/user_config/usr/share/solarfm/fileicons/rm.png b/user_config/usr/share/solarfm/fileicons/rm.png new file mode 100644 index 0000000..ae680c2 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/rm.png differ 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 0000000..5f3d622 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/rpm.png differ 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 0000000..c26ede1 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/rtf.png differ diff --git a/user_config/usr/share/solarfm/fileicons/rv.png b/user_config/usr/share/solarfm/fileicons/rv.png new file mode 100644 index 0000000..d7d46b5 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/rv.png differ diff --git a/user_config/usr/share/solarfm/fileicons/skp.png b/user_config/usr/share/solarfm/fileicons/skp.png new file mode 100644 index 0000000..778f0e3 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/skp.png differ diff --git a/user_config/usr/share/solarfm/fileicons/spx.png b/user_config/usr/share/solarfm/fileicons/spx.png new file mode 100644 index 0000000..4f3d7a1 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/spx.png differ 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 0000000..bf6c1ab Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/sql.png differ 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 0000000..5512ae8 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/sty.png differ diff --git a/user_config/usr/share/solarfm/fileicons/tar.png b/user_config/usr/share/solarfm/fileicons/tar.png new file mode 100644 index 0000000..dda5cea Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/tar.png differ 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 0000000..36bf00e Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/tex.png differ diff --git a/user_config/usr/share/solarfm/fileicons/tgz.png b/user_config/usr/share/solarfm/fileicons/tgz.png new file mode 100644 index 0000000..651d0b1 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/tgz.png differ diff --git a/user_config/usr/share/solarfm/fileicons/tiff.png b/user_config/usr/share/solarfm/fileicons/tiff.png new file mode 100644 index 0000000..b780ffa Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/tiff.png differ diff --git a/user_config/usr/share/solarfm/fileicons/ttf.png b/user_config/usr/share/solarfm/fileicons/ttf.png new file mode 100644 index 0000000..842f566 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/ttf.png differ diff --git a/user_config/usr/share/solarfm/fileicons/txt.png b/user_config/usr/share/solarfm/fileicons/txt.png new file mode 100644 index 0000000..cbae3ce Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/txt.png differ 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 0000000..70cc91d Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/vob.png differ 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 0000000..1dfa320 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/wav.png differ 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 0000000..c120508 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/wmv.png differ 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 0000000..cf5a2d0 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/xls.png differ 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 0000000..454fd5d Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/xlsx.png differ 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 0000000..609f131 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/xml.png differ diff --git a/user_config/usr/share/solarfm/fileicons/xpi.png b/user_config/usr/share/solarfm/fileicons/xpi.png new file mode 100644 index 0000000..fd479a6 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/xpi.png differ 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 0000000..8caadb2 Binary files /dev/null and b/user_config/usr/share/solarfm/fileicons/zip.png differ 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", 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"],