From 642f96f9ca93fc77d64296ed10c256453429550e Mon Sep 17 00:00:00 2001
From: itdominator <1itdominator@gmail.com>
Date: Sun, 23 Apr 2023 12:06:16 -0500
Subject: [PATCH] WIP refactor
---
src/__builtins__.py | 73 -------
src/new-src/__builtins__.py | 43 +++++
src/new-src/__init__.py | 3 +
src/new-src/__main__.py | 53 ++++++
src/new-src/app.py | 35 ++++
src/{context => new-src/core}/__init__.py | 0
src/new-src/core/containers/__init__.py | 3 +
src/new-src/core/containers/base_container.py | 31 +++
src/new-src/core/controller.py | 69 +++++++
src/new-src/core/controller_data.py | 69 +++++++
src/new-src/core/mixins/__init__.py | 3 +
src/new-src/core/mixins/signals/__init__.py | 3 +
.../core/mixins/signals/ipc_signals_mixin.py | 20 ++
.../mixins/signals/keyboard_signals_mixin.py | 94 +++++++++
src/new-src/core/mixins/signals_mixins.py | 13 ++
src/new-src/core/widgets/__init__.py | 3 +
src/new-src/core/window.py | 95 ++++++++++
src/new-src/plugins/__init__.py | 3 +
src/new-src/plugins/manifest.py | 64 +++++++
src/new-src/plugins/plugin_base.py | 61 ++++++
src/new-src/plugins/plugins_controller.py | 119 ++++++++++++
src/{ => new-src}/utils/__init__.py | 0
src/new-src/utils/endpoint_registry.py | 22 +++
src/new-src/utils/event_system.py | 54 ++++++
src/new-src/utils/ipc_server.py | 114 +++++++++++
src/new-src/utils/keybindings.py | 127 +++++++++++++
src/new-src/utils/logger.py | 61 ++++++
src/new-src/utils/settings/__init__.py | 4 +
src/new-src/utils/settings/settings.py | 179 ++++++++++++++++++
.../utils/settings/start_check_mixin.py | 50 +++++
src/new-src/utils/singleton.py | 24 +++
src/old-src/__builtins__.py | 53 ++++++
src/{ => old-src}/__init__.py | 0
src/{ => old-src}/__main__.py | 31 ++-
src/{ => old-src}/app.py | 8 +-
src/old-src/context/__init__.py | 3 +
src/{ => old-src}/context/controller.py | 0
src/{ => old-src}/context/controller_data.py | 0
src/{ => old-src}/context/icons/__init__.py | 0
src/{ => old-src}/context/icons/icon.py | 0
.../context/icons/mixins/__init__.py | 0
.../context/icons/mixins/desktopiconmixin.py | 0
.../context/icons/mixins/videoiconmixin.py | 0
.../context/icons/mixins/xdg/BaseDirectory.py | 0
.../context/icons/mixins/xdg/Config.py | 0
.../context/icons/mixins/xdg/DesktopEntry.py | 0
.../context/icons/mixins/xdg/Exceptions.py | 0
.../context/icons/mixins/xdg/IconTheme.py | 0
.../context/icons/mixins/xdg/IniFile.py | 0
.../context/icons/mixins/xdg/Locale.py | 0
.../context/icons/mixins/xdg/Menu.py | 0
.../context/icons/mixins/xdg/MenuEditor.py | 0
.../context/icons/mixins/xdg/Mime.py | 0
.../context/icons/mixins/xdg/RecentFiles.py | 0
.../context/icons/mixins/xdg/__init__.py | 0
.../context/icons/mixins/xdg/util.py | 0
src/{ => old-src}/context/mixins/__init__.py | 0
.../context/mixins/tree_view_update_mixin.py | 0
src/{ => old-src}/context/view.py | 0
src/old-src/utils/__init__.py | 3 +
src/old-src/utils/endpoint_registry.py | 22 +++
src/old-src/utils/event_system.py | 54 ++++++
src/{ => old-src/utils}/ipc_server.py | 3 +-
src/old-src/utils/keybindings.py | 138 ++++++++++++++
src/old-src/utils/logger.py | 59 ++++++
src/{ => old-src}/utils/settings.py | 5 +-
src/old-src/utils/singleton.py | 23 +++
src/utils/logger.py | 56 ------
user_config/bin/mirage2 | 23 +++
user_config/usr/applications/mirage2.desktop | 9 +
.../usr/share/mirage2/Main_Window.glade | 146 --------------
.../usr/share/mirage2/contexct_menu.json | 2 +
.../usr/share/mirage2/key-bindings.json | 23 +++
user_config/usr/share/mirage2/settings.json | 40 ++++
74 files changed, 1901 insertions(+), 292 deletions(-)
delete mode 100644 src/__builtins__.py
create mode 100644 src/new-src/__builtins__.py
create mode 100644 src/new-src/__init__.py
create mode 100644 src/new-src/__main__.py
create mode 100644 src/new-src/app.py
rename src/{context => new-src/core}/__init__.py (100%)
create mode 100644 src/new-src/core/containers/__init__.py
create mode 100644 src/new-src/core/containers/base_container.py
create mode 100644 src/new-src/core/controller.py
create mode 100644 src/new-src/core/controller_data.py
create mode 100644 src/new-src/core/mixins/__init__.py
create mode 100644 src/new-src/core/mixins/signals/__init__.py
create mode 100644 src/new-src/core/mixins/signals/ipc_signals_mixin.py
create mode 100644 src/new-src/core/mixins/signals/keyboard_signals_mixin.py
create mode 100644 src/new-src/core/mixins/signals_mixins.py
create mode 100644 src/new-src/core/widgets/__init__.py
create mode 100644 src/new-src/core/window.py
create mode 100644 src/new-src/plugins/__init__.py
create mode 100644 src/new-src/plugins/manifest.py
create mode 100644 src/new-src/plugins/plugin_base.py
create mode 100644 src/new-src/plugins/plugins_controller.py
rename src/{ => new-src}/utils/__init__.py (100%)
create mode 100644 src/new-src/utils/endpoint_registry.py
create mode 100644 src/new-src/utils/event_system.py
create mode 100644 src/new-src/utils/ipc_server.py
create mode 100644 src/new-src/utils/keybindings.py
create mode 100644 src/new-src/utils/logger.py
create mode 100644 src/new-src/utils/settings/__init__.py
create mode 100644 src/new-src/utils/settings/settings.py
create mode 100644 src/new-src/utils/settings/start_check_mixin.py
create mode 100644 src/new-src/utils/singleton.py
create mode 100644 src/old-src/__builtins__.py
rename src/{ => old-src}/__init__.py (100%)
rename src/{ => old-src}/__main__.py (53%)
rename src/{ => old-src}/app.py (91%)
create mode 100644 src/old-src/context/__init__.py
rename src/{ => old-src}/context/controller.py (100%)
rename src/{ => old-src}/context/controller_data.py (100%)
rename src/{ => old-src}/context/icons/__init__.py (100%)
rename src/{ => old-src}/context/icons/icon.py (100%)
rename src/{ => old-src}/context/icons/mixins/__init__.py (100%)
rename src/{ => old-src}/context/icons/mixins/desktopiconmixin.py (100%)
rename src/{ => old-src}/context/icons/mixins/videoiconmixin.py (100%)
rename src/{ => old-src}/context/icons/mixins/xdg/BaseDirectory.py (100%)
rename src/{ => old-src}/context/icons/mixins/xdg/Config.py (100%)
rename src/{ => old-src}/context/icons/mixins/xdg/DesktopEntry.py (100%)
rename src/{ => old-src}/context/icons/mixins/xdg/Exceptions.py (100%)
rename src/{ => old-src}/context/icons/mixins/xdg/IconTheme.py (100%)
rename src/{ => old-src}/context/icons/mixins/xdg/IniFile.py (100%)
rename src/{ => old-src}/context/icons/mixins/xdg/Locale.py (100%)
rename src/{ => old-src}/context/icons/mixins/xdg/Menu.py (100%)
rename src/{ => old-src}/context/icons/mixins/xdg/MenuEditor.py (100%)
rename src/{ => old-src}/context/icons/mixins/xdg/Mime.py (100%)
rename src/{ => old-src}/context/icons/mixins/xdg/RecentFiles.py (100%)
rename src/{ => old-src}/context/icons/mixins/xdg/__init__.py (100%)
rename src/{ => old-src}/context/icons/mixins/xdg/util.py (100%)
rename src/{ => old-src}/context/mixins/__init__.py (100%)
rename src/{ => old-src}/context/mixins/tree_view_update_mixin.py (100%)
rename src/{ => old-src}/context/view.py (100%)
create mode 100644 src/old-src/utils/__init__.py
create mode 100644 src/old-src/utils/endpoint_registry.py
create mode 100644 src/old-src/utils/event_system.py
rename src/{ => old-src/utils}/ipc_server.py (97%)
create mode 100644 src/old-src/utils/keybindings.py
create mode 100644 src/old-src/utils/logger.py
rename src/{ => old-src}/utils/settings.py (94%)
create mode 100644 src/old-src/utils/singleton.py
delete mode 100644 src/utils/logger.py
create mode 100755 user_config/bin/mirage2
create mode 100644 user_config/usr/applications/mirage2.desktop
delete mode 100644 user_config/usr/share/mirage2/Main_Window.glade
create mode 100644 user_config/usr/share/mirage2/contexct_menu.json
create mode 100644 user_config/usr/share/mirage2/key-bindings.json
create mode 100644 user_config/usr/share/mirage2/settings.json
diff --git a/src/__builtins__.py b/src/__builtins__.py
deleted file mode 100644
index 6bcd9b1..0000000
--- a/src/__builtins__.py
+++ /dev/null
@@ -1,73 +0,0 @@
-import builtins
-
-# Python imports
-import builtins
-
-# Lib imports
-
-# Application imports
-from ipc_server import IPCServer
-
-
-
-class EventSystem(IPCServer):
- """Docstring for __builtins__ extender"""
-
- def __init__(self):
- super(EventSystem, self).__init__()
- # NOTE: The format used is list of [type, target, data] Where:
- # type is useful context for control flow,
- # target is the method to call,
- # data is the method parameters to give
- # Where data may be any kind of data
- self._gui_events = []
- self._module_events = []
-
-
- # Makeshift fake "events" type system FIFO
- def _pop_gui_event(self):
- if len(self._gui_events) > 0:
- return self._gui_events.pop(0)
- return None
-
- def _pop_module_event(self):
- if len(self._module_events) > 0:
- return self._module_events.pop(0)
- return None
-
-
- def push_gui_event(self, event):
- if len(event) == 3:
- self._gui_events.append(event)
- return None
-
- raise Exception("Invald event format! Please do: [type, target, data]")
-
- def push_module_event(self, event):
- if len(event) == 3:
- self._module_events.append(event)
- return None
-
- raise Exception("Invald event format! Please do: [type, target, data]")
-
- def read_gui_event(self):
- return self._gui_events[0]
-
- def read_module_event(self):
- return self._module_events[0]
-
- def consume_gui_event(self):
- return self._pop_gui_event()
-
- def consume_module_event(self):
- return self._pop_module_event()
-
-
-
-# NOTE: Just reminding myself we can add to builtins two different ways...
-# __builtins__.update({"event_system": Builtins()})
-builtins.app_name = "Mirage2"
-builtins.event_system = EventSystem()
-builtins.event_sleep_time = 0.2
-builtins.debug = False
-builtins.trace_debug = False
diff --git a/src/new-src/__builtins__.py b/src/new-src/__builtins__.py
new file mode 100644
index 0000000..3963435
--- /dev/null
+++ b/src/new-src/__builtins__.py
@@ -0,0 +1,43 @@
+# Python imports
+import builtins
+import threading
+
+# Lib imports
+
+# Application imports
+from utils.event_system import EventSystem
+from utils.endpoint_registry import EndpointRegistry
+from utils.keybindings import Keybindings
+from utils.logger import Logger
+from utils.settings import Settings
+
+
+
+# NOTE: Threads WILL NOT die with parent's destruction.
+def threaded_wrapper(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_wrapper(fn):
+ def wrapper(*args, **kwargs):
+ threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start()
+ return wrapper
+
+
+
+# NOTE: Just reminding myself we can add to builtins two different ways...
+# __builtins__.update({"event_system": Builtins()})
+builtins.app_name = "Mirage2"
+builtins.keybindings = Keybindings()
+builtins.event_system = EventSystem()
+builtins.endpoint_registry = EndpointRegistry()
+builtins.settings = Settings()
+builtins.logger = Logger(settings.get_home_config_path(), \
+ _ch_log_lvl=settings.get_ch_log_lvl(), \
+ _fh_log_lvl=settings.get_fh_log_lvl()).get_logger()
+
+builtins.threaded = threaded_wrapper
+builtins.daemon_threaded = daemon_threaded_wrapper
+builtins.event_sleep_time = 0.05
diff --git a/src/new-src/__init__.py b/src/new-src/__init__.py
new file mode 100644
index 0000000..90dc8da
--- /dev/null
+++ b/src/new-src/__init__.py
@@ -0,0 +1,3 @@
+"""
+ Start of package.
+"""
diff --git a/src/new-src/__main__.py b/src/new-src/__main__.py
new file mode 100644
index 0000000..065a783
--- /dev/null
+++ b/src/new-src/__main__.py
@@ -0,0 +1,53 @@
+#!/usr/bin/python3
+
+# Python imports
+import argparse
+import faulthandler
+import traceback
+from setproctitle import setproctitle
+
+import tracemalloc
+tracemalloc.start()
+
+# Lib imports
+import gi
+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. '''
+
+ try:
+ 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", "-nt", default="false", help="Opens a 'New Tab' if a handler is set for it.")
+ parser.add_argument("--file", "-f", default="default", help="JUST SOME FILE ARG.")
+
+ # 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()
diff --git a/src/new-src/app.py b/src/new-src/app.py
new file mode 100644
index 0000000..85809d6
--- /dev/null
+++ b/src/new-src/app.py
@@ -0,0 +1,35 @@
+# Python imports
+import os
+
+# Lib imports
+
+# Application imports
+from utils.ipc_server import IPCServer
+from core.window import Window
+
+
+class AppLaunchException(Exception):
+ ...
+
+
+
+class Application(IPCServer):
+ """ docstring for Application. """
+
+ def __init__(self, args, unknownargs):
+ super(Application, self).__init__()
+ if not settings.is_trace_debug():
+ try:
+ self.create_ipc_listener()
+ except Exception:
+ ...
+
+ if not self.is_ipc_alive:
+ for arg in unknownargs + [args.new_tab,]:
+ if os.path.isfile(arg):
+ message = f"FILE|{arg}"
+ self.send_ipc_message(message)
+
+ raise AppLaunchException(f"{app_name} IPC Server Exists: Will send path(s) to it and close...")
+
+ Window(args, unknownargs)
diff --git a/src/context/__init__.py b/src/new-src/core/__init__.py
similarity index 100%
rename from src/context/__init__.py
rename to src/new-src/core/__init__.py
diff --git a/src/new-src/core/containers/__init__.py b/src/new-src/core/containers/__init__.py
new file mode 100644
index 0000000..4efd4b9
--- /dev/null
+++ b/src/new-src/core/containers/__init__.py
@@ -0,0 +1,3 @@
+"""
+ Containers Module
+"""
diff --git a/src/new-src/core/containers/base_container.py b/src/new-src/core/containers/base_container.py
new file mode 100644
index 0000000..1999ac8
--- /dev/null
+++ b/src/new-src/core/containers/base_container.py
@@ -0,0 +1,31 @@
+# Python imports
+
+# Lib imports
+import gi
+gi.require_version('Gtk', '3.0')
+from gi.repository import Gtk
+
+# Application imports
+
+
+
+
+class BaseContainer(Gtk.Box):
+ def __init__(self):
+ super(BaseContainer, self).__init__()
+
+ 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):
+ ...
diff --git a/src/new-src/core/controller.py b/src/new-src/core/controller.py
new file mode 100644
index 0000000..0483b1f
--- /dev/null
+++ b/src/new-src/core/controller.py
@@ -0,0 +1,69 @@
+# Python imports
+import os
+
+# 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 GLib
+
+# Application imports
+from .mixins.signals_mixins import SignalsMixins
+from .controller_data import ControllerData
+from .containers.base_container import BaseContainer
+
+
+
+class Controller(SignalsMixins, ControllerData):
+ def __init__(self, args, unknownargs):
+ self.setup_controller_data()
+
+ self._setup_styling()
+ self._setup_signals()
+ self._subscribe_to_events()
+
+ if args.no_plugins == "false":
+ self.plugins.launch_plugins()
+
+ for arg in unknownargs + [args.new_tab,]:
+ if os.path.isfile(arg):
+ message = f"FILE|{arg}"
+ event_system.emit("post_file_to_ipc", message)
+
+ if os.path.isdir(arg):
+ message = f"DIR|{arg}"
+ event_system.emit("post_file_to_ipc", message)
+
+ logger.info(f"Made it past {self.__class__} loading...")
+
+
+ def _setup_styling(self):
+ ...
+
+ def _setup_signals(self):
+ self.window.connect("focus-out-event", self.unset_keys_and_data)
+ self.window.connect("key-press-event", self.on_global_key_press_controller)
+ self.window.connect("key-release-event", self.on_global_key_release_controller)
+
+ def _subscribe_to_events(self):
+ event_system.subscribe("handle_file_from_ipc", self.handle_file_from_ipc)
+ event_system.subscribe("handle_dir_from_ipc", self.handle_dir_from_ipc)
+ event_system.subscribe("tggl_top_main_menubar", self._tggl_top_main_menubar)
+
+ def _tggl_top_main_menubar(self):
+ print("_tggl_top_main_menubar > stub...")
+
+ def setup_builder_and_container(self):
+ self.builder = Gtk.Builder()
+ # self.builder.add_from_file(settings.get_glade_file())
+ self.builder.expose_object("main_window", self.window)
+
+ settings.set_builder(self.builder)
+ self.base_container = BaseContainer()
+
+ settings.register_signals_to_builder([self, self.base_container])
+
+ def get_base_container(self):
+ return self.base_container
diff --git a/src/new-src/core/controller_data.py b/src/new-src/core/controller_data.py
new file mode 100644
index 0000000..910f627
--- /dev/null
+++ b/src/new-src/core/controller_data.py
@@ -0,0 +1,69 @@
+# Python imports
+import os
+import subprocess
+
+# Lib imports
+
+# Application imports
+from plugins.plugins_controller import PluginsController
+
+
+
+
+class ControllerData:
+ ''' ControllerData contains most of the state of the app at ay given time. It also has some support methods. '''
+
+ def setup_controller_data(self) -> None:
+ self.window = settings.get_main_window()
+ self.builder = None
+ self.base_container = None
+ self.was_midified_key = False
+ self.ctrl_down = False
+ self.shift_down = False
+ self.alt_down = False
+
+ self.setup_builder_and_container()
+ self.plugins = PluginsController()
+
+
+ def clear_console(self) -> None:
+ ''' Clears the terminal screen. '''
+ os.system('cls' if os.name == 'nt' else 'clear')
+
+ def call_method(self, _method_name: str, data: type) -> type:
+ '''
+ Calls a method from scope of class.
+
+ Parameters:
+ a (obj): self
+ b (str): method name to be called
+ c (*): Data (if any) to be passed to the method.
+ Note: It must be structured according to the given methods requirements.
+
+ Returns:
+ Return data is that which the calling method gives.
+ '''
+ method_name = str(_method_name)
+ method = getattr(self, method_name, lambda data: f"No valid key passed...\nkey={method_name}\nargs={data}")
+ return method(*data) if data else method()
+
+ def has_method(self, obj: type, method: type) -> type:
+ ''' Checks if a given method exists. '''
+ return callable(getattr(obj, method, None))
+
+ def clear_children(self, widget: type) -> None:
+ ''' Clear children of a gtk widget. '''
+ for child in widget.get_children():
+ widget.remove(child)
+
+ def get_clipboard_data(self, encoding="utf-8") -> str:
+ proc = subprocess.Popen(['xclip','-selection', 'clipboard', '-o'], stdout=subprocess.PIPE)
+ retcode = proc.wait()
+ data = proc.stdout.read()
+ return data.decode(encoding).strip()
+
+ def set_clipboard_data(self, data: type, encoding="utf-8") -> None:
+ proc = subprocess.Popen(['xclip','-selection','clipboard'], stdin=subprocess.PIPE)
+ proc.stdin.write(data.encode(encoding))
+ proc.stdin.close()
+ retcode = proc.wait()
diff --git a/src/new-src/core/mixins/__init__.py b/src/new-src/core/mixins/__init__.py
new file mode 100644
index 0000000..4589fc7
--- /dev/null
+++ b/src/new-src/core/mixins/__init__.py
@@ -0,0 +1,3 @@
+"""
+ Generic Mixins Module
+"""
diff --git a/src/new-src/core/mixins/signals/__init__.py b/src/new-src/core/mixins/signals/__init__.py
new file mode 100644
index 0000000..03c3ec2
--- /dev/null
+++ b/src/new-src/core/mixins/signals/__init__.py
@@ -0,0 +1,3 @@
+"""
+ Signals module
+"""
diff --git a/src/new-src/core/mixins/signals/ipc_signals_mixin.py b/src/new-src/core/mixins/signals/ipc_signals_mixin.py
new file mode 100644
index 0000000..760fe8e
--- /dev/null
+++ b/src/new-src/core/mixins/signals/ipc_signals_mixin.py
@@ -0,0 +1,20 @@
+# Python imports
+
+# Lib imports
+
+# Application imports
+
+
+
+
+class IPCSignalsMixin:
+ """ IPCSignalsMixin handle messages from another starting solarfm process. """
+
+ def print_to_console(self, message=None):
+ print(message)
+
+ def handle_file_from_ipc(self, path: str) -> None:
+ print(f"File From IPC: {path}")
+
+ def handle_dir_from_ipc(self, path: str) -> None:
+ print(f"Dir From IPC: {path}")
diff --git a/src/new-src/core/mixins/signals/keyboard_signals_mixin.py b/src/new-src/core/mixins/signals/keyboard_signals_mixin.py
new file mode 100644
index 0000000..1a99277
--- /dev/null
+++ b/src/new-src/core/mixins/signals/keyboard_signals_mixin.py
@@ -0,0 +1,94 @@
+# Python imports
+import re
+
+# 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
+
+# Application imports
+
+
+
+valid_keyvalue_pat = re.compile(r"[a-z0-9A-Z-_\[\]\(\)\| ]")
+
+
+
+class KeyboardSignalsMixin:
+ """ KeyboardSignalsMixin keyboard hooks controller. """
+
+ # TODO: Need to set methods that use this to somehow check the keybindings state instead.
+ def unset_keys_and_data(self, widget=None, eve=None):
+ self.ctrl_down = False
+ self.shift_down = False
+ self.alt_down = False
+
+ def on_global_key_press_controller(self, eve, user_data):
+ keyname = Gdk.keyval_name(user_data.keyval).lower()
+ modifiers = Gdk.ModifierType(user_data.get_state() & ~Gdk.ModifierType.LOCK_MASK)
+
+ self.was_midified_key = True if modifiers != 0 else False
+
+ if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]:
+ if "control" in keyname:
+ self.ctrl_down = True
+ if "shift" in keyname:
+ self.shift_down = True
+ if "alt" in keyname:
+ self.alt_down = True
+
+ def on_global_key_release_controller(self, widget, event):
+ """ Handler for keyboard events """
+ keyname = Gdk.keyval_name(event.keyval).lower()
+ modifiers = Gdk.ModifierType(event.get_state() & ~Gdk.ModifierType.LOCK_MASK)
+
+ if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]:
+ should_return = self.was_midified_key and (self.ctrl_down or self.shift_down or self.alt_down)
+
+ if "control" in keyname:
+ self.ctrl_down = False
+ if "shift" in keyname:
+ self.shift_down = False
+ if "alt" in keyname:
+ self.alt_down = False
+
+ # NOTE: In effect a filter after releasing a modifier and we have a modifier mapped
+ if should_return:
+ self.was_midified_key = False
+ return
+
+ mapping = keybindings.lookup(event)
+ logger.debug(f"on_global_key_release_controller > key > {keyname}")
+ logger.debug(f"on_global_key_release_controller > keyval > {event.keyval}")
+ logger.debug(f"on_global_key_release_controller > mapping > {mapping}")
+
+ if mapping:
+ # See if in controller scope
+ try:
+ getattr(self, mapping)()
+ return True
+ except Exception:
+ # Must be plugins scope, event call, OR we forgot to add method to controller scope
+ if "||" in mapping:
+ sender, eve_type = mapping.split("||")
+ else:
+ sender = ""
+ eve_type = mapping
+
+ self.handle_key_event_system(sender, eve_type)
+ else:
+ logger.debug(f"on_global_key_release_controller > key > {keyname}")
+
+ if self.ctrl_down:
+ if not keyname in ["1", "kp_1", "2", "kp_2", "3", "kp_3", "4", "kp_4"]:
+ self.handle_key_event_system(None, mapping)
+ else:
+ ...
+
+ def handle_key_event_system(self, sender, eve_type):
+ event_system.emit(eve_type)
+
+ def keyboard_close_tab(self):
+ ...
diff --git a/src/new-src/core/mixins/signals_mixins.py b/src/new-src/core/mixins/signals_mixins.py
new file mode 100644
index 0000000..76515f6
--- /dev/null
+++ b/src/new-src/core/mixins/signals_mixins.py
@@ -0,0 +1,13 @@
+# Python imports
+
+# Lib imports
+from .signals.ipc_signals_mixin import IPCSignalsMixin
+from .signals.keyboard_signals_mixin import KeyboardSignalsMixin
+
+# Application imports
+
+
+
+
+class SignalsMixins(KeyboardSignalsMixin, IPCSignalsMixin):
+ ...
diff --git a/src/new-src/core/widgets/__init__.py b/src/new-src/core/widgets/__init__.py
new file mode 100644
index 0000000..72b072b
--- /dev/null
+++ b/src/new-src/core/widgets/__init__.py
@@ -0,0 +1,3 @@
+"""
+ Widgets Module
+"""
diff --git a/src/new-src/core/window.py b/src/new-src/core/window.py
new file mode 100644
index 0000000..1300c63
--- /dev/null
+++ b/src/new-src/core/window.py
@@ -0,0 +1,95 @@
+# Python imports
+import time
+import signal
+
+# Lib imports
+import gi
+import cairo
+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 GLib
+
+# Application imports
+from core.controller import Controller
+
+
+class ControllerStartExceptiom(Exception):
+ ...
+
+
+
+
+class Window(Gtk.ApplicationWindow):
+ """ docstring for Window. """
+
+ def __init__(self, args, unknownargs):
+ super(Window, self).__init__()
+
+ self._controller = None
+
+ self._set_window_data()
+ self._setup_styling()
+ self._setup_signals()
+ self._subscribe_to_events()
+
+ settings.set_main_window(self)
+ self._load_widgets(args, unknownargs)
+
+ self.show()
+
+
+ def _setup_styling(self):
+ self.set_default_size(settings.get_main_window_width(),
+ settings.get_main_window_height())
+ self.set_title(f"{app_name}")
+ self.set_icon_from_file( settings.get_window_icon() )
+ self.set_gravity(5) # 5 = CENTER
+ self.set_position(1) # 1 = CENTER, 4 = CENTER_ALWAYS
+
+ def _setup_signals(self):
+ self.connect("delete-event", self._tear_down)
+ GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self._tear_down)
+
+ def _subscribe_to_events(self):
+ event_system.subscribe("tear_down", self._tear_down)
+
+ def _load_widgets(self, args, unknownargs):
+ if settings.is_debug():
+ self.set_interactive_debugging(True)
+
+
+ self._controller = Controller(args, unknownargs)
+ if not self._controller:
+ raise ControllerStartException("Controller exited and doesn't exist...")
+
+ self.add( self._controller.get_base_container() )
+
+ def _set_window_data(self) -> None:
+ screen = self.get_screen()
+ visual = screen.get_rgba_visual()
+
+ if visual != None and screen.is_composited():
+ self.set_visual(visual)
+ self.set_app_paintable(True)
+ self.connect("draw", self._area_draw)
+
+ # bind css file
+ cssProvider = Gtk.CssProvider()
+ cssProvider.load_from_path( settings.get_css_file() )
+ screen = Gdk.Screen.get_default()
+ styleContext = Gtk.StyleContext()
+ styleContext.add_provider_for_screen(screen, cssProvider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
+
+ def _area_draw(self, widget: Gtk.ApplicationWindow, cr: cairo.Context) -> None:
+ cr.set_source_rgba( *settings.get_paint_bg_color() )
+ cr.set_operator(cairo.OPERATOR_SOURCE)
+ cr.paint()
+ cr.set_operator(cairo.OPERATOR_OVER)
+
+
+ def _tear_down(self, widget=None, eve=None):
+ settings.clear_pid()
+ time.sleep(event_sleep_time)
+ Gtk.main_quit()
diff --git a/src/new-src/plugins/__init__.py b/src/new-src/plugins/__init__.py
new file mode 100644
index 0000000..5624b32
--- /dev/null
+++ b/src/new-src/plugins/__init__.py
@@ -0,0 +1,3 @@
+"""
+ Gtk Bound Plugins Module
+"""
diff --git a/src/new-src/plugins/manifest.py b/src/new-src/plugins/manifest.py
new file mode 100644
index 0000000..4088eed
--- /dev/null
+++ b/src/new-src/plugins/manifest.py
@@ -0,0 +1,64 @@
+# Python imports
+import os
+import json
+from os.path import join
+
+# Lib imports
+
+# Application imports
+
+
+
+
+class ManifestProcessor(Exception):
+ ...
+
+
+class Plugin:
+ path: str = None
+ name: str = None
+ author: str = None
+ version: str = None
+ support: str = None
+ requests:{} = None
+ reference: type = None
+
+
+class ManifestProcessor:
+ def __init__(self, path, builder):
+ manifest = join(path, "manifest.json")
+ if not os.path.exists(manifest):
+ raise Exception("Invalid Plugin Structure: Plugin doesn't have 'manifest.json'. Aboarting load...")
+
+ self._path = path
+ self._builder = builder
+ with open(manifest) as f:
+ data = json.load(f)
+ self._manifest = data["manifest"]
+ self._plugin = self.collect_info()
+
+ def collect_info(self) -> Plugin:
+ plugin = Plugin()
+ plugin.path = self._path
+ plugin.name = self._manifest["name"]
+ plugin.author = self._manifest["author"]
+ plugin.version = self._manifest["version"]
+ plugin.support = self._manifest["support"]
+ plugin.requests = self._manifest["requests"]
+
+ return plugin
+
+ def get_loading_data(self):
+ loading_data = {}
+ requests = self._plugin.requests
+ keys = requests.keys()
+
+ if "pass_events" in keys:
+ if requests["pass_events"] in ["true"]:
+ loading_data["pass_events"] = True
+
+ if "bind_keys" in keys:
+ if isinstance(requests["bind_keys"], list):
+ loading_data["bind_keys"] = requests["bind_keys"]
+
+ return self._plugin, loading_data
diff --git a/src/new-src/plugins/plugin_base.py b/src/new-src/plugins/plugin_base.py
new file mode 100644
index 0000000..3130bb4
--- /dev/null
+++ b/src/new-src/plugins/plugin_base.py
@@ -0,0 +1,61 @@
+# Python imports
+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
+ # where self.name should not be needed for message comms
+
+ self._builder = None
+ self._ui_objects = None
+ self._event_system = None
+
+
+ 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...")
+
+ 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 set_event_system(self, event_system):
+ """
+ Requests Key: 'pass_events': "true"
+ Must define in plugin if "pass_events" is set to "true" string.
+ """
+ self._event_system = 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 subscribe_to_events(self):
+ ...
+
+
+ def clear_children(self, widget: type) -> None:
+ """ Clear children of a gtk widget. """
+ for child in widget.get_children():
+ widget.remove(child)
diff --git a/src/new-src/plugins/plugins_controller.py b/src/new-src/plugins/plugins_controller.py
new file mode 100644
index 0000000..f0561f7
--- /dev/null
+++ b/src/new-src/plugins/plugins_controller.py
@@ -0,0 +1,119 @@
+# Python imports
+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
+from gi.repository import Gio
+
+# Application imports
+from .manifest import Plugin
+from .manifest import ManifestProcessor
+
+
+
+
+class InvalidPluginException(Exception):
+ ...
+
+
+class PluginsController:
+ """PluginsController controller"""
+
+ 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._builder = settings.get_builder()
+ self._plugins_path = settings.get_plugins_path()
+
+ self._plugins_dir_watcher = None
+ self._plugin_collection = []
+
+
+ def launch_plugins(self) -> None:
+ self._set_plugins_watcher()
+ self.load_plugins()
+
+ def _set_plugins_watcher(self) -> None:
+ self._plugins_dir_watcher = Gio.File.new_for_path(self._plugins_path) \
+ .monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, Gio.Cancellable())
+ self._plugins_dir_watcher.connect("changed", self._on_plugins_changed, ())
+
+ def _on_plugins_changed(self, file_monitor, file, other_file=None, eve_type=None, data=None):
+ if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED,
+ Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN,
+ Gio.FileMonitorEvent.MOVED_OUT]:
+ self.reload_plugins(file)
+
+ def load_plugins(self, file: str = None) -> None:
+ print(f"Loading plugins...")
+ parent_path = os.getcwd()
+
+ for path, folder in [[join(self._plugins_path, item), item] if os.path.isdir(join(self._plugins_path, item)) else None for item in os.listdir(self._plugins_path)]:
+ try:
+ target = join(path, "plugin.py")
+ manifest = ManifestProcessor(path, self._builder)
+
+ if not os.path.exists(target):
+ raise InvalidPluginException("Invalid Plugin Structure: Plugin doesn't have 'plugin.py'. Aboarting load...")
+
+ plugin, loading_data = manifest.get_loading_data()
+ module = self.load_plugin_module(path, folder, target)
+ self.execute_plugin(module, plugin, loading_data)
+ except Exception as e:
+ print(f"Malformed Plugin: Not loading -->: '{folder}' !")
+ traceback.print_exc()
+
+ os.chdir(parent_path)
+
+
+ def load_plugin_module(self, path, folder, target):
+ os.chdir(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: Plugin, loading_data: []):
+ plugin.reference = module.Plugin()
+ keys = loading_data.keys()
+
+ if "ui_target" in keys:
+ loading_data["ui_target"].add( plugin.reference.generate_reference_ui_element() )
+ loading_data["ui_target"].show_all()
+
+ if "pass_ui_objects" in keys:
+ plugin.reference.set_ui_object_collection( loading_data["pass_ui_objects"] )
+
+ if "pass_events" in keys:
+ plugin.reference.set_fm_event_system(event_system)
+ plugin.reference.subscribe_to_events()
+
+ if "bind_keys" in keys:
+ keybindings.append_bindings( loading_data["bind_keys"] )
+
+ plugin.reference.run()
+ self._plugin_collection.append(plugin)
+
+ def reload_plugins(self, file: str = None) -> None:
+ print(f"Reloading plugins... stub.")
diff --git a/src/utils/__init__.py b/src/new-src/utils/__init__.py
similarity index 100%
rename from src/utils/__init__.py
rename to src/new-src/utils/__init__.py
diff --git a/src/new-src/utils/endpoint_registry.py b/src/new-src/utils/endpoint_registry.py
new file mode 100644
index 0000000..86e4295
--- /dev/null
+++ b/src/new-src/utils/endpoint_registry.py
@@ -0,0 +1,22 @@
+# Python imports
+
+# Lib imports
+
+# Application imports
+from .singleton import Singleton
+
+
+
+class EndpointRegistry(Singleton):
+ 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/new-src/utils/event_system.py b/src/new-src/utils/event_system.py
new file mode 100644
index 0000000..9d876cf
--- /dev/null
+++ b/src/new-src/utils/event_system.py
@@ -0,0 +1,54 @@
+# Python imports
+from collections import defaultdict
+
+# Lib imports
+
+# Application imports
+from .singleton import Singleton
+
+
+
+class EventSystem(Singleton):
+ """ Create event system. """
+
+ def __init__(self):
+ self.subscribers = defaultdict(list)
+
+
+ def subscribe(self, event_type, fn):
+ self.subscribers[event_type].append(fn)
+
+ def unsubscribe(self, event_type, fn):
+ self.subscribers[event_type].remove(fn)
+
+ def unsubscribe_all(self, event_type):
+ self.subscribers.pop(event_type, None)
+
+ 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()
+
+ def emit_and_await(self, event_type, data = None):
+ """ NOTE: Should be used when signal has only one listener and vis-a-vis """
+ if event_type in self.subscribers:
+ response = None
+ for fn in self.subscribers[event_type]:
+ if data:
+ if hasattr(data, '__iter__') and not type(data) is str:
+ response = fn(*data)
+ else:
+ response = fn(data)
+ else:
+ response = fn()
+
+ if not response in (None, ''):
+ break
+
+ return response
diff --git a/src/new-src/utils/ipc_server.py b/src/new-src/utils/ipc_server.py
new file mode 100644
index 0000000..662fc89
--- /dev/null
+++ b/src/new-src/utils/ipc_server.py
@@ -0,0 +1,114 @@
+# Python imports
+import os
+import threading
+import time
+from multiprocessing.connection import Client
+from multiprocessing.connection import Listener
+
+# Lib imports
+
+# Application imports
+from .singleton import Singleton
+
+
+
+class IPCServer(Singleton):
+ """ Create a listener so that other {app_name} 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'{app_name}-ipc', 'utf-8')
+ self._ipc_timeout = 15.0
+
+ if conn_type == "socket":
+ self._ipc_address = f'/tmp/{app_name}-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
+
+ self._subscribe_to_events()
+
+ def _subscribe_to_events(self):
+ event_system.subscribe("post_file_to_ipc", self.send_ipc_message)
+
+
+ def create_ipc_listener(self) -> None:
+ if self._conn_type == "socket":
+ 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:
+ 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
+ self._run_ipc_loop(listener)
+
+ @daemon_threaded
+ def _run_ipc_loop(self, listener) -> None:
+ # NOTE: Not thread safe if using with Gtk. Need to import GLib and use idle_add
+ while True:
+ try:
+ conn = listener.accept()
+ start_time = time.perf_counter()
+ self._handle_ipc_message(conn, start_time)
+ except Exception as e:
+ ...
+
+ listener.close()
+
+ def _handle_ipc_message(self, conn, start_time) -> None:
+ while True:
+ msg = conn.recv()
+ if settings.is_debug():
+ print(msg)
+
+ if "FILE|" in msg:
+ file = msg.split("FILE|")[1].strip()
+ if file:
+ event_system.emit("handle_file_from_ipc", file)
+
+ if "DIR|" in msg:
+ file = msg.split("DIR|")[1].strip()
+ if file:
+ event_system.emit("handle_dir_from_ipc", file)
+
+ conn.close()
+ break
+
+
+ if msg in ['close connection', 'close server']:
+ conn.close()
+ break
+
+ # NOTE: Not perfect but insures we don't lock up the connection for too long.
+ end_time = time.perf_counter()
+ if (end_time - start_time) > self._ipc_timeout:
+ conn.close()
+ break
+
+
+ def send_ipc_message(self, message: str = "Empty Data...") -> None:
+ try:
+ if self._conn_type == "socket":
+ conn = Client(address=self._ipc_address, family="AF_UNIX", authkey=self._ipc_authkey)
+ elif "unsecured" not in self._conn_type:
+ conn = Client((self._ipc_address, self._ipc_port), authkey=self._ipc_authkey)
+ else:
+ conn = Client((self._ipc_address, self._ipc_port))
+
+ conn.send(message)
+ conn.close()
+ except ConnectionRefusedError as e:
+ print("Connection refused...")
+ except Exception as e:
+ print(repr(e))
diff --git a/src/new-src/utils/keybindings.py b/src/new-src/utils/keybindings.py
new file mode 100644
index 0000000..50e7b71
--- /dev/null
+++ b/src/new-src/utils/keybindings.py
@@ -0,0 +1,127 @@
+# Python imports
+import re
+
+# Lib imports
+import gi
+gi.require_version('Gdk', '3.0')
+from gi.repository import Gdk
+
+# Application imports
+from .singleton import Singleton
+
+
+
+def logger(log = ""):
+ print(log)
+
+
+class KeymapError(Exception):
+ """ Custom exception for errors in keybinding configurations """
+
+MODIFIER = re.compile('<([^<]+)>')
+class Keybindings(Singleton):
+ """ Class to handle loading and lookup of Terminator keybindings """
+
+ modifiers = {
+ 'ctrl': Gdk.ModifierType.CONTROL_MASK,
+ 'control': Gdk.ModifierType.CONTROL_MASK,
+ 'primary': Gdk.ModifierType.CONTROL_MASK,
+ 'shift': Gdk.ModifierType.SHIFT_MASK,
+ 'alt': Gdk.ModifierType.MOD1_MASK,
+ 'super': Gdk.ModifierType.SUPER_MASK,
+ 'hyper': Gdk.ModifierType.HYPER_MASK,
+ 'mod2': Gdk.ModifierType.MOD2_MASK
+ }
+
+ empty = {}
+ keys = None
+ _masks = None
+ _lookup = None
+
+ def __init__(self):
+ self.keymap = Gdk.Keymap.get_default()
+ self.configure({})
+
+ def configure(self, bindings):
+ """ Accept new bindings and reconfigure with them """
+ self.keys = bindings
+ self.reload()
+
+ def reload(self):
+ """ Parse bindings and mangle into an appropriate form """
+ self._lookup = {}
+ self._masks = 0
+
+ for action, bindings in list(self.keys.items()):
+ if isinstance(bindings, list):
+ bindings = (*bindings,)
+ elif not isinstance(bindings, tuple):
+ bindings = (bindings,)
+
+
+ for binding in bindings:
+ if not binding or binding == "None":
+ continue
+
+ try:
+ keyval, mask = self._parsebinding(binding)
+ # Does much the same, but with worse error handling.
+ # keyval, mask = Gtk.accelerator_parse(binding)
+ except KeymapError as e:
+ logger(f"Keybinding reload failed to parse binding '{binding}': {e}")
+ else:
+ if mask & Gdk.ModifierType.SHIFT_MASK:
+ if keyval == Gdk.KEY_Tab:
+ keyval = Gdk.KEY_ISO_Left_Tab
+ mask &= ~Gdk.ModifierType.SHIFT_MASK
+ else:
+ keyvals = Gdk.keyval_convert_case(keyval)
+ if keyvals[0] != keyvals[1]:
+ keyval = keyvals[1]
+ mask &= ~Gdk.ModifierType.SHIFT_MASK
+ else:
+ keyval = Gdk.keyval_to_lower(keyval)
+
+ self._lookup.setdefault(mask, {})
+ self._lookup[mask][keyval] = action
+ self._masks |= mask
+
+ def _parsebinding(self, binding):
+ """ Parse an individual binding using Gtk's binding function """
+ mask = 0
+ modifiers = re.findall(MODIFIER, binding)
+
+ if modifiers:
+ for modifier in modifiers:
+ mask |= self._lookup_modifier(modifier)
+
+ key = re.sub(MODIFIER, '', binding)
+ if key == '':
+ raise KeymapError('No key found!')
+
+ keyval = Gdk.keyval_from_name(key)
+
+ if keyval == 0:
+ raise KeymapError(f"Key '{key}' is unrecognised...")
+ return (keyval, mask)
+
+ def _lookup_modifier(self, modifier):
+ """ Map modifier names to gtk values """
+ try:
+ return self.modifiers[modifier.lower()]
+ except KeyError:
+ raise KeymapError(f"Unhandled modifier '<{modifier}>'")
+
+ def lookup(self, event):
+ """ Translate a keyboard event into a mapped key """
+ try:
+ _found, keyval, _egp, _lvl, consumed = self.keymap.translate_keyboard_state(
+ event.hardware_keycode,
+ Gdk.ModifierType(event.get_state() & ~Gdk.ModifierType.LOCK_MASK),
+ event.group)
+ except TypeError:
+ logger(f"Keybinding lookup failed to translate keyboard event: {dir(event)}")
+ return None
+
+ mask = (event.get_state() & ~consumed) & self._masks
+ return self._lookup.get(mask, self.empty).get(keyval, None)
diff --git a/src/new-src/utils/logger.py b/src/new-src/utils/logger.py
new file mode 100644
index 0000000..10e93c4
--- /dev/null
+++ b/src/new-src/utils/logger.py
@@ -0,0 +1,61 @@
+# Python imports
+import os
+import logging
+
+# Lib imports
+
+# Application imports
+from .singleton import Singleton
+
+
+
+class Logger(Singleton):
+ """
+ Create a new logging object and return it.
+ :note:
+ NOSET # Don't know the actual log level of this... (defaulting or literally none?)
+ Log Levels (From least to most)
+ Type Value
+ CRITICAL 50
+ ERROR 40
+ WARNING 30
+ INFO 20
+ DEBUG 10
+ :param loggerName: Sets the name of the logger object. (Used in log lines)
+ :param createFile: Whether we create a log file or just pump to terminal
+
+ :return: the logging object we created
+ """
+
+ def __init__(self, config_path: str, _ch_log_lvl = logging.CRITICAL, _fh_log_lvl = logging.INFO):
+ self._CONFIG_PATH = config_path
+ self.global_lvl = logging.DEBUG # Keep this at highest so that handlers can filter to their desired levels
+ self.ch_log_lvl = _ch_log_lvl # Prety much the only one we ever change
+ self.fh_log_lvl = _fh_log_lvl
+
+ def get_logger(self, loggerName: str = "NO_LOGGER_NAME_PASSED", createFile: bool = True) -> logging.Logger:
+ log = logging.getLogger(loggerName)
+ log.setLevel(self.global_lvl)
+
+ # Set our log output styles
+ fFormatter = logging.Formatter('[%(asctime)s] %(pathname)s:%(lineno)d %(levelname)s - %(message)s', '%m-%d %H:%M:%S')
+ cFormatter = logging.Formatter('%(pathname)s:%(lineno)d] %(levelname)s - %(message)s')
+
+ ch = logging.StreamHandler()
+ ch.setLevel(level=self.ch_log_lvl)
+ ch.setFormatter(cFormatter)
+ log.addHandler(ch)
+
+ if createFile:
+ folder = self._CONFIG_PATH
+ file = f"{folder}/application.log"
+
+ if not os.path.exists(folder):
+ os.mkdir(folder)
+
+ fh = logging.FileHandler(file)
+ fh.setLevel(level=self.fh_log_lvl)
+ fh.setFormatter(fFormatter)
+ log.addHandler(fh)
+
+ return log
diff --git a/src/new-src/utils/settings/__init__.py b/src/new-src/utils/settings/__init__.py
new file mode 100644
index 0000000..e07c5a0
--- /dev/null
+++ b/src/new-src/utils/settings/__init__.py
@@ -0,0 +1,4 @@
+"""
+ Settings module
+"""
+from .settings import Settings
diff --git a/src/new-src/utils/settings/settings.py b/src/new-src/utils/settings/settings.py
new file mode 100644
index 0000000..01c7579
--- /dev/null
+++ b/src/new-src/utils/settings/settings.py
@@ -0,0 +1,179 @@
+# Python imports
+import os
+import io
+import json
+import inspect
+import zipfile
+
+# Lib imports
+
+# Application imports
+from ..singleton import Singleton
+from .start_check_mixin import StartCheckMixin
+
+
+class MissingConfigError(Exception):
+ pass
+
+
+
+class Settings(StartCheckMixin, Singleton):
+ def __init__(self):
+ self._SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__))
+ self._USER_HOME = os.path.expanduser('~')
+ self._HOME_CONFIG_PATH = f"{self._USER_HOME}/.config/{app_name.lower()}"
+ self._USR_PATH = f"/usr/share/{app_name.lower()}"
+ self._USR_CONFIG_FILE = f"{self._USR_PATH}/settings.json"
+
+ self._PLUGINS_PATH = f"{self._HOME_CONFIG_PATH}/plugins"
+ self._DEFAULT_ICONS = f"{self._HOME_CONFIG_PATH}/icons"
+ self._CONFIG_FILE = f"{self._HOME_CONFIG_PATH}/settings.json"
+ self._GLADE_FILE = f"{self._HOME_CONFIG_PATH}/Main_Window.glade"
+ self._CSS_FILE = f"{self._HOME_CONFIG_PATH}/stylesheet.css"
+ self._KEY_BINDINGS_FILE = f"{self._HOME_CONFIG_PATH}/key-bindings.json"
+ self._PID_FILE = f"{self._HOME_CONFIG_PATH}/{app_name.lower()}.pid"
+ self._UI_WIDEGTS_PATH = f"{self._HOME_CONFIG_PATH}/ui_widgets"
+ self._CONTEXT_MENU = f"{self._HOME_CONFIG_PATH}/contexct_menu.json"
+ self._WINDOW_ICON = f"{self._DEFAULT_ICONS}/{app_name.lower()}.png"
+
+ if not os.path.exists(self._HOME_CONFIG_PATH):
+ os.mkdir(self._HOME_CONFIG_PATH)
+ if not os.path.exists(self._PLUGINS_PATH):
+ os.mkdir(self._PLUGINS_PATH)
+
+ if not os.path.exists(self._CONFIG_FILE):
+ import shutil
+ try:
+ shutil.copyfile(self._USR_CONFIG_FILE, self._CONFIG_FILE)
+ except Exception as e:
+ raise
+
+ if not os.path.exists(self._DEFAULT_ICONS):
+ self._DEFAULT_ICONS = f"{self._USR_PATH}/icons"
+ if not os.path.exists(self._DEFAULT_ICONS):
+ raise MissingConfigError("Unable to find the application icons directory.")
+ # if not os.path.exists(self._GLADE_FILE):
+ # self._GLADE_FILE = f"{self._USR_PATH}/Main_Window.glade"
+ # if not os.path.exists(self._GLADE_FILE):
+ raise MissingConfigError("Unable to find the application Glade file.")
+ if not os.path.exists(self._KEY_BINDINGS_FILE):
+ self._KEY_BINDINGS_FILE = f"{self._USR_PATH}/key-bindings.json"
+ if not os.path.exists(self._KEY_BINDINGS_FILE):
+ raise MissingConfigError("Unable to find the application Keybindings file.")
+ if not os.path.exists(self._CSS_FILE):
+ self._CSS_FILE = f"{self._USR_PATH}/stylesheet.css"
+ if not os.path.exists(self._CSS_FILE):
+ raise MissingConfigError("Unable to find the application Stylesheet file.")
+ if not os.path.exists(self._WINDOW_ICON):
+ self._WINDOW_ICON = f"{self._USR_PATH}/icons/{app_name.lower()}.png"
+ if not os.path.exists(self._WINDOW_ICON):
+ raise MissingConfigError("Unable to find the application icon.")
+ if not os.path.exists(self._UI_WIDEGTS_PATH):
+ self._UI_WIDEGTS_PATH = f"{self._USR_PATH}/ui_widgets"
+ if not os.path.exists(self._CONTEXT_MENU):
+ self._CONTEXT_MENU = f"{self._USR_PATH}/contexct_menu.json"
+
+
+ try:
+ with open(self._KEY_BINDINGS_FILE) as file:
+ bindings = json.load(file)["keybindings"]
+ keybindings.configure(bindings)
+ except Exception as e:
+ print( f"Settings: {self._KEY_BINDINGS_FILE}\n\t\t{repr(e)}" )
+
+ try:
+ with open(self._CONTEXT_MENU) as file:
+ self._context_menu_data = json.load(file)
+ except Exception as e:
+ print( f"Settings: {self._CONTEXT_MENU}\n\t\t{repr(e)}" )
+
+
+ self._main_window = None
+ self._main_window_w = 800
+ self._main_window_h = 600
+ self._builder = None
+ self.PAINT_BG_COLOR = (0, 0, 0, 0.54)
+
+ self._trace_debug = False
+ self._debug = False
+ self._dirty_start = False
+
+ self.load_settings()
+
+
+ def register_signals_to_builder(self, classes=None):
+ handlers = {}
+
+ for c in classes:
+ methods = None
+ try:
+ methods = inspect.getmembers(c, predicate=inspect.ismethod)
+ handlers.update(methods)
+ except Exception as e:
+ ...
+
+ self._builder.connect_signals(handlers)
+
+ def set_main_window(self, window): self._main_window = window
+ def set_builder(self, builder) -> any: self._builder = builder
+
+
+ def get_monitor_data(self) -> list:
+ screen = self._main_window.get_screen()
+ monitors = []
+ for m in range(screen.get_n_monitors()):
+ monitors.append(screen.get_monitor_geometry(m))
+ print("{}x{}+{}+{}".format(monitor.width, monitor.height, monitor.x, monitor.y))
+
+ return monitors
+
+ def get_main_window(self) -> any: return self._main_window
+ def get_main_window_width(self) -> any: return self._main_window_w
+ def get_main_window_height(self) -> any: return self._main_window_h
+ def get_builder(self) -> any: return self._builder
+ def get_paint_bg_color(self) -> any: return self.PAINT_BG_COLOR
+ def get_glade_file(self) -> str: return self._GLADE_FILE
+ def get_ui_widgets_path(self) -> str: return self._UI_WIDEGTS_PATH
+ def get_context_menu_data(self) -> str: return self._context_menu_data
+
+ def get_plugins_path(self) -> str: return self._PLUGINS_PATH
+ def get_icon_theme(self) -> str: return self._ICON_THEME
+ def get_css_file(self) -> str: return self._CSS_FILE
+ def get_home_config_path(self) -> str: return self._HOME_CONFIG_PATH
+ def get_window_icon(self) -> str: return self._WINDOW_ICON
+ def get_home_path(self) -> str: return self._USER_HOME
+
+ # Filter returns
+ def get_office_filter(self) -> tuple: return tuple(self._settings["filters"]["office"])
+ def get_vids_filter(self) -> tuple: return tuple(self._settings["filters"]["videos"])
+ def get_text_filter(self) -> tuple: return tuple(self._settings["filters"]["text"])
+ def get_music_filter(self) -> tuple: return tuple(self._settings["filters"]["music"])
+ def get_images_filter(self) -> tuple: return tuple(self._settings["filters"]["images"])
+ def get_pdf_filter(self) -> tuple: return tuple(self._settings["filters"]["pdf"])
+
+ def get_success_color(self) -> str: return self._theming["success_color"]
+ def get_warning_color(self) -> str: return self._theming["warning_color"]
+ def get_error_color(self) -> str: return self._theming["error_color"]
+
+ def is_trace_debug(self) -> str: return self._trace_debug
+ def is_debug(self) -> str: return self._debug
+
+ def get_ch_log_lvl(self) -> str: return self._settings["debugging"]["ch_log_lvl"]
+ def get_fh_log_lvl(self) -> str: return self._settings["debugging"]["fh_log_lvl"]
+
+ def set_trace_debug(self, trace_debug):
+ self._trace_debug = trace_debug
+
+ def set_debug(self, debug):
+ self._debug = debug
+
+
+ def load_settings(self):
+ with open(self._CONFIG_FILE) as f:
+ self._settings = json.load(f)
+ self._config = self._settings["config"]
+ self._theming = self._settings["theming"]
+
+ def save_settings(self):
+ with open(self._CONFIG_FILE, 'w') as outfile:
+ json.dump(self._settings, outfile, separators=(',', ':'), indent=4)
diff --git a/src/new-src/utils/settings/start_check_mixin.py b/src/new-src/utils/settings/start_check_mixin.py
new file mode 100644
index 0000000..7fba503
--- /dev/null
+++ b/src/new-src/utils/settings/start_check_mixin.py
@@ -0,0 +1,50 @@
+# Python imports
+import os
+import json
+import inspect
+
+# Lib imports
+
+# Application imports
+
+
+
+
+class StartCheckMixin:
+ def is_dirty_start(self) -> bool: return self._dirty_start
+ def clear_pid(self): self._clean_pid()
+
+ 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}")
diff --git a/src/new-src/utils/singleton.py b/src/new-src/utils/singleton.py
new file mode 100644
index 0000000..23b7191
--- /dev/null
+++ b/src/new-src/utils/singleton.py
@@ -0,0 +1,24 @@
+# Python imports
+
+# Lib imports
+
+# Application imports
+
+
+
+class SingletonError(Exception):
+ pass
+
+
+
+class Singleton:
+ ccount = 0
+
+ def __new__(cls, *args, **kwargs):
+ obj = super(Singleton, cls).__new__(cls)
+ cls.ccount += 1
+
+ if cls.ccount == 2:
+ raise SingletonError(f"Exceeded {cls.__name__} instantiation limit...")
+
+ return obj
diff --git a/src/old-src/__builtins__.py b/src/old-src/__builtins__.py
new file mode 100644
index 0000000..dd2c9a0
--- /dev/null
+++ b/src/old-src/__builtins__.py
@@ -0,0 +1,53 @@
+# Python imports
+import builtins
+import threading
+import sys
+
+# Lib imports
+
+# Application imports
+from utils.event_system import EventSystem
+from utils.endpoint_registry import EndpointRegistry
+from utils.keybindings import Keybindings
+from utils.logger import Logger
+from utils.settings import Settings
+
+
+
+# NOTE: Threads WILL NOT die with parent's destruction.
+def threaded_wrapper(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_wrapper(fn):
+ def wrapper(*args, **kwargs):
+ threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start()
+ return wrapper
+
+def sizeof_fmt_def(num, suffix="B"):
+ for unit in ["", "K", "M", "G", "T", "Pi", "Ei", "Zi"]:
+ if abs(num) < 1024.0:
+ return f"{num:3.1f} {unit}{suffix}"
+ num /= 1024.0
+ return f"{num:.1f} Yi{suffix}"
+
+
+
+
+# NOTE: Just reminding myself we can add to builtins two different ways...
+# __builtins__.update({"event_system": Builtins()})
+builtins.app_name = "Mirage2"
+builtins.keybindings = Keybindings()
+builtins.event_system = EventSystem()
+builtins.endpoint_registry = EndpointRegistry()
+builtins.settings = Settings()
+builtins.logger = Logger(settings.get_home_config_path(), \
+ _ch_log_lvl=settings.get_ch_log_lvl(), \
+ _fh_log_lvl=settings.get_fh_log_lvl()).get_logger()
+
+builtins.threaded = threaded_wrapper
+builtins.daemon_threaded = daemon_threaded_wrapper
+builtins.debug = False
+builtins.trace_debug = False
diff --git a/src/__init__.py b/src/old-src/__init__.py
similarity index 100%
rename from src/__init__.py
rename to src/old-src/__init__.py
diff --git a/src/__main__.py b/src/old-src/__main__.py
similarity index 53%
rename from src/__main__.py
rename to src/old-src/__main__.py
index 6606814..e4c8304 100644
--- a/src/__main__.py
+++ b/src/old-src/__main__.py
@@ -2,40 +2,51 @@
# Python imports
-import argparse, faulthandler, traceback
+import argparse
+import faulthandler
+import traceback
from setproctitle import setproctitle
-import tracemalloc
-tracemalloc.start()
-
-
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
+from __builtins__ import *
from app import Application
-if __name__ == "__main__":
- try:
- # import web_pdb
- # web_pdb.set_trace()
- setproctitle('Mirage2')
+def run():
+ try:
+ 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("--file", "-f", default=None, help="Open an image.")
parser.add_argument("--dir", "-d", default=None, help="Load a dir with images.")
# 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)
+
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/app.py b/src/old-src/app.py
similarity index 91%
rename from src/app.py
rename to src/old-src/app.py
index 9d6e4d2..192b891 100644
--- a/src/app.py
+++ b/src/old-src/app.py
@@ -1,13 +1,12 @@
# Python imports
-import os, inspect, time
+import os
+import inspect
# Lib imports
# Application imports
-from utils.settings import Settings
+from utils.ipc_server import IPCServer
from context.controller import Controller
-from __builtins__ import EventSystem
-
@@ -29,7 +28,6 @@ class Application(EventSystem):
# raise Exception("IPC Server Exists: Will send data to it and close...")
- settings = Settings()
settings.create_window()
controller = Controller(settings, args, unknownargs)
diff --git a/src/old-src/context/__init__.py b/src/old-src/context/__init__.py
new file mode 100644
index 0000000..90cfadc
--- /dev/null
+++ b/src/old-src/context/__init__.py
@@ -0,0 +1,3 @@
+"""
+ Gtk Bound Signal Module
+"""
diff --git a/src/context/controller.py b/src/old-src/context/controller.py
similarity index 100%
rename from src/context/controller.py
rename to src/old-src/context/controller.py
diff --git a/src/context/controller_data.py b/src/old-src/context/controller_data.py
similarity index 100%
rename from src/context/controller_data.py
rename to src/old-src/context/controller_data.py
diff --git a/src/context/icons/__init__.py b/src/old-src/context/icons/__init__.py
similarity index 100%
rename from src/context/icons/__init__.py
rename to src/old-src/context/icons/__init__.py
diff --git a/src/context/icons/icon.py b/src/old-src/context/icons/icon.py
similarity index 100%
rename from src/context/icons/icon.py
rename to src/old-src/context/icons/icon.py
diff --git a/src/context/icons/mixins/__init__.py b/src/old-src/context/icons/mixins/__init__.py
similarity index 100%
rename from src/context/icons/mixins/__init__.py
rename to src/old-src/context/icons/mixins/__init__.py
diff --git a/src/context/icons/mixins/desktopiconmixin.py b/src/old-src/context/icons/mixins/desktopiconmixin.py
similarity index 100%
rename from src/context/icons/mixins/desktopiconmixin.py
rename to src/old-src/context/icons/mixins/desktopiconmixin.py
diff --git a/src/context/icons/mixins/videoiconmixin.py b/src/old-src/context/icons/mixins/videoiconmixin.py
similarity index 100%
rename from src/context/icons/mixins/videoiconmixin.py
rename to src/old-src/context/icons/mixins/videoiconmixin.py
diff --git a/src/context/icons/mixins/xdg/BaseDirectory.py b/src/old-src/context/icons/mixins/xdg/BaseDirectory.py
similarity index 100%
rename from src/context/icons/mixins/xdg/BaseDirectory.py
rename to src/old-src/context/icons/mixins/xdg/BaseDirectory.py
diff --git a/src/context/icons/mixins/xdg/Config.py b/src/old-src/context/icons/mixins/xdg/Config.py
similarity index 100%
rename from src/context/icons/mixins/xdg/Config.py
rename to src/old-src/context/icons/mixins/xdg/Config.py
diff --git a/src/context/icons/mixins/xdg/DesktopEntry.py b/src/old-src/context/icons/mixins/xdg/DesktopEntry.py
similarity index 100%
rename from src/context/icons/mixins/xdg/DesktopEntry.py
rename to src/old-src/context/icons/mixins/xdg/DesktopEntry.py
diff --git a/src/context/icons/mixins/xdg/Exceptions.py b/src/old-src/context/icons/mixins/xdg/Exceptions.py
similarity index 100%
rename from src/context/icons/mixins/xdg/Exceptions.py
rename to src/old-src/context/icons/mixins/xdg/Exceptions.py
diff --git a/src/context/icons/mixins/xdg/IconTheme.py b/src/old-src/context/icons/mixins/xdg/IconTheme.py
similarity index 100%
rename from src/context/icons/mixins/xdg/IconTheme.py
rename to src/old-src/context/icons/mixins/xdg/IconTheme.py
diff --git a/src/context/icons/mixins/xdg/IniFile.py b/src/old-src/context/icons/mixins/xdg/IniFile.py
similarity index 100%
rename from src/context/icons/mixins/xdg/IniFile.py
rename to src/old-src/context/icons/mixins/xdg/IniFile.py
diff --git a/src/context/icons/mixins/xdg/Locale.py b/src/old-src/context/icons/mixins/xdg/Locale.py
similarity index 100%
rename from src/context/icons/mixins/xdg/Locale.py
rename to src/old-src/context/icons/mixins/xdg/Locale.py
diff --git a/src/context/icons/mixins/xdg/Menu.py b/src/old-src/context/icons/mixins/xdg/Menu.py
similarity index 100%
rename from src/context/icons/mixins/xdg/Menu.py
rename to src/old-src/context/icons/mixins/xdg/Menu.py
diff --git a/src/context/icons/mixins/xdg/MenuEditor.py b/src/old-src/context/icons/mixins/xdg/MenuEditor.py
similarity index 100%
rename from src/context/icons/mixins/xdg/MenuEditor.py
rename to src/old-src/context/icons/mixins/xdg/MenuEditor.py
diff --git a/src/context/icons/mixins/xdg/Mime.py b/src/old-src/context/icons/mixins/xdg/Mime.py
similarity index 100%
rename from src/context/icons/mixins/xdg/Mime.py
rename to src/old-src/context/icons/mixins/xdg/Mime.py
diff --git a/src/context/icons/mixins/xdg/RecentFiles.py b/src/old-src/context/icons/mixins/xdg/RecentFiles.py
similarity index 100%
rename from src/context/icons/mixins/xdg/RecentFiles.py
rename to src/old-src/context/icons/mixins/xdg/RecentFiles.py
diff --git a/src/context/icons/mixins/xdg/__init__.py b/src/old-src/context/icons/mixins/xdg/__init__.py
similarity index 100%
rename from src/context/icons/mixins/xdg/__init__.py
rename to src/old-src/context/icons/mixins/xdg/__init__.py
diff --git a/src/context/icons/mixins/xdg/util.py b/src/old-src/context/icons/mixins/xdg/util.py
similarity index 100%
rename from src/context/icons/mixins/xdg/util.py
rename to src/old-src/context/icons/mixins/xdg/util.py
diff --git a/src/context/mixins/__init__.py b/src/old-src/context/mixins/__init__.py
similarity index 100%
rename from src/context/mixins/__init__.py
rename to src/old-src/context/mixins/__init__.py
diff --git a/src/context/mixins/tree_view_update_mixin.py b/src/old-src/context/mixins/tree_view_update_mixin.py
similarity index 100%
rename from src/context/mixins/tree_view_update_mixin.py
rename to src/old-src/context/mixins/tree_view_update_mixin.py
diff --git a/src/context/view.py b/src/old-src/context/view.py
similarity index 100%
rename from src/context/view.py
rename to src/old-src/context/view.py
diff --git a/src/old-src/utils/__init__.py b/src/old-src/utils/__init__.py
new file mode 100644
index 0000000..a8e5edd
--- /dev/null
+++ b/src/old-src/utils/__init__.py
@@ -0,0 +1,3 @@
+"""
+ Utils module
+"""
diff --git a/src/old-src/utils/endpoint_registry.py b/src/old-src/utils/endpoint_registry.py
new file mode 100644
index 0000000..86e4295
--- /dev/null
+++ b/src/old-src/utils/endpoint_registry.py
@@ -0,0 +1,22 @@
+# Python imports
+
+# Lib imports
+
+# Application imports
+from .singleton import Singleton
+
+
+
+class EndpointRegistry(Singleton):
+ 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/old-src/utils/event_system.py b/src/old-src/utils/event_system.py
new file mode 100644
index 0000000..9d876cf
--- /dev/null
+++ b/src/old-src/utils/event_system.py
@@ -0,0 +1,54 @@
+# Python imports
+from collections import defaultdict
+
+# Lib imports
+
+# Application imports
+from .singleton import Singleton
+
+
+
+class EventSystem(Singleton):
+ """ Create event system. """
+
+ def __init__(self):
+ self.subscribers = defaultdict(list)
+
+
+ def subscribe(self, event_type, fn):
+ self.subscribers[event_type].append(fn)
+
+ def unsubscribe(self, event_type, fn):
+ self.subscribers[event_type].remove(fn)
+
+ def unsubscribe_all(self, event_type):
+ self.subscribers.pop(event_type, None)
+
+ 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()
+
+ def emit_and_await(self, event_type, data = None):
+ """ NOTE: Should be used when signal has only one listener and vis-a-vis """
+ if event_type in self.subscribers:
+ response = None
+ for fn in self.subscribers[event_type]:
+ if data:
+ if hasattr(data, '__iter__') and not type(data) is str:
+ response = fn(*data)
+ else:
+ response = fn(data)
+ else:
+ response = fn()
+
+ if not response in (None, ''):
+ break
+
+ return response
diff --git a/src/ipc_server.py b/src/old-src/utils/ipc_server.py
similarity index 97%
rename from src/ipc_server.py
rename to src/old-src/utils/ipc_server.py
index 8f2f9c9..6b733c4 100644
--- a/src/ipc_server.py
+++ b/src/old-src/utils/ipc_server.py
@@ -5,6 +5,7 @@ from multiprocessing.connection import Listener, Client
# Lib imports
# Application imports
+from .singleton import Singleton
def threaded(fn):
@@ -15,7 +16,7 @@ def threaded(fn):
-class IPCServer:
+class IPCServer(Singleton):
""" Create a listener so that other SolarFM instances send requests back to existing instance. """
def __init__(self, conn_type="socket"):
self.is_ipc_alive = False
diff --git a/src/old-src/utils/keybindings.py b/src/old-src/utils/keybindings.py
new file mode 100644
index 0000000..053a274
--- /dev/null
+++ b/src/old-src/utils/keybindings.py
@@ -0,0 +1,138 @@
+# Python imports
+import re
+
+# Gtk imports
+import gi
+gi.require_version('Gdk', '3.0')
+from gi.repository import Gdk
+
+# Application imports
+from .singleton import Singleton
+
+
+
+def logger(log = ""):
+ print(log)
+
+
+class KeymapError(Exception):
+ """ Custom exception for errors in keybinding configurations """
+
+MODIFIER = re.compile('<([^<]+)>')
+class Keybindings(Singleton):
+ """ Class to handle loading and lookup of Terminator keybindings """
+
+ modifiers = {
+ 'ctrl': Gdk.ModifierType.CONTROL_MASK,
+ 'control': Gdk.ModifierType.CONTROL_MASK,
+ 'primary': Gdk.ModifierType.CONTROL_MASK,
+ 'shift': Gdk.ModifierType.SHIFT_MASK,
+ 'alt': Gdk.ModifierType.MOD1_MASK,
+ 'super': Gdk.ModifierType.SUPER_MASK,
+ 'hyper': Gdk.ModifierType.HYPER_MASK,
+ 'mod2': Gdk.ModifierType.MOD2_MASK
+ }
+
+ empty = {}
+ keys = None
+ _masks = None
+ _lookup = None
+
+ def __init__(self):
+ self.keymap = Gdk.Keymap.get_default()
+ self.configure({})
+
+ def print_keys(self):
+ print(self.keys)
+
+ def append_bindings(self, combos):
+ """ Accept new binding(s) and reload """
+ for item in combos:
+ method, keys = item.split(":")
+ self.keys[method] = keys
+
+ self.reload()
+
+ def configure(self, bindings):
+ """ Accept new bindings and reconfigure with them """
+ self.keys = bindings
+ self.reload()
+
+ def reload(self):
+ """ Parse bindings and mangle into an appropriate form """
+ self._lookup = {}
+ self._masks = 0
+
+ for action, bindings in list(self.keys.items()):
+ if isinstance(bindings, list):
+ bindings = (*bindings,)
+ elif not isinstance(bindings, tuple):
+ bindings = (bindings,)
+
+
+ for binding in bindings:
+ if not binding or binding == "None":
+ continue
+
+ try:
+ keyval, mask = self._parsebinding(binding)
+ # Does much the same, but with poorer error handling.
+ # keyval, mask = Gtk.accelerator_parse(binding)
+ except KeymapError as e:
+ logger(f"Keybinding reload failed to parse binding '{binding}': {e}")
+ else:
+ if mask & Gdk.ModifierType.SHIFT_MASK:
+ if keyval == Gdk.KEY_Tab:
+ keyval = Gdk.KEY_ISO_Left_Tab
+ mask &= ~Gdk.ModifierType.SHIFT_MASK
+ else:
+ keyvals = Gdk.keyval_convert_case(keyval)
+ if keyvals[0] != keyvals[1]:
+ keyval = keyvals[1]
+ mask &= ~Gdk.ModifierType.SHIFT_MASK
+ else:
+ keyval = Gdk.keyval_to_lower(keyval)
+
+ self._lookup.setdefault(mask, {})
+ self._lookup[mask][keyval] = action
+ self._masks |= mask
+
+ def _parsebinding(self, binding):
+ """ Parse an individual binding using Gtk's binding function """
+ mask = 0
+ modifiers = re.findall(MODIFIER, binding)
+
+ if modifiers:
+ for modifier in modifiers:
+ mask |= self._lookup_modifier(modifier)
+
+ key = re.sub(MODIFIER, '', binding)
+ if key == '':
+ raise KeymapError('No key found!')
+
+ keyval = Gdk.keyval_from_name(key)
+
+ if keyval == 0:
+ raise KeymapError(f"Key '{key}' is unrecognised...")
+ return (keyval, mask)
+
+ def _lookup_modifier(self, modifier):
+ """ Map modifier names to gtk values """
+ try:
+ return self.modifiers[modifier.lower()]
+ except KeyError:
+ raise KeymapError(f"Unhandled modifier '<{modifier}>'")
+
+ def lookup(self, event):
+ """ Translate a keyboard event into a mapped key """
+ try:
+ _found, keyval, _egp, _lvl, consumed = self.keymap.translate_keyboard_state(
+ event.hardware_keycode,
+ Gdk.ModifierType(event.get_state() & ~Gdk.ModifierType.LOCK_MASK),
+ event.group)
+ except TypeError:
+ logger("Keybinding lookup failed to translate keyboard event: {dir(event)}")
+ return None
+
+ mask = (event.get_state() & ~consumed) & self._masks
+ return self._lookup.get(mask, self.empty).get(keyval, None)
diff --git a/src/old-src/utils/logger.py b/src/old-src/utils/logger.py
new file mode 100644
index 0000000..aad2d3b
--- /dev/null
+++ b/src/old-src/utils/logger.py
@@ -0,0 +1,59 @@
+# Python imports
+import os
+import logging
+
+# Application imports
+from .singleton import Singleton
+
+
+
+class Logger(Singleton):
+ """
+ Create a new logging object and return it.
+ :note:
+ NOSET # Don't know the actual log level of this... (defaulting or literally none?)
+ Log Levels (From least to most)
+ Type Value
+ CRITICAL 50
+ ERROR 40
+ WARNING 30
+ INFO 20
+ DEBUG 10
+ :param loggerName: Sets the name of the logger object. (Used in log lines)
+ :param createFile: Whether we create a log file or just pump to terminal
+
+ :return: the logging object we created
+ """
+
+ def __init__(self, config_path: str, _ch_log_lvl = logging.CRITICAL, _fh_log_lvl = logging.INFO):
+ self._CONFIG_PATH = config_path
+ self.global_lvl = logging.DEBUG # Keep this at highest so that handlers can filter to their desired levels
+ self.ch_log_lvl = _ch_log_lvl # Prety much the only one we ever change
+ self.fh_log_lvl = _fh_log_lvl
+
+ def get_logger(self, loggerName: str = "NO_LOGGER_NAME_PASSED", createFile: bool = True) -> logging.Logger:
+ log = logging.getLogger(loggerName)
+ log.setLevel(self.global_lvl)
+
+ # Set our log output styles
+ fFormatter = logging.Formatter('[%(asctime)s] %(pathname)s:%(lineno)d %(levelname)s - %(message)s', '%m-%d %H:%M:%S')
+ cFormatter = logging.Formatter('%(pathname)s:%(lineno)d] %(levelname)s - %(message)s')
+
+ ch = logging.StreamHandler()
+ ch.setLevel(level=self.ch_log_lvl)
+ ch.setFormatter(cFormatter)
+ log.addHandler(ch)
+
+ if createFile:
+ folder = self._CONFIG_PATH
+ file = f"{folder}/application.log"
+
+ if not os.path.exists(folder):
+ os.mkdir(folder)
+
+ fh = logging.FileHandler(file)
+ fh.setLevel(level=self.fh_log_lvl)
+ fh.setFormatter(fFormatter)
+ log.addHandler(fh)
+
+ return log
diff --git a/src/utils/settings.py b/src/old-src/utils/settings.py
similarity index 94%
rename from src/utils/settings.py
rename to src/old-src/utils/settings.py
index 3804c10..62e0d84 100644
--- a/src/utils/settings.py
+++ b/src/old-src/utils/settings.py
@@ -11,15 +11,17 @@ from gi.repository import Gdk
# Application imports
+from .singleton import Singleton
from .logger import Logger
-class Settings:
+class Settings(Singleton):
def __init__(self):
self._SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__))
self._USER_HOME = os.path.expanduser('~')
self._CONFIG_PATH = f"{self._USER_HOME}/.config/{app_name.lower()}"
+ self._HOME_CONFIG_PATH = f"{self._USER_HOME}/.config/{app_name.lower()}"
self._GLADE_FILE = f"{self._CONFIG_PATH}/Main_Window.glade"
self._CSS_FILE = f"{self._CONFIG_PATH}/stylesheet.css"
self._DEFAULT_ICONS = f"{self._CONFIG_PATH}/icons"
@@ -100,6 +102,7 @@ class Settings:
def get_logger(self): return self._logger
def get_main_window(self): return self._main_window
def get_home_path(self): return self._USER_HOME
+ def get_home_config_path(self) -> str: return self._HOME_CONFIG_PATH
# Filter returns
def get_images_filter(self): return self._images_filter
diff --git a/src/old-src/utils/singleton.py b/src/old-src/utils/singleton.py
new file mode 100644
index 0000000..ee85368
--- /dev/null
+++ b/src/old-src/utils/singleton.py
@@ -0,0 +1,23 @@
+# Python imports
+
+# Lib imports
+
+# Application imports
+
+
+class SingletonError(Exception):
+ pass
+
+
+
+class Singleton:
+ ccount = 0
+
+ def __new__(cls, *args, **kwargs):
+ obj = super(Singleton, cls).__new__(cls)
+ cls.ccount += 1
+
+ if cls.ccount == 2:
+ raise SingletonError(f"Exceeded {cls.__name__} instantiation limit...")
+
+ return obj
diff --git a/src/utils/logger.py b/src/utils/logger.py
deleted file mode 100644
index 06eed47..0000000
--- a/src/utils/logger.py
+++ /dev/null
@@ -1,56 +0,0 @@
-# Python imports
-import os, logging
-
-# Application imports
-
-
-class Logger:
- def __init__(self, config_path):
- self._CONFIG_PATH = config_path
-
- def get_logger(self, loggerName = "NO_LOGGER_NAME_PASSED", createFile = True):
- """
- Create a new logging object and return it.
- :note:
- NOSET # Don't know the actual log level of this... (defaulting or literally none?)
- Log Levels (From least to most)
- Type Value
- CRITICAL 50
- ERROR 40
- WARNING 30
- INFO 20
- DEBUG 10
- :param loggerName: Sets the name of the logger object. (Used in log lines)
- :param createFile: Whether we create a log file or just pump to terminal
-
- :return: the logging object we created
- """
-
- globalLogLvl = logging.DEBUG # Keep this at highest so that handlers can filter to their desired levels
- chLogLevel = logging.CRITICAL # Prety musch the only one we change ever
- fhLogLevel = logging.DEBUG
- log = logging.getLogger(loggerName)
- log.setLevel(globalLogLvl)
-
- # Set our log output styles
- fFormatter = logging.Formatter('[%(asctime)s] %(pathname)s:%(lineno)d %(levelname)s - %(message)s', '%m-%d %H:%M:%S')
- cFormatter = logging.Formatter('%(pathname)s:%(lineno)d] %(levelname)s - %(message)s')
-
- ch = logging.StreamHandler()
- ch.setLevel(level=chLogLevel)
- ch.setFormatter(cFormatter)
- log.addHandler(ch)
-
- if createFile:
- folder = self._CONFIG_PATH
- file = f"{folder}/application.log"
-
- if not os.path.exists(folder):
- os.mkdir(folder)
-
- fh = logging.FileHandler(file)
- fh.setLevel(level=fhLogLevel)
- fh.setFormatter(fFormatter)
- log.addHandler(fh)
-
- return log
diff --git a/user_config/bin/mirage2 b/user_config/bin/mirage2
new file mode 100755
index 0000000..1e1d90e
--- /dev/null
+++ b/user_config/bin/mirage2
@@ -0,0 +1,23 @@
+#!/bin/bash
+
+# . CONFIG.sh
+
+# set -o xtrace ## To debug scripts
+# set -o errexit ## To exit on error
+# set -o errunset ## To exit if a variable is referenced but not set
+
+
+function main() {
+ call_path=`pwd`
+ path=""
+
+ if [[ ! "${1::1}" == /* ]]; then
+ path="${call_path}/${1}"
+ else
+ path="${1}"
+ fi
+
+ cd "/opt/"
+ python ./mirage2.zip "${path}"
+}
+main "$@";
diff --git a/user_config/usr/applications/mirage2.desktop b/user_config/usr/applications/mirage2.desktop
new file mode 100644
index 0000000..07898dd
--- /dev/null
+++ b/user_config/usr/applications/mirage2.desktop
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Name=Mirage2
+Comment=A fast GTK+ Image Viewer
+Exec=/bin/mirage2 %f
+Terminal=false
+Type=Application
+Icon=/usr/share/mirage2/mirage2.png
+Categories=GTK;Graphics;2DGraphics;Viewer;
+MimeType=image/bmp;image/gif;image/jpeg;image/jpg;image/pjpeg;image/png;image/tiff;image/x-bmp;image/x-pcx;image/x-png;image/x-portable-anymap;image/x-portable-bitmap;image/x-portable-graymap;image/x-portable-pixmap;image/x-sun-raster;image/x-tga;image/x-xbitmap;image/x-xpixmap;image/svg+xml;
diff --git a/user_config/usr/share/mirage2/Main_Window.glade b/user_config/usr/share/mirage2/Main_Window.glade
deleted file mode 100644
index 25e2a0e..0000000
--- a/user_config/usr/share/mirage2/Main_Window.glade
+++ /dev/null
@@ -1,146 +0,0 @@
-
-
-
-
-
-
-
diff --git a/user_config/usr/share/mirage2/contexct_menu.json b/user_config/usr/share/mirage2/contexct_menu.json
new file mode 100644
index 0000000..2c63c08
--- /dev/null
+++ b/user_config/usr/share/mirage2/contexct_menu.json
@@ -0,0 +1,2 @@
+{
+}
diff --git a/user_config/usr/share/mirage2/key-bindings.json b/user_config/usr/share/mirage2/key-bindings.json
new file mode 100644
index 0000000..a172eea
--- /dev/null
+++ b/user_config/usr/share/mirage2/key-bindings.json
@@ -0,0 +1,23 @@
+{
+ "keybindings": {
+ "help" : "F1",
+ "rename_files" : ["F2", "e"],
+ "open_terminal" : "F4",
+ "refresh_tab" : ["F5", "r"],
+ "delete_files" : "Delete",
+ "tggl_top_main_menubar" : "Alt_L",
+ "trash_files" : "t",
+ "tear_down" : "q",
+ "go_up" : "Up",
+ "go_home" : "slash",
+ "grab_focus_path_entry" : "l",
+ "open_files" : "o",
+ "show_hide_hidden_files" : "h",
+ "keyboard_create_tab" : "t",
+ "keyboard_close_tab" : "w",
+ "keyboard_copy_files" : "c",
+ "keyboard_cut_files" : "x",
+ "paste_files" : "v",
+ "show_new_file_menu" : "n"
+ }
+}
diff --git a/user_config/usr/share/mirage2/settings.json b/user_config/usr/share/mirage2/settings.json
new file mode 100644
index 0000000..f75af4f
--- /dev/null
+++ b/user_config/usr/share/mirage2/settings.json
@@ -0,0 +1,40 @@
+{
+ "config": {
+ "base_of_home": "",
+ "hide_hidden_files": "true",
+ "thumbnailer_path": "ffmpegthumbnailer",
+ "go_past_home": "true",
+ "lock_folder": "false",
+ "locked_folders": "venv::::flasks",
+ "mplayer_options": "-quiet -really-quiet -xy 1600 -geometry 50%:50%",
+ "music_app": "/opt/deadbeef/bin/deadbeef",
+ "media_app": "mpv",
+ "image_app": "mirage",
+ "office_app": "libreoffice",
+ "pdf_app": "evince",
+ "text_app": "leafpad",
+ "file_manager_app": "solarfm",
+ "terminal_app": "terminator",
+ "remux_folder_max_disk_usage": "8589934592"
+ },
+ "filters": {
+ "meshs": [".blend", ".dae", ".fbx", ".gltf", ".obj", ".stl"],
+ "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"],
+ "text": [".txt", ".text", ".sh", ".cfg", ".conf", ".log"],
+ "music": [".psf", ".mp3", ".ogg", ".flac", ".m4a"],
+ "pdf": [".pdf"]
+
+ },
+ "theming":{
+ "success_color":"#88cc27",
+ "warning_color":"#ffa800",
+ "error_color":"#ff0000"
+ },
+ "debugging": {
+ "ch_log_lvl": 10,
+ "fh_log_lvl": 20
+ }
+}