Refactored to reduce Glade dependency...
This commit is contained in:
parent
ee812c520f
commit
e427cddec9
|
@ -7,6 +7,7 @@ gi.require_version('Gtk', '3.0')
|
||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
|
from plugins.plugin_base import PluginBase
|
||||||
|
|
||||||
|
|
||||||
# NOTE: Threads WILL NOT die with parent's destruction.
|
# NOTE: Threads WILL NOT die with parent's destruction.
|
||||||
|
@ -24,53 +25,22 @@ def daemon_threaded(fn):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Plugin:
|
class Plugin(PluginBase):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
self.name = "Example Plugin" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus
|
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
|
# where self.name should not be needed for message comms
|
||||||
self._event_system = None
|
|
||||||
self._event_sleep_time = .5
|
|
||||||
self._event_message = None
|
|
||||||
|
|
||||||
|
|
||||||
def get_ui_element(self):
|
def generate_reference_ui_element(self):
|
||||||
button = Gtk.Button(label=self.name)
|
button = Gtk.Button(label=self.name)
|
||||||
button.connect("button-release-event", self.send_message)
|
button.connect("button-release-event", self.send_message)
|
||||||
return button
|
return button
|
||||||
|
|
||||||
def set_fm_event_system(self, fm_event_system):
|
|
||||||
self._event_system = fm_event_system
|
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
self._module_event_observer()
|
...
|
||||||
|
|
||||||
|
|
||||||
def send_message(self, widget=None, eve=None):
|
def send_message(self, widget=None, eve=None):
|
||||||
message = "Hello, World!"
|
message = "Hello, World!"
|
||||||
self._event_system.push_gui_event([self.name, "display_message", ("warning", message, None)])
|
event_system.emit("display_message", ("warning", message, None))
|
||||||
|
|
||||||
|
|
||||||
def wait_for_fm_message(self):
|
|
||||||
while not self._event_message:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@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()
|
|
||||||
|
|
||||||
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))
|
|
||||||
|
|
|
@ -8,7 +8,8 @@ import builtins
|
||||||
# Application imports
|
# Application imports
|
||||||
from utils.event_system import EventSystem
|
from utils.event_system import EventSystem
|
||||||
from utils.endpoint_registry import EndpointRegistry
|
from utils.endpoint_registry import EndpointRegistry
|
||||||
|
from utils.keybindings import Keybindings
|
||||||
|
from utils.settings import Settings
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -30,10 +31,12 @@ def daemon_threaded_wrapper(fn):
|
||||||
# NOTE: Just reminding myself we can add to builtins two different ways...
|
# NOTE: Just reminding myself we can add to builtins two different ways...
|
||||||
# __builtins__.update({"event_system": Builtins()})
|
# __builtins__.update({"event_system": Builtins()})
|
||||||
builtins.app_name = "<change_me>"
|
builtins.app_name = "<change_me>"
|
||||||
|
builtins.keybindings = Keybindings()
|
||||||
builtins.event_system = EventSystem()
|
builtins.event_system = EventSystem()
|
||||||
builtins.endpoint_registry = EndpointRegistry()
|
builtins.endpoint_registry = EndpointRegistry()
|
||||||
|
builtins.settings = Settings()
|
||||||
|
builtins.logger = settings.get_logger()
|
||||||
|
|
||||||
builtins.threaded = threaded_wrapper
|
builtins.threaded = threaded_wrapper
|
||||||
builtins.daemon_threaded = daemon_threaded_wrapper
|
builtins.daemon_threaded = daemon_threaded_wrapper
|
||||||
builtins.event_sleep_time = 0.05
|
builtins.event_sleep_time = 0.05
|
||||||
builtins.trace_debug = False
|
|
||||||
builtins.debug = False
|
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
|
|
||||||
# Python imports
|
# Python imports
|
||||||
import argparse, faulthandler, traceback
|
import argparse
|
||||||
|
import faulthandler
|
||||||
|
import traceback
|
||||||
from setproctitle import setproctitle
|
from setproctitle import setproctitle
|
||||||
|
|
||||||
import tracemalloc
|
import tracemalloc
|
||||||
|
@ -14,6 +16,7 @@ gi.require_version('Gtk', '3.0')
|
||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
|
from __builtins__ import *
|
||||||
from app import Application
|
from app import Application
|
||||||
|
|
||||||
|
|
||||||
|
@ -21,13 +24,13 @@ if __name__ == "__main__":
|
||||||
''' Set process title, get arguments, and create GTK main thread. '''
|
''' Set process title, get arguments, and create GTK main thread. '''
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# import web_pdb
|
|
||||||
# web_pdb.set_trace()
|
|
||||||
|
|
||||||
setproctitle('<change_me>')
|
setproctitle('<change_me>')
|
||||||
faulthandler.enable() # For better debug info
|
faulthandler.enable() # For better debug info
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
parser = argparse.ArgumentParser()
|
||||||
# Add long and short arguments
|
# 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("--file", "-f", default="default", help="JUST SOME FILE ARG.")
|
parser.add_argument("--file", "-f", default="default", help="JUST SOME FILE ARG.")
|
||||||
|
|
||||||
# Read arguments (If any...)
|
# Read arguments (If any...)
|
||||||
|
|
42
src/app.py
42
src/app.py
|
@ -1,19 +1,20 @@
|
||||||
# Python imports
|
# Python imports
|
||||||
import os, inspect, time
|
import os
|
||||||
|
import time
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from __builtins__ import *
|
|
||||||
from utils.ipc_server import IPCServer
|
from utils.ipc_server import IPCServer
|
||||||
from utils.settings import Settings
|
from utils.settings import Settings
|
||||||
from context.controller import Controller
|
from core.window import Window
|
||||||
|
|
||||||
|
|
||||||
class App_Launch_Exception(Exception):
|
|
||||||
|
class AppLaunchException(Exception):
|
||||||
...
|
...
|
||||||
|
|
||||||
class Controller_Start_Exceptio(Exception):
|
class ControllerStartExceptiom(Exception):
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
|
@ -22,8 +23,13 @@ class Application(IPCServer):
|
||||||
|
|
||||||
def __init__(self, args, unknownargs):
|
def __init__(self, args, unknownargs):
|
||||||
super(Application, self).__init__()
|
super(Application, self).__init__()
|
||||||
|
if args.debug == "true":
|
||||||
|
settings.set_debug(True)
|
||||||
|
|
||||||
if not trace_debug:
|
if args.trace_debug == "true":
|
||||||
|
settings.set_trace_debug(True)
|
||||||
|
|
||||||
|
if not settings.is_trace_debug():
|
||||||
self.create_ipc_listener()
|
self.create_ipc_listener()
|
||||||
time.sleep(0.05)
|
time.sleep(0.05)
|
||||||
|
|
||||||
|
@ -34,26 +40,6 @@ class Application(IPCServer):
|
||||||
message = f"FILE|{arg}"
|
message = f"FILE|{arg}"
|
||||||
self.send_ipc_message(message)
|
self.send_ipc_message(message)
|
||||||
|
|
||||||
raise App_Launch_Exception(f"IPC Server Exists: Will send path(s) to it and close...\nNote: If no fm exists, remove /tmp/{app_name}-ipc.sock")
|
raise AppLaunchException(f"IPC Server Exists: Will send path(s) to it and close...\nNote: If no fm exists, remove /tmp/{app_name}-ipc.sock")
|
||||||
|
|
||||||
|
Window(args, unknownargs)
|
||||||
settings = Settings()
|
|
||||||
settings.create_window()
|
|
||||||
|
|
||||||
controller = Controller(settings, args, unknownargs)
|
|
||||||
if not controller:
|
|
||||||
raise Controller_Start_Exceptio("Controller exited and doesn't exist...")
|
|
||||||
|
|
||||||
# Gets the methods from the classes and sets to handler.
|
|
||||||
# Then, builder from settings will connect to any signals it needs.
|
|
||||||
classes = [controller]
|
|
||||||
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))
|
|
||||||
|
|
||||||
settings.get_builder().connect_signals(handlers)
|
|
||||||
|
|
|
@ -1,61 +0,0 @@
|
||||||
# Python imports
|
|
||||||
import os, signal
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
from gi.repository import GLib
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from plugins.plugins import Plugins
|
|
||||||
|
|
||||||
|
|
||||||
class Controller_Data:
|
|
||||||
''' Controller_Data contains most of the state of the app at ay given time. It also has some support methods. '''
|
|
||||||
|
|
||||||
def setup_controller_data(self, _settings: type) -> None:
|
|
||||||
self.plugins = Plugins(_settings)
|
|
||||||
|
|
||||||
self.settings = _settings
|
|
||||||
self.builder = self.settings.get_builder()
|
|
||||||
self.window = self.settings.get_main_window()
|
|
||||||
self.logger = self.settings.get_logger()
|
|
||||||
self.keybindings = self.settings.get_keybindings()
|
|
||||||
|
|
||||||
|
|
||||||
self.home_path = self.settings.get_home_path()
|
|
||||||
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.window.connect("delete-event", self.tear_down)
|
|
||||||
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.tear_down)
|
|
||||||
|
|
||||||
|
|
||||||
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, name: type) -> type:
|
|
||||||
''' Checks if a given method exists. '''
|
|
||||||
return callable(getattr(obj, name, None))
|
|
||||||
|
|
||||||
def clear_children(self, widget: type) -> None:
|
|
||||||
''' Clear children of a gtk widget. '''
|
|
||||||
for child in widget.get_children():
|
|
||||||
widget.remove(child)
|
|
|
@ -1,39 +1,58 @@
|
||||||
# Python imports
|
# Python imports
|
||||||
import subprocess, time
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
# Gtk imports
|
# Gtk imports
|
||||||
import gi
|
import gi
|
||||||
|
|
||||||
gi.require_version('Gtk', '3.0')
|
gi.require_version('Gtk', '3.0')
|
||||||
gi.require_version('Gdk', '3.0')
|
gi.require_version('Gdk', '3.0')
|
||||||
from gi.repository import Gtk, Gdk, GLib
|
|
||||||
|
from gi.repository import Gtk
|
||||||
|
from gi.repository import Gdk
|
||||||
|
from gi.repository import GLib
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from .mixins.dummy_mixin import DummyMixin
|
from .mixins.dummy_mixin import DummyMixin
|
||||||
from .controller_data import Controller_Data
|
from .controller_data import ControllerData
|
||||||
|
from .core_widget import CoreWidget
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Controller(DummyMixin, ControllerData):
|
||||||
|
def __init__(self, args, unknownargs):
|
||||||
|
self._setup_styling()
|
||||||
|
self._setup_signals()
|
||||||
|
self._subscribe_to_events()
|
||||||
|
|
||||||
class Controller(DummyMixin, Controller_Data):
|
self.setup_controller_data()
|
||||||
def __init__(self, _settings, args, unknownargs):
|
|
||||||
self.setup_controller_data(_settings)
|
|
||||||
self.window.show()
|
|
||||||
self.print_hello_world() # A mixin method from the DummyMixin file
|
self.print_hello_world() # A mixin method from the DummyMixin file
|
||||||
|
|
||||||
self._subscribe_to_events()
|
|
||||||
|
def _setup_styling(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
def _setup_signals(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
def _subscribe_to_events(self):
|
def _subscribe_to_events(self):
|
||||||
event_system.subscribe("handle_file_from_ipc", self.handle_file_from_ipc)
|
event_system.subscribe("handle_file_from_ipc", self.handle_file_from_ipc)
|
||||||
|
|
||||||
|
|
||||||
def tear_down(self, widget=None, eve=None):
|
|
||||||
time.sleep(event_sleep_time)
|
|
||||||
Gtk.main_quit()
|
|
||||||
|
|
||||||
def handle_file_from_ipc(self, path: str) -> None:
|
def handle_file_from_ipc(self, path: str) -> None:
|
||||||
print(f"Path From IPC: {path}")
|
print(f"Path From IPC: {path}")
|
||||||
|
|
||||||
|
def load_glade_file(self):
|
||||||
|
self.builder = Gtk.Builder()
|
||||||
|
self.builder.add_from_file(settings.get_glade_file())
|
||||||
|
settings.set_builder(self.builder)
|
||||||
|
self.core_widget = CoreWidget()
|
||||||
|
|
||||||
|
settings.register_signals_to_builder([self, self.core_widget])
|
||||||
|
|
||||||
|
def get_core_widget(self):
|
||||||
|
return self.core_widget
|
||||||
|
|
||||||
|
|
||||||
def on_global_key_release_controller(self, widget: type, event: type) -> None:
|
def on_global_key_release_controller(self, widget: type, event: type) -> None:
|
||||||
"""Handler for keyboard events"""
|
"""Handler for keyboard events"""
|
||||||
keyname = Gdk.keyval_name(event.keyval).lower()
|
keyname = Gdk.keyval_name(event.keyval).lower()
|
||||||
|
@ -46,7 +65,7 @@ class Controller(DummyMixin, Controller_Data):
|
||||||
self.alt_down = False
|
self.alt_down = False
|
||||||
|
|
||||||
|
|
||||||
mapping = self.keybindings.lookup(event)
|
mapping = keybindings.lookup(event)
|
||||||
if mapping:
|
if mapping:
|
||||||
getattr(self, mapping)()
|
getattr(self, mapping)()
|
||||||
return True
|
return True
|
||||||
|
@ -55,7 +74,6 @@ class Controller(DummyMixin, Controller_Data):
|
||||||
print(f"Add logic or remove this from: {self.__class__}")
|
print(f"Add logic or remove this from: {self.__class__}")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def get_clipboard_data(self) -> str:
|
def get_clipboard_data(self) -> str:
|
||||||
proc = subprocess.Popen(['xclip','-selection', 'clipboard', '-o'], stdout=subprocess.PIPE)
|
proc = subprocess.Popen(['xclip','-selection', 'clipboard', '-o'], stdout=subprocess.PIPE)
|
||||||
retcode = proc.wait()
|
retcode = proc.wait()
|
|
@ -0,0 +1,51 @@
|
||||||
|
# Python imports
|
||||||
|
import os
|
||||||
|
|
||||||
|
# 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.logger = settings.get_logger()
|
||||||
|
self.builder = None
|
||||||
|
self.core_widget = None
|
||||||
|
|
||||||
|
self.load_glade_file()
|
||||||
|
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)
|
|
@ -0,0 +1,37 @@
|
||||||
|
# Python imports
|
||||||
|
|
||||||
|
# Gtk imports
|
||||||
|
import gi
|
||||||
|
|
||||||
|
gi.require_version('Gtk', '3.0')
|
||||||
|
|
||||||
|
from gi.repository import Gtk
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
|
||||||
|
|
||||||
|
class CoreWidget(Gtk.Box):
|
||||||
|
def __init__(self):
|
||||||
|
super(CoreWidget, 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):
|
||||||
|
button = Gtk.Button(label="Click Me!")
|
||||||
|
button.connect("clicked", self._hello_world)
|
||||||
|
|
||||||
|
self.add(button)
|
||||||
|
|
||||||
|
def _hello_world(self, widget=None, eve=None):
|
||||||
|
print("Hello, World!")
|
|
@ -0,0 +1,76 @@
|
||||||
|
# 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 Window(Gtk.ApplicationWindow):
|
||||||
|
"""docstring for Window."""
|
||||||
|
|
||||||
|
def __init__(self, args, unknownargs):
|
||||||
|
super(Window, self).__init__()
|
||||||
|
|
||||||
|
self._set_window_data()
|
||||||
|
self._setup_styling()
|
||||||
|
self._setup_signals()
|
||||||
|
self._load_widgets(args, unknownargs)
|
||||||
|
|
||||||
|
self.show_all()
|
||||||
|
|
||||||
|
|
||||||
|
def _setup_styling(self):
|
||||||
|
self.set_default_size(1670, 830)
|
||||||
|
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 _load_widgets(self, args, unknownargs):
|
||||||
|
controller = Controller(args, unknownargs)
|
||||||
|
self.add( controller.get_core_widget() )
|
||||||
|
|
||||||
|
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(0, 0, 0, 0.54)
|
||||||
|
cr.set_operator(cairo.OPERATOR_SOURCE)
|
||||||
|
cr.paint()
|
||||||
|
cr.set_operator(cairo.OPERATOR_OVER)
|
||||||
|
|
||||||
|
|
||||||
|
def _tear_down(self, widget=None, eve=None):
|
||||||
|
time.sleep(event_sleep_time)
|
||||||
|
Gtk.main_quit()
|
|
@ -52,9 +52,9 @@ class ManifestProcessor:
|
||||||
requests = self._plugin.requests
|
requests = self._plugin.requests
|
||||||
keys = requests.keys()
|
keys = requests.keys()
|
||||||
|
|
||||||
if "pass_fm_events" in keys:
|
if "pass_events" in keys:
|
||||||
if requests["pass_fm_events"] in ["true"]:
|
if requests["pass_events"] in ["true"]:
|
||||||
loading_data["pass_fm_events"] = True
|
loading_data["pass_events"] = True
|
||||||
|
|
||||||
if "bind_keys" in keys:
|
if "bind_keys" in keys:
|
||||||
if isinstance(requests["bind_keys"], list):
|
if isinstance(requests["bind_keys"], list):
|
||||||
|
|
|
@ -0,0 +1,60 @@
|
||||||
|
# Python imports
|
||||||
|
import os, 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)
|
|
@ -17,17 +17,15 @@ class InvalidPluginException(Exception):
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
class Plugins:
|
class PluginsController:
|
||||||
"""Plugins controller"""
|
"""PluginsController controller"""
|
||||||
|
|
||||||
def __init__(self, settings: type):
|
def __init__(self):
|
||||||
path = os.path.dirname(os.path.realpath(__file__))
|
path = os.path.dirname(os.path.realpath(__file__))
|
||||||
sys.path.insert(0, path) # NOTE: I think I'm not using this correctly...
|
sys.path.insert(0, path) # NOTE: I think I'm not using this correctly...
|
||||||
|
|
||||||
self._settings = settings
|
self._builder = settings.get_builder()
|
||||||
self._builder = self._settings.get_builder()
|
self._plugins_path = settings.get_plugins_path()
|
||||||
self._plugins_path = self._settings.get_plugins_path()
|
|
||||||
self._keybindings = self._settings.get_keybindings()
|
|
||||||
|
|
||||||
self._plugins_dir_watcher = None
|
self._plugins_dir_watcher = None
|
||||||
self._plugin_collection = []
|
self._plugin_collection = []
|
||||||
|
@ -72,23 +70,41 @@ class Plugins:
|
||||||
|
|
||||||
def load_plugin_module(self, path, folder, target):
|
def load_plugin_module(self, path, folder, target):
|
||||||
os.chdir(path)
|
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)
|
module = importlib.util.module_from_spec(spec)
|
||||||
sys.modules[folder] = module
|
sys.modules[folder] = module
|
||||||
spec.loader.exec_module(module)
|
spec.loader.exec_module(module)
|
||||||
|
|
||||||
return 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: []):
|
def execute_plugin(self, module: type, plugin: Plugin, loading_data: []):
|
||||||
plugin.reference = module.Plugin()
|
plugin.reference = module.Plugin()
|
||||||
keys = loading_data.keys()
|
keys = loading_data.keys()
|
||||||
|
|
||||||
if "pass_fm_events" in 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.set_fm_event_system(event_system)
|
||||||
|
plugin.reference.subscribe_to_events()
|
||||||
|
|
||||||
if "bind_keys" in keys:
|
if "bind_keys" in keys:
|
||||||
self._keybindings.append_bindings( loading_data["bind_keys"] )
|
keybindings.append_bindings( loading_data["bind_keys"] )
|
||||||
|
|
||||||
plugin.reference.run()
|
plugin.reference.run()
|
||||||
self._plugin_collection.append(plugin)
|
self._plugin_collection.append(plugin)
|
|
@ -1,18 +1,13 @@
|
||||||
# Python imports
|
# Python imports
|
||||||
import os, json
|
import os
|
||||||
|
import json
|
||||||
|
import inspect
|
||||||
|
|
||||||
# Gtk imports
|
# 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
|
# Application imports
|
||||||
from .logger import Logger
|
from .logger import Logger
|
||||||
from .keybindings import Keybindings
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -23,7 +18,7 @@ class Settings:
|
||||||
self._CONFIG_PATH = f"{self._USER_HOME}/.config/{app_name.lower()}"
|
self._CONFIG_PATH = f"{self._USER_HOME}/.config/{app_name.lower()}"
|
||||||
self._PLUGINS_PATH = f"{self._CONFIG_PATH}/plugins"
|
self._PLUGINS_PATH = f"{self._CONFIG_PATH}/plugins"
|
||||||
self._GLADE_FILE = f"{self._CONFIG_PATH}/Main_Window.glade"
|
self._GLADE_FILE = f"{self._CONFIG_PATH}/Main_Window.glade"
|
||||||
self._KEY_BINDINGS = f"{self._CONFIG_PATH}/key-bindings.json"
|
self._KEY_BINDINGS_FILE = f"{self._CONFIG_PATH}/key-bindings.json"
|
||||||
self._CSS_FILE = f"{self._CONFIG_PATH}/stylesheet.css"
|
self._CSS_FILE = f"{self._CONFIG_PATH}/stylesheet.css"
|
||||||
self._DEFAULT_ICONS = f"{self._CONFIG_PATH}/icons"
|
self._DEFAULT_ICONS = f"{self._CONFIG_PATH}/icons"
|
||||||
self._WINDOW_ICON = f"{self._DEFAULT_ICONS}/{app_name.lower()}.png"
|
self._WINDOW_ICON = f"{self._DEFAULT_ICONS}/{app_name.lower()}.png"
|
||||||
|
@ -36,8 +31,8 @@ class Settings:
|
||||||
|
|
||||||
if not os.path.exists(self._GLADE_FILE):
|
if not os.path.exists(self._GLADE_FILE):
|
||||||
self._GLADE_FILE = f"{self._USR_PATH}/Main_Window.glade"
|
self._GLADE_FILE = f"{self._USR_PATH}/Main_Window.glade"
|
||||||
if not os.path.exists(self._KEY_BINDINGS):
|
if not os.path.exists(self._KEY_BINDINGS_FILE):
|
||||||
self._KEY_BINDINGS = f"{self._USR_SOLARFM}/key-bindings.json"
|
self._KEY_BINDINGS_FILE = f"{self._USR_PATH}/key-bindings.json"
|
||||||
if not os.path.exists(self._CSS_FILE):
|
if not os.path.exists(self._CSS_FILE):
|
||||||
self._CSS_FILE = f"{self._USR_PATH}/stylesheet.css"
|
self._CSS_FILE = f"{self._USR_PATH}/stylesheet.css"
|
||||||
if not os.path.exists(self._WINDOW_ICON):
|
if not os.path.exists(self._WINDOW_ICON):
|
||||||
|
@ -57,62 +52,39 @@ class Settings:
|
||||||
self._warning_color = "#ffa800"
|
self._warning_color = "#ffa800"
|
||||||
self._error_color = "#ff0000"
|
self._error_color = "#ff0000"
|
||||||
|
|
||||||
self._keybindings = Keybindings()
|
with open(self._KEY_BINDINGS_FILE) as file:
|
||||||
with open(self._KEY_BINDINGS) as file:
|
bindings = json.load(file)["keybindings"]
|
||||||
keybindings = json.load(file)["keybindings"]
|
keybindings.configure(bindings)
|
||||||
self._keybindings.configure(keybindings)
|
|
||||||
|
|
||||||
self._main_window = None
|
self._builder = None
|
||||||
self._logger = Logger(self._CONFIG_PATH).get_logger()
|
self._logger = Logger(self._CONFIG_PATH).get_logger()
|
||||||
self._builder = Gtk.Builder()
|
|
||||||
self._builder.add_from_file(self._GLADE_FILE)
|
self._trace_debug = False
|
||||||
|
self._debug = False
|
||||||
|
|
||||||
|
|
||||||
|
def get_builder(self) -> any: return self._builder
|
||||||
|
def set_builder(self, builder) -> any: self._builder = builder
|
||||||
|
def get_glade_file(self) -> str: return self._GLADE_FILE
|
||||||
|
|
||||||
def create_window(self) -> None:
|
def register_signals_to_builder(self, classes=None):
|
||||||
# Get window and connect signals
|
handlers = {}
|
||||||
self._main_window = self._builder.get_object("Main_Window")
|
|
||||||
self.set_window_data()
|
|
||||||
|
|
||||||
def set_window_data(self) -> None:
|
for c in classes:
|
||||||
self._main_window.set_icon_from_file(self._WINDOW_ICON)
|
methods = None
|
||||||
screen = self._main_window.get_screen()
|
try:
|
||||||
visual = screen.get_rgba_visual()
|
methods = inspect.getmembers(c, predicate=inspect.ismethod)
|
||||||
|
handlers.update(methods)
|
||||||
|
except Exception as e:
|
||||||
|
print(repr(e))
|
||||||
|
|
||||||
if visual != None and screen.is_composited():
|
self._builder.connect_signals(handlers)
|
||||||
self._main_window.set_visual(visual)
|
|
||||||
self._main_window.set_app_paintable(True)
|
|
||||||
self._main_window.connect("draw", self.draw_area)
|
|
||||||
|
|
||||||
# bind css file
|
|
||||||
cssProvider = Gtk.CssProvider()
|
|
||||||
cssProvider.load_from_path(self._CSS_FILE)
|
|
||||||
screen = Gdk.Screen.get_default()
|
|
||||||
styleContext = Gtk.StyleContext()
|
|
||||||
styleContext.add_provider_for_screen(screen, cssProvider, Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
|
||||||
|
|
||||||
def get_monitor_data(self) -> list:
|
|
||||||
screen = self._builder.get_object("Main_Window").get_screen()
|
|
||||||
monitors = []
|
|
||||||
for m in range(screen.get_n_monitors()):
|
|
||||||
monitors.append(screen.get_monitor_geometry(m))
|
|
||||||
print("{}x{}|{}+{}".format(monitor.width, monitor.height, monitor.x, monitor.y))
|
|
||||||
|
|
||||||
return monitors
|
|
||||||
|
|
||||||
def draw_area(self, widget, cr):
|
|
||||||
cr.set_source_rgba(0, 0, 0, 0.54)
|
|
||||||
cr.set_operator(cairo.OPERATOR_SOURCE)
|
|
||||||
cr.paint()
|
|
||||||
cr.set_operator(cairo.OPERATOR_OVER)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
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
|
def get_logger(self) -> Logger: return self._logger
|
||||||
def get_keybindings(self) -> Keybindings: return self._keybindings
|
|
||||||
def get_plugins_path(self) -> str: return self._PLUGINS_PATH
|
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_window_icon(self) -> str: return self._WINDOW_ICON
|
||||||
def get_home_path(self) -> str: return self._USER_HOME
|
def get_home_path(self) -> str: return self._USER_HOME
|
||||||
|
|
||||||
# Filter returns
|
# Filter returns
|
||||||
|
@ -126,3 +98,13 @@ class Settings:
|
||||||
def get_success_color(self) -> str: return self._success_color
|
def get_success_color(self) -> str: return self._success_color
|
||||||
def get_warning_color(self) -> str: return self._warning_color
|
def get_warning_color(self) -> str: return self._warning_color
|
||||||
def get_error_color(self) -> str: return self._error_color
|
def get_error_color(self) -> str: return self._error_color
|
||||||
|
|
||||||
|
def is_trace_debug(self) -> str: return self._trace_debug
|
||||||
|
def is_debug(self) -> str: return self._debug
|
||||||
|
|
||||||
|
|
||||||
|
def set_trace_debug(self, trace_debug):
|
||||||
|
self._trace_debug = trace_debug
|
||||||
|
|
||||||
|
def set_debug(self, debug):
|
||||||
|
self._debug = debug
|
||||||
|
|
Loading…
Reference in New Issue