Compare commits

...

20 Commits

Author SHA1 Message Date
itdominator cca007db76 Added VTE widget; css changes; format cleanup 2024-11-08 22:46:07 -06:00
itdominator 33c0827ca2 Moved args info to settings and restructured calls 2024-10-20 16:03:19 -05:00
itdominator f2b33066af Added stronger typing in settings; logging loading times; css changes to transparency 2024-10-20 15:20:18 -05:00
itdominator fafc1a985f Updated __init__ files; theming css changes, other 2024-10-14 21:57:18 -05:00
itdominator e2e9dc8c1f made default hiding transparency controls easier with restructured show calls 2024-08-31 22:27:11 -05:00
itdominator 25b6b5305b update readme 2024-07-26 19:53:04 -05:00
itdominator 62debf9ece extending plugins to load pre or post app start 2024-06-29 21:24:57 -05:00
itdominator cc5966dab2 Added status icon for system trays 2024-05-06 19:11:18 -05:00
itdominator e82e4fb1eb Updated a requirement 2024-05-03 01:11:52 -05:00
itdominator 48bac7e791 Added a requirement; added a debug handler 2024-05-03 01:08:49 -05:00
itdominator b78fac0aa5 Fixed depricated exception type; fixed siguser type 2024-03-25 22:48:05 -05:00
itdominator bdb9c157f7 moved controls to dir; added open files button example; css transparency changes 2024-03-16 22:36:04 -05:00
itdominator a0f32a7c00 app_name --> APP_NAME 2024-02-29 18:50:34 -06:00
itdominator b1096055b7 Renamed settings folder; hyphenated event names 2024-02-25 16:19:14 -06:00
itdominator ea62eb280c Added builder wrapper due to plugin setup 2024-02-24 21:55:40 -06:00
itdominator cc8f62776d Fixed bindings; added bindings 2024-02-23 23:45:13 -06:00
itdominator 2389f1d414 moved db stuff to own folder 2024-02-21 22:15:25 -06:00
itdominator d81bf3815a Removed logging item 2024-02-21 20:34:42 -06:00
itdominator 1695f5bc5c Overode method to add debug logging 2024-02-16 20:43:09 -06:00
itdominator 1245951da9 Cleanup of starting file collection 2024-02-16 20:13:36 -06:00
65 changed files with 772 additions and 363 deletions

View File

@ -3,11 +3,13 @@ A template project for Python with Gtk applications.
### Requirements ### Requirements
* PyGObject (Gtk introspection library) * PyGObject (Gtk introspection library)
* pygobject-stubs (For actually getting pylsp or python-language-server to auto complete in LSPs. Do if GTK3 --no-cache-dir --config-settings=config=Gtk3,Gdk3,Soup2)
* pyxdg (Desktop ".desktop" file parser) * pyxdg (Desktop ".desktop" file parser)
* setproctitle (Define process title to search and kill more easily) * setproctitle (Define process title to search and kill more easily)
* sqlmodel (SQL databases and is powered by Pydantic and SQLAlchemy) * sqlmodel (SQL databases and is powered by Pydantic and SQLAlchemy)
### Note ### Note
* pyrightconfig.json can prompt IDEs that use pyright lsp on where imports are located- look at venvPath and venv. "venvPath" is parent path of "venv" where "venv" is just the name of the folder under the parent path that is the python created venv.
* Move respetive sub folder content under user_config to the same places in Linux. Though, user/share/<app name> can go to ~/.config folder if prefered. * Move respetive sub folder content under user_config to the same places in Linux. Though, user/share/<app name> can go to ~/.config folder if prefered.
* In additiion, place the plugins folder in the same app folder you moved to /usr/share/<app name> or ~/.config/<app name> . * In additiion, place the plugins folder in the same app folder you moved to /usr/share/<app name> or ~/.config/<app name> .
There are a "\<change_me\>" strings and files that need to be set according to your app's name located at: There are a "\<change_me\>" strings and files that need to be set according to your app's name located at:

View File

@ -1,2 +1,31 @@
### Note ### Note
Copy the example and rename it to your desired name. The Main class and passed in arguments are required. You don't necessarily need to use the passed in socket_id or event_system. Copy the example and rename it to your desired name. Plugins define a ui target slot with the 'ui_target' requests data but don't have to if not directly interacted with.
Plugins must have a run method defined; though, you do not need to necessarily do anything within it. The run method implies that the passed in event system or other data is ready for the plugin to use.
### Manifest Example (All are required even if empty.)
```
class Manifest:
name: str = "Example Plugin"
author: str = "John Doe"
version: str = "0.0.1"
support: str = ""
requests: {} = {
'pass_ui_objects': ["plugin_control_list"],
'pass_events': "true",
'bind_keys': []
}
pre_launch: bool = False
```
### Requests
```
requests: {} = {
'pass_events': "true", # If empty or not present will be ignored.
"pass_ui_objects": [""], # Request reference to a UI component. Will be passed back as array to plugin.
'bind_keys': [f"{name}||send_message:<Control>f"],
f"{name}||do_save:<Control>s"] # Bind keys with method and key pare using list. Must pass "name" like shown with delimiter to its right.
}
```

13
pyrightconfig.json Normal file
View File

@ -0,0 +1,13 @@
{
"reportUndefinedVariable": false,
"reportUnusedVariable": false,
"reportUnusedImport": true,
"reportDuplicateImport": true,
"executionEnvironments": [
{
"root": "./src/versions/solarfm-0.0.1/solarfm"
}
],
"venvPath": ".",
"venv": ".venv"
}

View File

@ -1,4 +1,7 @@
PyGObject PyGObject==3.40.1
pyxdg pygobject-stubs --no-cache-dir --config-settings=config=Gtk3,Gdk3,Soup2
setproctitle setproctitle==1.2.2
sqlmodel pyxdg==0.27
psutil==5.8.0
pycryptodome==3.20.0
sqlmodel==0.0.19

View File

@ -11,7 +11,7 @@ from libs.event_system import EventSystem
from libs.endpoint_registry import EndpointRegistry from libs.endpoint_registry import EndpointRegistry
from libs.keybindings import Keybindings from libs.keybindings import Keybindings
from libs.logger import Logger from libs.logger import Logger
from libs.settings_manager.manager import SettingsManager from libs.settings.manager import SettingsManager
@ -35,7 +35,7 @@ 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.keybindings = Keybindings()
builtins.event_system = EventSystem() builtins.event_system = EventSystem()
@ -46,9 +46,11 @@ builtins.settings_manager = SettingsManager()
settings_manager.load_settings() settings_manager.load_settings()
builtins.settings = settings_manager.settings builtins.settings = settings_manager.settings
builtins.logger = Logger(settings_manager.get_home_config_path(), \ builtins.logger = Logger(
_ch_log_lvl=settings.debugging.ch_log_lvl, \ settings_manager.get_home_config_path(), \
_fh_log_lvl=settings.debugging.fh_log_lvl).get_logger() _ch_log_lvl = settings.debugging.ch_log_lvl, \
_fh_log_lvl = settings.debugging.fh_log_lvl
).get_logger()
builtins.threaded = threaded_wrapper builtins.threaded = threaded_wrapper
builtins.daemon_threaded = daemon_threaded_wrapper builtins.daemon_threaded = daemon_threaded_wrapper
@ -60,6 +62,6 @@ def custom_except_hook(exc_type, exc_value, exc_traceback):
sys.__excepthook__(exc_type, exc_value, exc_traceback) sys.__excepthook__(exc_type, exc_value, exc_traceback)
return return
logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback)) logger.error("Uncaught exception", exc_info = (exc_type, exc_value, exc_traceback))
sys.excepthook = custom_except_hook sys.excepthook = custom_except_hook

View File

@ -1,3 +1,3 @@
""" """
Start of package. Src Package.
""" """

View File

@ -17,8 +17,9 @@ from app import Application
def main(args, unknownargs): def main():
setproctitle(f'{app_name}') setproctitle(f'{APP_NAME}')
settings_manager.set_start_load_time()
if args.debug == "true": if args.debug == "true":
settings_manager.set_debug(True) settings_manager.set_debug(True)
@ -27,7 +28,7 @@ def main(args, unknownargs):
settings_manager.set_trace_debug(True) settings_manager.set_trace_debug(True)
settings_manager.do_dirty_start_check() settings_manager.do_dirty_start_check()
Application(args, unknownargs) Application()
@ -36,19 +37,20 @@ if __name__ == "__main__":
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("--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("--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("--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("--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.") parser.add_argument("--file", "-f", default = "default", help = "JUST SOME FILE ARG.")
# Read arguments (If any...) # Read arguments (If any...)
args, unknownargs = parser.parse_known_args() args, unknownargs = parser.parse_known_args()
settings_manager.set_starting_args( args, unknownargs )
try: try:
faulthandler.enable() # For better debug info faulthandler.enable() # For better debug info
main(args, unknownargs) main()
except Exception as e: except Exception as e:
traceback.print_exc() traceback.print_exc()
quit() quit()

View File

@ -19,27 +19,29 @@ class AppLaunchException(Exception):
class Application: class Application:
""" docstring for Application. """ """ docstring for Application. """
def __init__(self, args, unknownargs): def __init__(self):
super(Application, self).__init__() super(Application, self).__init__()
if not settings_manager.is_trace_debug(): if not settings_manager.is_trace_debug():
self.load_ipc(args, unknownargs) self.load_ipc()
self.setup_debug_hook() self.setup_debug_hook()
Window(args, unknownargs).main() Window().main()
def load_ipc(self, args, unknownargs): def load_ipc(self):
args, \
unknownargs = settings_manager.get_starting_args()
ipc_server = IPCServer() ipc_server = IPCServer()
self.ipc_realization_check(ipc_server)
self.ipc_realization_check(ipc_server)
if not ipc_server.is_ipc_alive: if not ipc_server.is_ipc_alive:
for arg in unknownargs + [args.new_tab,]: for arg in unknownargs + [args.new_tab,]:
if os.path.isfile(arg): if os.path.isfile(arg):
message = f"FILE|{arg}" message = f"FILE|{arg}"
ipc_server.send_ipc_message(message) ipc_server.send_ipc_message(message)
raise AppLaunchException(f"{app_name} IPC Server Exists: Have sent path(s) to it and closing...") raise AppLaunchException(f"{APP_NAME} IPC Server Exists: Have sent path(s) to it and closing...")
def ipc_realization_check(self, ipc_server): def ipc_realization_check(self, ipc_server):
try: try:
@ -56,7 +58,7 @@ class Application:
try: try:
# kill -SIGUSR2 <pid> from Linux/Unix or SIGBREAK signal from Windows # kill -SIGUSR2 <pid> from Linux/Unix or SIGBREAK signal from Windows
signal.signal( signal.signal(
vars(signal).get("SIGBREAK") or vars(signal).get("SIGUSR1"), vars(signal).get("SIGBREAK") or vars(signal).get("SIGUSR2"),
debug_signal_handler debug_signal_handler
) )
except ValueError: except ValueError:

View File

@ -1,3 +1,3 @@
""" """
Core Module Core Package
""" """

View File

@ -0,0 +1,33 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
class BuilderWrapper(Gtk.Builder):
"""docstring for BuilderWrapper."""
def __init__(self):
super(BuilderWrapper, self).__init__()
self.objects = {}
def get_object(self, id: str, use_gtk: bool = True) -> any:
if not use_gtk:
return self.objects[id]
return super(BuilderWrapper, self).get_object(id)
def expose_object(self, id: str, object: any, use_gtk: bool = True) -> None:
if not use_gtk:
self.objects[id] = object
else:
super(BuilderWrapper, self).expose_object(id, object)
def dereference_object(self, id: str) -> None:
del self.objects[id]

View File

@ -1,3 +1,3 @@
""" """
Containers Module Containers Package
""" """

View File

@ -22,7 +22,7 @@ class BaseContainer(Gtk.Box):
self._subscribe_to_events() self._subscribe_to_events()
self._load_widgets() self._load_widgets()
self.show_all() self.show()
def _setup_styling(self): def _setup_styling(self):
@ -33,8 +33,8 @@ class BaseContainer(Gtk.Box):
... ...
def _subscribe_to_events(self): def _subscribe_to_events(self):
event_system.subscribe("update_transparency", self._update_transparency) event_system.subscribe("update-transparency", self._update_transparency)
event_system.subscribe("remove_transparency", self._remove_transparency) event_system.subscribe("remove-transparency", self._remove_transparency)
def _load_widgets(self): def _load_widgets(self):
self.add(HeaderContainer()) self.add(HeaderContainer())

View File

@ -23,7 +23,7 @@ class BodyContainer(Gtk.Box):
self._subscribe_to_events() self._subscribe_to_events()
self._load_widgets() self._load_widgets()
self.show_all() self.show()
def _setup_styling(self): def _setup_styling(self):

View File

@ -21,6 +21,8 @@ class CenterContainer(Gtk.Box):
self._subscribe_to_events() self._subscribe_to_events()
self._load_widgets() self._load_widgets()
self.show()
def _setup_styling(self): def _setup_styling(self):
self.set_orientation(Gtk.Orientation.VERTICAL) self.set_orientation(Gtk.Orientation.VERTICAL)
@ -31,7 +33,6 @@ class CenterContainer(Gtk.Box):
... ...
def _subscribe_to_events(self): def _subscribe_to_events(self):
# event_system.subscribe("handle_file_from_ipc", self.handle_file_from_ipc)
... ...
def _load_widgets(self): def _load_widgets(self):
@ -40,6 +41,9 @@ class CenterContainer(Gtk.Box):
button.connect("clicked", self._hello_world) button.connect("clicked", self._hello_world)
button.show()
glade_box.show()
self.add(button) self.add(button)
self.add(glade_box) self.add(glade_box)
self.add( WebkitUI() ) self.add( WebkitUI() )

View File

@ -6,7 +6,8 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
# Application imports # Application imports
from ..widgets.transparency_scale import TransparencyScale from ..widgets.controls.open_files_button import OpenFilesButton
from ..widgets.controls.transparency_scale import TransparencyScale
@ -32,15 +33,19 @@ class HeaderContainer(Gtk.Box):
... ...
def _subscribe_to_events(self): def _subscribe_to_events(self):
... event_system.subscribe("tggl-top-main-menubar", self.tggl_top_main_menubar)
def _load_widgets(self): def _load_widgets(self):
button = Gtk.Button(label = "Interactive Debug") button = Gtk.Button(label = "Interactive Debug")
button.connect("clicked", self._interactive_debug) button.connect("clicked", self._interactive_debug)
self.add(TransparencyScale()) self.add( OpenFilesButton() )
self.add( TransparencyScale() )
self.add(button) self.add(button)
def _interactive_debug(self, widget = None, eve = None): def _interactive_debug(self, widget = None, eve = None):
event_system.emit("load_interactive_debug") event_system.emit("load-interactive-debug")
def tggl_top_main_menubar(self):
self.hide() if self.is_visible() else self.show_all()

View File

@ -18,6 +18,8 @@ class LeftContainer(Gtk.Box):
self._subscribe_to_events() self._subscribe_to_events()
self._load_widgets() self._load_widgets()
self.show()
def _setup_styling(self): def _setup_styling(self):
self.set_orientation(Gtk.Orientation.VERTICAL) self.set_orientation(Gtk.Orientation.VERTICAL)
@ -28,7 +30,6 @@ class LeftContainer(Gtk.Box):
... ...
def _subscribe_to_events(self): def _subscribe_to_events(self):
# event_system.subscribe("handle_file_from_ipc", self.handle_file_from_ipc)
... ...
def _load_widgets(self): def _load_widgets(self):

View File

@ -6,6 +6,7 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
# Application imports # Application imports
from ..widgets.vte_widget import VteWidget
@ -18,6 +19,8 @@ class RightContainer(Gtk.Box):
self._subscribe_to_events() self._subscribe_to_events()
self._load_widgets() self._load_widgets()
self.show()
def _setup_styling(self): def _setup_styling(self):
self.set_orientation(Gtk.Orientation.VERTICAL) self.set_orientation(Gtk.Orientation.VERTICAL)
@ -28,8 +31,8 @@ class RightContainer(Gtk.Box):
... ...
def _subscribe_to_events(self): def _subscribe_to_events(self):
# event_system.subscribe("handle_file_from_ipc", self.handle_file_from_ipc)
... ...
def _load_widgets(self): def _load_widgets(self):
... vte_widget = VteWidget()
self.add( vte_widget )

View File

@ -1,3 +1,3 @@
""" """
Controllers Module Controllers Package
""" """

View File

@ -1,5 +1,4 @@
# Python imports # Python imports
import os
# Lib imports # Lib imports
import gi import gi
@ -18,27 +17,18 @@ from .bridge_controller import BridgeController
class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerData): class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerData):
def __init__(self, args, unknownargs): def __init__(self):
self.setup_controller_data()
self._setup_controller_data()
self._setup_styling() self._setup_styling()
self._setup_signals() self._setup_signals()
self._subscribe_to_events() self._subscribe_to_events()
self._load_controllers() self._load_controllers()
self._load_plugins_and_files()
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...") logger.info(f"Made it past {self.__class__} loading...")
settings_manager.set_end_load_time()
settings_manager.log_load_time()
def _setup_styling(self): def _setup_styling(self):
@ -50,20 +40,30 @@ class BaseController(IPCSignalsMixin, KeyboardSignalsMixin, BaseControllerData):
self.window.connect("key-release-event", self.on_global_key_release_controller) self.window.connect("key-release-event", self.on_global_key_release_controller)
def _subscribe_to_events(self): def _subscribe_to_events(self):
event_system.subscribe("shutting_down", lambda: print("Shutting down...")) event_system.subscribe("shutting-down", lambda: print("Shutting down..."))
event_system.subscribe("handle_file_from_ipc", self.handle_file_from_ipc) 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("handle-dir-from-ipc", self.handle_dir_from_ipc)
event_system.subscribe("tggl_top_main_menubar", self._tggl_top_main_menubar) event_system.subscribe("tggl-top-main-menubar", self._tggl_top_main_menubar)
def _load_controllers(self): def _load_controllers(self):
BridgeController() BridgeController()
def _load_plugins_and_files(self):
args, unknownargs = settings_manager.get_starting_args()
if args.no_plugins == "false":
self.plugins_controller.pre_launch_plugins()
if args.no_plugins == "false":
self.plugins_controller.post_launch_plugins()
for file in settings_manager.get_starting_files():
event_system.emit("post-file-to-ipc", file)
def _tggl_top_main_menubar(self): def _tggl_top_main_menubar(self):
logger.debug("_tggl_top_main_menubar > stub...") logger.debug("_tggl_top_main_menubar > stub...")
def setup_builder_and_container(self): def _load_glade_file(self):
self.builder = Gtk.Builder() self.builder.add_from_file( settings_manager.get_glade_file() )
self.builder.add_from_file(settings_manager.get_glade_file())
self.builder.expose_object("main_window", self.window) self.builder.expose_object("main_window", self.window)
settings_manager.set_builder(self.builder) settings_manager.set_builder(self.builder)

View File

@ -7,23 +7,44 @@ from shutil import which
# Application imports # Application imports
from plugins.plugins_controller import PluginsController from plugins.plugins_controller import PluginsController
from ..builder_wrapper import BuilderWrapper
class BaseControllerData: class BaseControllerData:
''' BaseControllerData contains most of the state of the app at ay given time. It also has some support methods. ''' ''' BaseControllerData contains most of the state of the app at ay given time. It also has some support methods. '''
def setup_controller_data(self) -> None: def _setup_controller_data(self) -> None:
self.window = settings_manager.get_main_window() self.window = settings_manager.get_main_window()
self.builder = None self.builder = BuilderWrapper()
self.plugins_controller = PluginsController()
self.base_container = None self.base_container = None
self.was_midified_key = False self.was_midified_key = False
self.ctrl_down = False self.ctrl_down = False
self.shift_down = False self.shift_down = False
self.alt_down = False self.alt_down = False
self.setup_builder_and_container() self._collect_files_dirs()
self.plugins = PluginsController() self._load_glade_file()
def _collect_files_dirs(self):
args, \
unknownargs = settings_manager.get_starting_args()
files = []
for arg in unknownargs + [args.new_tab,]:
if os.path.isdir( arg.replace("file://", "") ):
files.append( f"DIR|{arg.replace('file://', '')}" )
# NOTE: If passing line number with file split against :
if os.path.isfile( arg.replace("file://", "").split(":")[0] ):
files.append( f"FILE|{arg.replace('file://', '')}" )
if len(files) > 0:
settings_manager.set_is_starting_with_file(True)
settings_manager.set_starting_files(files)
def get_base_container(self): def get_base_container(self):
return self.base_container return self.base_container

View File

@ -20,19 +20,19 @@ class BridgeController:
... ...
def _subscribe_to_events(self): def _subscribe_to_events(self):
event_system.subscribe("handle_bridge_event", self.handle_bridge_event) event_system.subscribe("handle-bridge-event", self.handle_bridge_event)
def handle_bridge_event(self, event): def handle_bridge_event(self, event):
match event.topic: match event.topic:
case "save": case "save":
event_system.emit(f"handle_file_event_{event.originator}", (event,)) event_system.emit(f"handle-file-event-{event.originator}", (event,))
case "close": case "close":
event_system.emit(f"handle_file_event_{event.originator}", (event,)) event_system.emit(f"handle-file-event-{event.originator}", (event,))
case "load_buffer": case "load_buffer":
event_system.emit(f"handle_file_event_{event.originator}", (event,)) event_system.emit(f"handle-file-event-{event.originator}", (event,))
case "load_file": case "load_file":
event_system.emit(f"handle_file_event_{event.originator}", (event,)) event_system.emit(f"handle-file-event-{event.originator}", (event,))
case "alert": case "alert":
content = base64.b64decode( event.content.encode() ).decode("utf-8") content = base64.b64decode( event.content.encode() ).decode("utf-8")
logger.info(f"\nMessage Topic: {event.topic}\nMessage Content: {content}") logger.info(f"\nMessage Topic: {event.topic}\nMessage Content: {content}")

View File

@ -1,3 +1,3 @@
""" """
Widgets Module Widgets Package
""" """

View File

@ -0,0 +1,3 @@
"""
Widgets.Controls Package
"""

View File

@ -0,0 +1,86 @@
# 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 Gio
# Application imports
class OpenFilesButton(Gtk.Button):
"""docstring for OpenFilesButton."""
def __init__(self):
super(OpenFilesButton, self).__init__()
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets()
def _setup_styling(self):
self.set_label("Open File(s)...")
self.set_image( Gtk.Image.new_from_icon_name("gtk-open", 4) )
self.set_always_show_image(True)
self.set_image_position(1) # Left - 0, Right = 1
self.set_hexpand(False)
def _setup_signals(self):
self.connect("button-release-event", self._open_files)
def _subscribe_to_events(self):
event_system.subscribe("open_files", self._open_files)
def _load_widgets(self):
...
def _open_files(self, widget = None, eve = None, gfile = None):
start_dir = None
_gfiles = []
if gfile and gfile.query_exists():
start_dir = gfile.get_parent()
chooser = Gtk.FileChooserDialog("Open File(s)...", None,
Gtk.FileChooserAction.OPEN,
(
Gtk.STOCK_CANCEL,
Gtk.ResponseType.CANCEL,
Gtk.STOCK_OPEN,
Gtk.ResponseType.OK
)
)
chooser.set_select_multiple(True)
try:
folder = widget.get_current_file().get_parent() if not start_dir else start_dir
chooser.set_current_folder( folder.get_path() )
except Exception as e:
...
response = chooser.run()
if not response == Gtk.ResponseType.OK:
chooser.destroy()
return _gfiles
filenames = chooser.get_filenames()
if not filenames:
chooser.destroy()
return _gfiles
for file in filenames:
path = file if os.path.isabs(file) else os.path.abspath(file)
_gfiles.append( Gio.File.new_for_path(path) )
chooser.destroy()
logger.debug(_gfiles)
return _gfiles

View File

@ -37,12 +37,12 @@ class TransparencyScale(Gtk.Scale):
def _load_widgets(self): def _load_widgets(self):
adjust = self.get_adjustment() adjust = self.get_adjustment()
adjust.set_lower(0) adjust.set_lower(0)
adjust.set_upper(99) adjust.set_upper(100)
adjust.set_value(settings.theming.transparency) adjust.set_value(settings.theming.transparency)
adjust.set_step_increment(1.0) adjust.set_step_increment(1.0)
def _update_transparency(self, range): def _update_transparency(self, range):
event_system.emit("remove_transparency") event_system.emit("remove-transparency")
tp = int(range.get_value()) tp = int(range.get_value())
settings.theming.transparency = tp settings.theming.transparency = tp
event_system.emit("update_transparency") event_system.emit("update-transparency")

View File

@ -0,0 +1,125 @@
# Python imports
import os
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
gi.require_version('Vte', '2.91')
from gi.repository import Gtk
from gi.repository import Gdk
from gi.repository import GLib
from gi.repository import Vte
# Application imports
from libs.dto.event import Event
class VteWidgetException(Exception):
...
class VteWidget(Vte.Terminal):
"""
https://stackoverflow.com/questions/60454326/how-to-implement-a-linux-terminal-in-a-pygtk-app-like-vscode-and-pycharm-has
"""
def __init__(self):
super(VteWidget, self).__init__()
self.cd_cmd_prefix = ("cd".encode(), "cd ".encode())
self.dont_process = False
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets()
self._do_session_spawn()
self.show()
def _setup_styling(self):
ctx = self.get_style_context()
ctx.add_class("vte-widget")
self.set_clear_background(False)
self.set_enable_sixel(True)
self.set_cursor_shape( Vte.CursorShape.IBEAM )
def _setup_signals(self):
self.connect("commit", self._commit)
def _subscribe_to_events(self):
event_system.subscribe("update_term_path", self.update_term_path)
def _load_widgets(self):
...
def _do_session_spawn(self):
self.spawn_sync(
Vte.PtyFlags.DEFAULT,
settings_manager.get_home_path(),
["/bin/bash"],
[],
GLib.SpawnFlags.DEFAULT,
None, None,
)
# Note: '-->:' is used as a delimiter to split on to get command actual.
# !!! DO NOT REMOVE UNLESS CODE UPDATED ACCORDINGLY !!!
startup_cmds = [
"env -i /bin/bash --noprofile --norc\n",
"export TERM='xterm-256color'\n",
"export LC_ALL=C\n",
"export XDG_RUNTIME_DIR='/run/user/1000'\n",
"export DISPLAY=:0\n",
f"export XAUTHORITY='{settings_manager.get_home_path()}/.Xauthority'\n",
f"\nexport HOME='{settings_manager.get_home_path()}'\n",
"export PS1='\\h@\\u \\W -->: '\n",
"clear\n"
]
for i in startup_cmds:
self.run_command(i)
def _commit(self, terminal, text, size):
if self.dont_process:
self.dont_process = False
return
if not text.encode() == "\r".encode(): return
text, attributes = self.get_text()
lines = text.strip().splitlines()
command_ran = None
try:
command_ran = lines[-1].split("-->:")[1].strip()
except VteWidgetException as e:
logger.debud(e)
return
if not command_ran[0:3].encode() in self.cd_cmd_prefix:
return
target_path = command_ran.split( command_ran[0:3] )[1]
if target_path in (".", "./"): return
if not target_path:
target_path = settings_manager.get_home_path()
event = Event("pty_path_updated", "", target_path)
event_system.emit("handle_bridge_event", (event,))
def update_term_path(self, fpath):
self.dont_process = True
cmds = [f"cd '{fpath}'\n", "clear\n"]
for i in cmds:
self.run_command(i)
def run_command(self, cmd):
self.feed_child_binary(bytes(cmd, 'utf8'))

View File

@ -1,3 +1,3 @@
""" """
WebKit2 UI Module WebKit2 UI Package
""" """

View File

@ -9,17 +9,14 @@ from gi.repository import Gdk
from gi.repository import WebKit2 from gi.repository import WebKit2
# Application imports # Application imports
from libs.data_types import Event from libs.settings.other.webkit_ui_settings import WebkitUISettings
from libs.dto.event import Event
class WebkitUI(WebKit2.WebView): class WebkitUI(WebKit2.WebView):
def __init__(self): def __init__(self):
super(WebkitUI, self).__init__() super(WebkitUI, self).__init__()
# self.get_context().set_sandbox_enabled(False)
self._load_settings()
self._setup_styling() self._setup_styling()
self._subscribe_to_events() self._subscribe_to_events()
self._load_view() self._load_view()
@ -27,10 +24,6 @@ class WebkitUI(WebKit2.WebView):
self.show_all() self.show_all()
if settings_manager.is_debug():
inspector = self.get_inspector()
inspector.show()
def _setup_styling(self): def _setup_styling(self):
self.set_vexpand(True) self.set_vexpand(True)
@ -38,7 +31,7 @@ class WebkitUI(WebKit2.WebView):
self.set_background_color( Gdk.RGBA(0, 0, 0, 0.0) ) self.set_background_color( Gdk.RGBA(0, 0, 0, 0.0) )
def _subscribe_to_events(self): def _subscribe_to_events(self):
event_system.subscribe(f"ui_message", self.ui_message) event_system.subscribe(f"ui-message", self.ui_message)
def _load_settings(self): def _load_settings(self):
self.set_settings( WebkitUISettings() ) self.set_settings( WebkitUISettings() )
@ -54,7 +47,6 @@ class WebkitUI(WebKit2.WebView):
def _setup_content_manager(self): def _setup_content_manager(self):
content_manager = self.get_user_content_manager() content_manager = self.get_user_content_manager()
content_manager.connect("script-message-received", self._process_js_message) content_manager.connect("script-message-received", self._process_js_message)
content_manager.register_script_message_handler("backend") content_manager.register_script_message_handler("backend")
@ -64,45 +56,14 @@ class WebkitUI(WebKit2.WebView):
try: try:
event = Event( **json.loads(message) ) event = Event( **json.loads(message) )
event_system.emit("handle_bridge_event", (event,)) event_system.emit("handle-bridge-event", (event,))
except Exception as e: except Exception as e:
logger.info(e) logger.info(e)
def ui_message(self, mtype, message): def ui_message(self, message, mtype):
command = f"displayMessage('{message}', '{mtype}', '3')" command = f"displayMessage('{message}', '{mtype}', '3')"
self.run_javascript(command, None, None) self.run_javascript(command, None, None)
def run_javascript(self, script, cancellable, callback):
class WebkitUISettings(WebKit2.Settings): logger.debug(script)
def __init__(self): super().run_javascript(script, cancellable, callback)
super(WebkitUISettings, self).__init__()
self._set_default_settings()
# Note: Highly insecure setup but most "app" like setup I could think of.
# Audit heavily any scripts/links ran/clicked under this setup!
def _set_default_settings(self):
# self.set_enable_xss_auditor(True)
# self.set_enable_hyperlink_auditing(True)
self.set_enable_xss_auditor(False)
self.set_enable_hyperlink_auditing(False)
self.set_enable_dns_prefetching(False)
self.set_allow_file_access_from_file_urls(True)
self.set_allow_universal_access_from_file_urls(True)
# self.set_enable_java(True)
self.set_enable_page_cache(False)
self.set_enable_offline_web_application_cache(False)
self.set_enable_html5_local_storage(False)
self.set_enable_html5_database(False)
self.set_print_backgrounds(False)
self.set_enable_tabs_to_links(False)
self.set_enable_fullscreen(True)
self.set_enable_developer_extras(True)
self.set_enable_webrtc(True)
self.set_enable_webaudio(True)
self.set_enable_accelerated_2d_canvas(True)
self.set_user_agent(f"Mozilla/5.0 {app_name}")

View File

@ -1,64 +0,0 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gdk', '3.0')
gi.require_version('WebKit2', '4.0')
from gi.repository import Gdk
from gi.repository import WebKit2
# Application imports
from libs.settings_manager.other.webkit_ui_settings import WebkitUISettings
class WebkitUI(WebKit2.WebView):
def __init__(self):
super(WebkitUI, self).__init__()
self._setup_styling()
self._subscribe_to_events()
self._load_view()
self._setup_content_manager()
self.show_all()
def _setup_styling(self):
self.set_vexpand(True)
self.set_hexpand(True)
self.set_background_color( Gdk.RGBA(0, 0, 0, 0.0) )
def _subscribe_to_events(self):
event_system.subscribe(f"ui_message", self.ui_message)
def _load_settings(self):
self.set_settings( WebkitUISettings() )
def _load_view(self):
path = settings_manager.get_context_path()
data = None
with open(f"{path}/index.html", "r") as f:
data = f.read()
self.load_html(content = data, base_uri = f"file://{path}/")
def _setup_content_manager(self):
content_manager = self.get_user_content_manager()
content_manager.connect("script-message-received", self._process_js_message)
content_manager.register_script_message_handler("backend")
def _process_js_message(self, user_content_manager, js_result):
js_value = js_result.get_js_value()
message = js_value.to_string()
try:
event = Event( **json.loads(message) )
event_system.emit("handle_bridge_event", (event,))
except Exception as e:
logger.info(e)
def ui_message(self, message, mtype):
command = f"displayMessage('{message}', '{mtype}', '3')"
self.run_javascript(command, None, None)

View File

@ -11,6 +11,7 @@ from gi.repository import Gdk
from gi.repository import GLib from gi.repository import GLib
# Application imports # Application imports
from libs.status_icon import StatusIcon
from core.controllers.base_controller import BaseController from core.controllers.base_controller import BaseController
@ -23,16 +24,17 @@ class ControllerStartExceptiom(Exception):
class Window(Gtk.ApplicationWindow): class Window(Gtk.ApplicationWindow):
""" docstring for Window. """ """ docstring for Window. """
def __init__(self, args, unknownargs): def __init__(self):
super(Window, self).__init__() super(Window, self).__init__()
settings_manager.set_main_window(self) settings_manager.set_main_window(self)
self._status_icon = None
self._controller = None self._controller = None
self._setup_styling() self._setup_styling()
self._setup_signals() self._setup_signals()
self._subscribe_to_events() self._subscribe_to_events()
self._load_widgets(args, unknownargs) self._load_widgets()
self._set_window_data() self._set_window_data()
self._set_size_constraints() self._set_size_constraints()
@ -41,7 +43,7 @@ class Window(Gtk.ApplicationWindow):
def _setup_styling(self): def _setup_styling(self):
self.set_title(f"{app_name}") self.set_title(f"{APP_NAME}")
self.set_icon_from_file( settings_manager.get_window_icon() ) self.set_icon_from_file( settings_manager.get_window_icon() )
self.set_gravity(5) # 5 = CENTER self.set_gravity(5) # 5 = CENTER
self.set_position(1) # 1 = CENTER, 4 = CENTER_ALWAYS self.set_position(1) # 1 = CENTER, 4 = CENTER_ALWAYS
@ -58,14 +60,15 @@ class Window(Gtk.ApplicationWindow):
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self._tear_down) GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self._tear_down)
def _subscribe_to_events(self): def _subscribe_to_events(self):
event_system.subscribe("tear_down", self._tear_down) event_system.subscribe("tear-down", self._tear_down)
event_system.subscribe("load_interactive_debug", self._load_interactive_debug) event_system.subscribe("load-interactive-debug", self._load_interactive_debug)
def _load_widgets(self, args, unknownargs): def _load_widgets(self):
if settings_manager.is_debug(): if settings_manager.is_debug():
self.set_interactive_debugging(True) self.set_interactive_debugging(True)
self._controller = BaseController(args, unknownargs) self._controller = BaseController()
self._status_icon = StatusIcon()
if not self._controller: if not self._controller:
raise ControllerStartException("BaseController exited and doesn't exist...") raise ControllerStartException("BaseController exited and doesn't exist...")
@ -90,7 +93,7 @@ class Window(Gtk.ApplicationWindow):
if visual and screen.is_composited() and settings.config.make_transparent == 0: if visual and screen.is_composited() and settings.config.make_transparent == 0:
self.set_visual(visual) self.set_visual(visual)
self.set_app_paintable(True) self.set_app_paintable(True)
self.connect("draw", self._area_draw) # self.connect("draw", self._area_draw)
# bind css file # bind css file
cssProvider = Gtk.CssProvider() cssProvider = Gtk.CssProvider()
@ -107,17 +110,17 @@ class Window(Gtk.ApplicationWindow):
def _on_focus_in_event(self, widget, event): def _on_focus_in_event(self, widget, event):
event_system.emit("pause_dnd_signals") event_system.emit("pause-dnd-signals")
def _on_focus_out_event(self, widget, event): def _on_focus_out_event(self, widget, event):
event_system.emit("listen_dnd_signals") event_system.emit("listen-dnd-signals")
def _load_interactive_debug(self): def _load_interactive_debug(self):
self.set_interactive_debugging(True) self.set_interactive_debugging(True)
def _tear_down(self, widget = None, eve = None): def _tear_down(self, widget = None, eve = None):
event_system.emit("shutting_down") event_system.emit("shutting-down")
size = self.get_size() size = self.get_size()
pos = self.get_position() pos = self.get_position()

View File

@ -1,3 +1,3 @@
""" """
Utils module Libs Package
""" """

View File

@ -1,5 +0,0 @@
"""
Dasta Class module
"""
from .event import Event

6
src/libs/db/__init__.py Normal file
View File

@ -0,0 +1,6 @@
"""
DB Package
"""
from .models import User
from .db import DB

View File

@ -18,7 +18,7 @@ def debug_signal_handler(signal, frame):
rpdb2.start_embedded_debugger("foobar", True, True) rpdb2.start_embedded_debugger("foobar", True, True)
rpdb2.setbreak(depth=1) rpdb2.setbreak(depth=1)
return return
except StandardError: except Exception:
... ...
try: try:
@ -26,7 +26,7 @@ def debug_signal_handler(signal, frame):
logger.debug("\n\nStarting embedded rconsole debugger...\n\n") logger.debug("\n\nStarting embedded rconsole debugger...\n\n")
rconsole.spawn_server() rconsole.spawn_server()
return return
except StandardError as ex: except Exception as ex:
... ...
try: try:
@ -34,7 +34,15 @@ def debug_signal_handler(signal, frame):
logger.debug("\n\nStarting PuDB debugger...\n\n") logger.debug("\n\nStarting PuDB debugger...\n\n")
set_trace(paused = True) set_trace(paused = True)
return return
except StandardError as ex: except Exception as ex:
...
try:
import ipdb
logger.debug("\n\nStarting IPDB debugger...\n\n")
ipdb.set_trace()
return
except Exception as ex:
... ...
try: try:
@ -42,11 +50,11 @@ def debug_signal_handler(signal, frame):
logger.debug("\n\nStarting embedded PDB debugger...\n\n") logger.debug("\n\nStarting embedded PDB debugger...\n\n")
pdb.Pdb(skip=['gi.*']).set_trace() pdb.Pdb(skip=['gi.*']).set_trace()
return return
except StandardError as ex: except Exception as ex:
... ...
try: try:
import code import code
code.interact() code.interact()
except StandardError as ex: except Exception as ex:
logger.debug(f"{ex}, returning to normal program flow...") logger.debug(f"{ex}, returning to normal program flow...")

5
src/libs/dto/__init__.py Normal file
View File

@ -0,0 +1,5 @@
"""
Dasta Class Package
"""
from .event import Event

View File

@ -13,17 +13,17 @@ from .singleton import Singleton
class IPCServer(Singleton): class IPCServer(Singleton):
""" Create a listener so that other {app_name} instances send requests back to existing instance. """ """ 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"): def __init__(self, ipc_address: str = '127.0.0.1', conn_type: str = "socket"):
self.is_ipc_alive = False self.is_ipc_alive = False
self._ipc_port = 4848 self._ipc_port = 4848
self._ipc_address = ipc_address self._ipc_address = ipc_address
self._conn_type = conn_type self._conn_type = conn_type
self._ipc_authkey = b'' + bytes(f'{app_name}-ipc', 'utf-8') self._ipc_authkey = b'' + bytes(f'{APP_NAME}-ipc', 'utf-8')
self._ipc_timeout = 15.0 self._ipc_timeout = 15.0
if conn_type == "socket": if conn_type == "socket":
self._ipc_address = f'/tmp/{app_name}-ipc.sock' self._ipc_address = f'/tmp/{APP_NAME}-ipc.sock'
elif conn_type == "full_network": elif conn_type == "full_network":
self._ipc_address = '0.0.0.0' self._ipc_address = '0.0.0.0'
elif conn_type == "full_network_unsecured": elif conn_type == "full_network_unsecured":
@ -35,7 +35,7 @@ class IPCServer(Singleton):
self._subscribe_to_events() self._subscribe_to_events()
def _subscribe_to_events(self): def _subscribe_to_events(self):
event_system.subscribe("post_file_to_ipc", self.send_ipc_message) event_system.subscribe("post-file-to-ipc", self.send_ipc_message)
def create_ipc_listener(self) -> None: def create_ipc_listener(self) -> None:
@ -56,7 +56,7 @@ class IPCServer(Singleton):
@daemon_threaded @daemon_threaded
def _run_ipc_loop(self, listener) -> None: def _run_ipc_loop(self, listener) -> None:
# NOTE: Not thread safe if using with Gtk. Need to import GLib and use idle_add # NOTE: Not thread safe if using with Gtk. Need to import GLib and use idle_add
while True: while self.is_ipc_alive:
try: try:
conn = listener.accept() conn = listener.accept()
start_time = time.perf_counter() start_time = time.perf_counter()
@ -67,19 +67,22 @@ class IPCServer(Singleton):
listener.close() listener.close()
def _handle_ipc_message(self, conn, start_time) -> None: def _handle_ipc_message(self, conn, start_time) -> None:
while True: while self.is_ipc_alive:
msg = conn.recv() msg = conn.recv()
logger.debug(msg) logger.debug(msg)
if "FILE|" in msg: if "FILE|" in msg:
file = msg.split("FILE|")[1].strip() file = msg.split("FILE|")[1].strip()
if file: if file:
event_system.emit("handle_file_from_ipc", file) event_system.emit("handle-file-from-ipc", file)
conn.close()
break
if "DIR|" in msg: if "DIR|" in msg:
file = msg.split("DIR|")[1].strip() file = msg.split("DIR|")[1].strip()
if file: if file:
event_system.emit("handle_dir_from_ipc", file) event_system.emit("handle-dir-from-ipc", file)
conn.close() conn.close()
break break

View File

@ -1,3 +1,3 @@
""" """
Utils/Mixins module Libs.Mixins Package
""" """

View File

@ -67,4 +67,4 @@ class DnDMixin:
files.append(gfile) files.append(gfile)
event_system.emit('set_pre_drop_dnd', (files,)) event_system.emit('set-pre-drop-dnd', (files,))

View File

@ -1,6 +1,8 @@
# Python imports # Python imports
# Lib imports # Lib imports
import gi
from gi.repository import GLib
# Application imports # Application imports
@ -8,13 +10,22 @@
class IPCSignalsMixin: class IPCSignalsMixin:
""" IPCSignalsMixin handle messages from another starting solarfm process. """ """ IPCSignalsMixin handle messages from another starting {APP_NAME} process. """
def print_to_console(self, message=None): def print_to_console(self, message = None):
logger.debug(message) logger.debug(message)
def handle_file_from_ipc(self, path: str) -> None: def handle_file_from_ipc(self, fpath: str) -> None:
logger.debug(f"File From IPC: {path}") logger.debug(f"File From IPC: {fpath}")
GLib.idle_add(
self.broadcast_message, "handle-file", (fpath,)
)
def handle_dir_from_ipc(self, path: str) -> None: def handle_dir_from_ipc(self, fpath: str) -> None:
logger.debug(f"Dir From IPC: {path}") logger.debug(f"Dir From IPC: {fpath}")
GLib.idle_add(
self.broadcast_message, "handle-folder", (fpath,)
)
def broadcast_message(self, message_type: str = "none", data: () = ()) -> None:
event_system.emit(message_type, data)

View File

@ -68,11 +68,11 @@ class KeyboardSignalsMixin:
if mapping: if mapping:
self.handle_mapped_key_event(mapping) self.handle_mapped_key_event(mapping)
else: else:
self.handle_as_key_event_scope(mapping) self.handle_as_key_event_scope(keyname)
def handle_mapped_key_event(self, mapping): def handle_mapped_key_event(self, mapping):
try: try:
self.handle_as_controller_scope() self.handle_as_controller_scope(mapping)
except Exception: except Exception:
self.handle_as_plugin_scope(mapping) self.handle_as_plugin_scope(mapping)
@ -86,13 +86,11 @@ class KeyboardSignalsMixin:
sender = "" sender = ""
eve_type = mapping eve_type = mapping
self.handle_as_key_event_system(sender, eve_type) self.handle_key_event_system(sender, eve_type)
def handle_as_key_event_scope(self, mapping):
logger.debug(f"on_global_key_release_controller > key > {keyname}")
def handle_as_key_event_scope(self, keyname):
if self.ctrl_down and not keyname in ["1", "kp_1", "2", "kp_2", "3", "kp_3", "4", "kp_4"]: if self.ctrl_down and not keyname in ["1", "kp_1", "2", "kp_2", "3", "kp_3", "4", "kp_4"]:
self.handle_key_event_system(None, mapping) self.handle_key_event_system(None, keyname)
def handle_key_event_system(self, sender, eve_type): def handle_key_event_system(self, sender, eve_type):
event_system.emit(eve_type) event_system.emit(eve_type)

View File

@ -0,0 +1,4 @@
"""
Settings Package
"""
from .manager import SettingsManager

View File

@ -1,5 +1,6 @@
# Python imports # Python imports
import inspect import inspect
import time
import json import json
import zipfile import zipfile
@ -22,35 +23,35 @@ class MissingConfigError(Exception):
class SettingsManager(StartCheckMixin, Singleton): class SettingsManager(StartCheckMixin, Singleton):
def __init__(self): def __init__(self):
self._SCRIPT_PTH = path.dirname(path.realpath(__file__)) self._SCRIPT_PTH: str = path.dirname(path.realpath(__file__))
self._USER_HOME = path.expanduser('~') self._USER_HOME: str = path.expanduser('~')
self._HOME_CONFIG_PATH = f"{self._USER_HOME}/.config/{app_name.lower()}" self._HOME_CONFIG_PATH: str = f"{self._USER_HOME}/.config/{APP_NAME.lower()}"
self._USR_PATH = f"/usr/share/{app_name.lower()}" self._USR_PATH: str = f"/usr/share/{APP_NAME.lower()}"
self._USR_CONFIG_FILE = f"{self._USR_PATH}/settings.json" self._USR_CONFIG_FILE: str = f"{self._USR_PATH}/settings.json"
self._CONTEXT_PATH = f"{self._HOME_CONFIG_PATH}/context_path" self._CONTEXT_PATH: str = f"{self._HOME_CONFIG_PATH}/context_path"
self._PLUGINS_PATH = f"{self._HOME_CONFIG_PATH}/plugins" self._PLUGINS_PATH: str = f"{self._HOME_CONFIG_PATH}/plugins"
self._DEFAULT_ICONS = f"{self._HOME_CONFIG_PATH}/icons" self._DEFAULT_ICONS: str = f"{self._HOME_CONFIG_PATH}/icons"
self._CONFIG_FILE = f"{self._HOME_CONFIG_PATH}/settings.json" self._CONFIG_FILE: str = f"{self._HOME_CONFIG_PATH}/settings.json"
self._GLADE_FILE = f"{self._HOME_CONFIG_PATH}/Main_Window.glade" self._GLADE_FILE: str = f"{self._HOME_CONFIG_PATH}/Main_Window.glade"
self._CSS_FILE = f"{self._HOME_CONFIG_PATH}/stylesheet.css" self._CSS_FILE: str = f"{self._HOME_CONFIG_PATH}/stylesheet.css"
self._KEY_BINDINGS_FILE = f"{self._HOME_CONFIG_PATH}/key-bindings.json" self._KEY_BINDINGS_FILE: str = f"{self._HOME_CONFIG_PATH}/key-bindings.json"
self._PID_FILE = f"{self._HOME_CONFIG_PATH}/{app_name.lower()}.pid" self._PID_FILE: str = f"{self._HOME_CONFIG_PATH}/{APP_NAME.lower()}.pid"
self._UI_WIDEGTS_PATH = f"{self._HOME_CONFIG_PATH}/ui_widgets" self._UI_WIDEGTS_PATH: str = f"{self._HOME_CONFIG_PATH}/ui_widgets"
self._CONTEXT_MENU = f"{self._HOME_CONFIG_PATH}/contexct_menu.json" self._CONTEXT_MENU: str = f"{self._HOME_CONFIG_PATH}/contexct_menu.json"
self._WINDOW_ICON = f"{self._DEFAULT_ICONS}/{app_name.lower()}.png" self._WINDOW_ICON: str = f"{self._DEFAULT_ICONS}/{APP_NAME.lower()}.png"
# self._USR_CONFIG_FILE = f"{self._USR_PATH}/settings.json" # self._USR_CONFIG_FILE: str = f"{self._USR_PATH}/settings.json"
# self._PLUGINS_PATH = f"plugins" # self._PLUGINS_PATH: str = f"plugins"
# self._CONFIG_FILE = f"settings.json" # self._CONFIG_FILE: str = f"settings.json"
# self._GLADE_FILE = f"Main_Window.glade" # self._GLADE_FILE: str = f"Main_Window.glade"
# self._CSS_FILE = f"stylesheet.css" # self._CSS_FILE: str = f"stylesheet.css"
# self._KEY_BINDINGS_FILE = f"key-bindings.json" # self._KEY_BINDINGS_FILE: str = f"key-bindings.json"
# self._PID_FILE = f"{app_name.lower()}.pid" # self._PID_FILE: str = f"{APP_NAME.lower()}.pid"
# self._WINDOW_ICON = f"{app_name.lower()}.png" # self._WINDOW_ICON: str = f"{APP_NAME.lower()}.png"
# self._UI_WIDEGTS_PATH = f"ui_widgets" # self._UI_WIDEGTS_PATH: str = f"ui_widgets"
# self._CONTEXT_MENU = f"contexct_menu.json" # self._CONTEXT_MENU: str = f"contexct_menu.json"
# self._DEFAULT_ICONS = f"icons" # self._DEFAULT_ICONS: str = f"icons"
# with zipfile.ZipFile("files.zip", mode="r", allowZip64=True) as zf: # with zipfile.ZipFile("files.zip", mode="r", allowZip64=True) as zf:
@ -79,7 +80,7 @@ class SettingsManager(StartCheckMixin, Singleton):
if not path.exists(self._CSS_FILE): if not path.exists(self._CSS_FILE):
raise MissingConfigError("Unable to find the application Stylesheet file.") raise MissingConfigError("Unable to find the application Stylesheet file.")
if not path.exists(self._WINDOW_ICON): if not path.exists(self._WINDOW_ICON):
self._WINDOW_ICON = f"{self._USR_PATH}/icons/{app_name.lower()}.png" self._WINDOW_ICON = f"{self._USR_PATH}/icons/{APP_NAME.lower()}.png"
if not path.exists(self._WINDOW_ICON): if not path.exists(self._WINDOW_ICON):
raise MissingConfigError("Unable to find the application icon.") raise MissingConfigError("Unable to find the application icon.")
if not path.exists(self._UI_WIDEGTS_PATH): if not path.exists(self._UI_WIDEGTS_PATH):
@ -105,20 +106,22 @@ class SettingsManager(StartCheckMixin, Singleton):
self.settings: Settings = None self.settings: Settings = None
self._main_window = None self._main_window = None
self._builder = None self._builder = None
self.PAINT_BG_COLOR = (0, 0, 0, 0.0) self.PAINT_BG_COLOR: tuple = (0, 0, 0, 0.0)
self._trace_debug = False self._trace_debug: bool = False
self._debug = False self._debug: bool = False
self._dirty_start = False self._dirty_start: bool = False
self._passed_in_file: bool = False
self._starting_files: list = []
def register_signals_to_builder(self, classes=None): def register_signals_to_builder(self, classes = None):
handlers = {} handlers = {}
for c in classes: for c in classes:
methods = None methods = None
try: try:
methods = inspect.getmembers(c, predicate=inspect.ismethod) methods = inspect.getmembers(c, predicate = inspect.ismethod)
handlers.update(methods) handlers.update(methods)
except Exception as e: except Exception as e:
... ...
@ -128,7 +131,6 @@ class SettingsManager(StartCheckMixin, Singleton):
def set_main_window(self, window): self._main_window = window def set_main_window(self, window): self._main_window = window
def set_builder(self, builder) -> any: self._builder = builder def set_builder(self, builder) -> any: self._builder = builder
def get_monitor_data(self) -> list: def get_monitor_data(self) -> list:
screen = self._main_window.get_screen() screen = self._main_window.get_screen()
monitors = [] monitors = []
@ -152,29 +154,45 @@ class SettingsManager(StartCheckMixin, Singleton):
def get_home_config_path(self) -> str: return self._HOME_CONFIG_PATH def get_home_config_path(self) -> str: return self._HOME_CONFIG_PATH
def get_window_icon(self) -> str: return self._WINDOW_ICON 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
def get_starting_files(self) -> list: return self._starting_files
def get_starting_args(self):
return self.args, self.unknownargs
def set_main_window_x(self, x: int = 0): self.settings.config.main_window_x = x
def set_main_window_y(self, y: int = 0): self.settings.config.main_window_y = y
def set_main_window_width(self, width: int = 800): self.settings.config.main_window_width = width
def set_main_window_height(self, height: int = 600): self.settings.config.main_window_height = height
def set_main_window_min_width(self, width: int = 720): self.settings.config.main_window_min_width = width
def set_main_window_min_height(self, height: int = 480): self.settings.config.main_window_min_height = height
def set_starting_files(self, files: list): self._starting_files = files
def set_start_load_time(self): self._start_load_time = time.perf_counter()
def set_end_load_time(self): self._end_load_time = time.perf_counter()
def set_starting_args(self, args, unknownargs):
self.args = args
self.unknownargs = unknownargs
def set_trace_debug(self, trace_debug: bool):
self._trace_debug = trace_debug
def set_debug(self, debug: bool):
self._debug = debug
def set_is_starting_with_file(self, is_passed_in_file: bool = False):
self._passed_in_file = is_passed_in_file
def is_trace_debug(self) -> str: return self._trace_debug def is_trace_debug(self) -> str: return self._trace_debug
def is_debug(self) -> str: return self._debug def is_debug(self) -> str: return self._debug
def is_starting_with_file(self) -> bool: return self._passed_in_file
def call_method(self, target_class = None, _method_name = None, data = None): def log_load_time(self): logger.info( f"Load Time: {self._end_load_time - self._start_load_time}" )
def call_method(self, target_class: any = None, _method_name: str = "", data: any = None):
method_name = str(_method_name) method_name = str(_method_name)
method = getattr(target_class, method_name, lambda data: f"No valid key passed...\nkey={method_name}\nargs={data}") method = getattr(target_class, method_name, lambda data: f"No valid key passed...\nkey={method_name}\nargs={data}")
return method(data) if data else method() return method(data) if data else method()
def set_main_window_x(self, x = 0): self.settings.config.main_window_x = x
def set_main_window_y(self, y = 0): self.settings.config.main_window_y = y
def set_main_window_width(self, width = 800): self.settings.config.main_window_width = width
def set_main_window_height(self, height = 600): self.settings.config.main_window_height = height
def set_main_window_min_width(self, width = 720): self.settings.config.main_window_min_width = width
def set_main_window_min_height(self, height = 480): self.settings.config.main_window_min_height = height
def set_trace_debug(self, trace_debug):
self._trace_debug = trace_debug
def set_debug(self, debug):
self._debug = debug
def load_settings(self): def load_settings(self):
if not path.exists(self._CONFIG_FILE): if not path.exists(self._CONFIG_FILE):
self.settings = Settings() self.settings = Settings()

View File

@ -1,5 +1,5 @@
""" """
Options module Settings.Options Package
""" """
from .settings import Settings from .settings import Settings
from .config import Config from .config import Config

View File

@ -0,0 +1,3 @@
"""
Settings.Other Package
"""

View File

@ -39,4 +39,4 @@ class WebkitUISettings(WebKit2.Settings):
self.set_enable_webaudio(True) self.set_enable_webaudio(True)
self.set_enable_accelerated_2d_canvas(True) self.set_enable_accelerated_2d_canvas(True)
self.set_user_agent(f"{app_name}") self.set_user_agent(f"{APP_NAME}")

View File

@ -41,7 +41,7 @@ class StartCheckMixin:
try: try:
os.kill(pid, 0) os.kill(pid, 0)
except OSError: except OSError:
print(f"{app_name} PID file exists but PID is irrelevant; starting dirty...") print(f"{APP_NAME} PID file exists but PID is irrelevant; starting dirty...")
self._dirty_start = True self._dirty_start = True
return False return False
@ -53,7 +53,7 @@ class StartCheckMixin:
self._print_pid(pid) self._print_pid(pid)
def _print_pid(self, pid): def _print_pid(self, pid):
print(f"{app_name} PID: {pid}") print(f"{APP_NAME} PID: {pid}")
def _clean_pid(self): def _clean_pid(self):
os.unlink(self._PID_FILE) os.unlink(self._PID_FILE)

View File

@ -1,4 +0,0 @@
"""
Settings module
"""
from .manager import SettingsManager

View File

@ -1,3 +0,0 @@
"""
Settings Other module
"""

67
src/libs/status_icon.py Normal file
View File

@ -0,0 +1,67 @@
# Python imports
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('AppIndicator3', '0.1')
from gi.repository import Gtk
from gi.repository import GLib
from gi.repository import AppIndicator3
# Application imports
class StatusIcon():
""" StatusIcon for Application to go to Status Tray. """
def __init__(self):
self._setup_styling()
self._setup_signals()
self._subscribe_to_events()
self._load_widgets()
def _setup_styling(self):
...
def _setup_signals(self):
...
def _subscribe_to_events(self):
...
def _load_widgets(self):
status_menu = Gtk.Menu()
icon_theme = Gtk.IconTheme.get_default()
check_menu_item = Gtk.CheckMenuItem.new_with_label("Update icon")
quit_menu_item = Gtk.MenuItem.new_with_label("Quit")
# Create StatusNotifierItem
self.indicator = AppIndicator3.Indicator.new(
f"{APP_NAME}-statusicon",
"gtk-info",
AppIndicator3.IndicatorCategory.APPLICATION_STATUS)
self.indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE)
check_menu_item.connect("activate", self.check_menu_item_cb)
quit_menu_item.connect("activate", self.quit_menu_item_cb)
icon_theme.connect('changed', self.icon_theme_changed_cb)
self.indicator.set_menu(status_menu)
status_menu.append(check_menu_item)
status_menu.append(quit_menu_item)
status_menu.show_all()
def update_icon(self, icon_name):
self.indicator.set_icon(icon_name)
def check_menu_item_cb(self, widget, data = None):
icon_name = "parole" if widget.get_active() else "gtk-info"
self.update_icon(icon_name)
def icon_theme_changed_cb(self, theme):
self.update_icon("gtk-info")
def quit_menu_item_cb(self, widget, data = None):
event_system.emit("tear-down")

View File

@ -22,6 +22,7 @@ class Plugin:
support: str = None support: str = None
requests:{} = None requests:{} = None
reference: type = None reference: type = None
pre_launch: bool = False
class ManifestProcessor: class ManifestProcessor:
@ -46,23 +47,29 @@ class ManifestProcessor:
plugin.support = self._manifest["support"] plugin.support = self._manifest["support"]
plugin.requests = self._manifest["requests"] plugin.requests = self._manifest["requests"]
if "pre_launch" in self._manifest.keys():
plugin.pre_launch = True if self._manifest["pre_launch"] == "true" else False
return plugin return plugin
def get_loading_data(self): def get_loading_data(self):
loading_data = {} loading_data = {}
requests = self._plugin.requests requests = self._plugin.requests
keys = requests.keys()
if "pass_events" in keys: if "pass_events" in requests:
if requests["pass_events"] in ["true"]: if requests["pass_events"] in ["true"]:
loading_data["pass_events"] = True loading_data["pass_events"] = True
if "bind_keys" in keys: if "pass_ui_objects" in requests:
if isinstance(requests["bind_keys"], list):
loading_data["bind_keys"] = requests["bind_keys"]
if "pass_ui_objects" in keys:
if isinstance(requests["pass_ui_objects"], list): if isinstance(requests["pass_ui_objects"], list):
loading_data["pass_ui_objects"] = [ self._builder.get_object(obj) for obj in requests["pass_ui_objects"] ] loading_data["pass_ui_objects"] = [ self._builder.get_object(obj) for obj in requests["pass_ui_objects"] ]
if "bind_keys" in requests:
if isinstance(requests["bind_keys"], list):
loading_data["bind_keys"] = requests["bind_keys"]
return self._plugin, loading_data return self._plugin, loading_data
def is_pre_launch(self):
return self._plugin.pre_launch

View File

@ -10,6 +10,7 @@ from os.path import isdir
import gi import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
from gi.repository import GLib
from gi.repository import Gio from gi.repository import Gio
# Application imports # Application imports
@ -35,11 +36,23 @@ class PluginsController:
self._plugins_dir_watcher = None self._plugins_dir_watcher = None
self._plugin_collection = [] self._plugin_collection = []
self._plugin_manifests = {}
self._load_manifests()
def launch_plugins(self) -> None: def _load_manifests(self):
logger.info(f"Loading manifests...")
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)]:
manifest = ManifestProcessor(path, self._builder)
self._plugin_manifests[path] = {
"path": path,
"folder": folder,
"manifest": manifest
}
self._set_plugins_watcher() self._set_plugins_watcher()
self.load_plugins()
def _set_plugins_watcher(self) -> None: def _set_plugins_watcher(self) -> None:
self._plugins_dir_watcher = Gio.File.new_for_path(self._plugins_path) \ self._plugins_dir_watcher = Gio.File.new_for_path(self._plugins_path) \
@ -52,21 +65,47 @@ class PluginsController:
Gio.FileMonitorEvent.MOVED_OUT]: Gio.FileMonitorEvent.MOVED_OUT]:
self.reload_plugins(file) self.reload_plugins(file)
def load_plugins(self, file: str = None) -> None: def pre_launch_plugins(self) -> None:
logger.info(f"Loading plugins...") logger.info(f"Loading pre-launch plugins...")
plugin_manifests: {} = {}
for key in self._plugin_manifests:
target_manifest = self._plugin_manifests[key]["manifest"]
if target_manifest.is_pre_launch():
plugin_manifests[key] = self._plugin_manifests[key]
self._load_plugins(plugin_manifests, is_pre_launch = True)
def post_launch_plugins(self) -> None:
logger.info(f"Loading post-launch plugins...")
plugin_manifests: {} = {}
for key in self._plugin_manifests:
target_manifest = self._plugin_manifests[key]["manifest"]
if not target_manifest.is_pre_launch():
plugin_manifests[key] = self._plugin_manifests[key]
self._load_plugins(plugin_manifests)
def _load_plugins(self, plugin_manifests: {} = {}, is_pre_launch: bool = False) -> None:
parent_path = os.getcwd() 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)]: for key in plugin_manifests:
target_manifest = plugin_manifests[key]
path, folder, manifest = target_manifest["path"], target_manifest["folder"], target_manifest["manifest"]
try: try:
target = join(path, "plugin.py") target = join(path, "plugin.py")
manifest = ManifestProcessor(path, self._builder)
if not os.path.exists(target): if not os.path.exists(target):
raise InvalidPluginException("Invalid Plugin Structure: Plugin doesn't have 'plugin.py'. Aboarting load...") raise InvalidPluginException("Invalid Plugin Structure: Plugin doesn't have 'plugin.py'. Aboarting load...")
plugin, loading_data = manifest.get_loading_data() plugin, loading_data = manifest.get_loading_data()
module = self.load_plugin_module(path, folder, target) module = self.load_plugin_module(path, folder, target)
if is_pre_launch:
self.execute_plugin(module, plugin, loading_data) self.execute_plugin(module, plugin, loading_data)
else:
GLib.idle_add(self.execute_plugin, *(module, plugin, loading_data))
except Exception as e: except Exception as e:
logger.info(f"Malformed Plugin: Not loading -->: '{folder}' !") logger.info(f"Malformed Plugin: Not loading -->: '{folder}' !")
logger.debug("Trace: ", traceback.print_exc()) logger.debug("Trace: ", traceback.print_exc())

View File

@ -43,6 +43,6 @@
/* Other message text colors */ /* Other message text colors */
.errorTxt { color: rgb(170, 18, 18); } .error-txt { color: rgb(170, 18, 18); }
.warningTxt { color: rgb(255, 168, 0); } .warning-txt { color: rgb(255, 168, 0); }
.successTxt { color: rgb(136, 204, 39); } .success=txt { color: rgb(136, 204, 39); }

View File

@ -1,6 +1,7 @@
html, body { html, body {
display: block; display: block;
background-color: #32383e00; // background-color: #32383e00;
background-color: rgba(39, 43, 52, 0.64);
color: #ffffff; color: #ffffff;
text-wrap: wrap; text-wrap: wrap;
} }

View File

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -1,23 +1,24 @@
{ {
"keybindings": { "keybindings": {
"help" : "F1", "help" : "F1",
"rename_files" : ["F2", "<Control>e"], "rename-files" : ["F2", "<Control>e"],
"open_terminal" : "F4", "open-terminal" : "F4",
"refresh_tab" : ["F5", "<Control>r"], "refresh-tab" : ["F5", "<Control>r"],
"delete_files" : "Delete", "delete-files" : "Delete",
"tggl_top_main_menubar" : "Alt_L", "tggl-top-main-menubar" : "Alt_L",
"trash_files" : "<Shift><Control>t", "tggl-top-main-menubar" : "<Control>0",
"tear_down" : "<Control>q", "trash-files" : "<Shift><Control>t",
"go_up" : "<Control>Up", "tear-down" : "<Control>q",
"go_home" : "<Control>slash", "go-up" : "<Control>Up",
"grab_focus_path_entry" : "<Control>l", "go-home" : "<Control>slash",
"open_files" : "<Control>o", "grab-focus-path-entry" : "<Control>l",
"show_hide_hidden_files" : "<Control>h", "open-files" : "<Control>o",
"keyboard_create_tab" : "<Control>t", "show-hide-hidden-files" : "<Control>h",
"keyboard_close_tab" : "<Control>w", "keyboard-create-tab" : "<Control>t",
"keyboard_copy_files" : "<Control>c", "keyboard-close-tab" : "<Control>w",
"keyboard_cut_files" : "<Control>x", "keyboard-copy-files" : "<Control>c",
"paste_files" : "<Control>v", "keyboard-cut-files" : "<Control>x",
"show_new_file_menu" : "<Control>n" "paste-files" : "<Control>v",
"show-new-file-menu" : "<Control>n"
} }
} }

View File

@ -1,3 +1,18 @@
/* ---- Make most desired things base transparent ---- */
/* ---- Make most desired things base transparent ---- */
popover,
popover > box
.main-window,
.base-container,
.body-container,
.center-container,
.header-container,
.left-containerm,
.right-container {
background: rgba(0, 0, 0, 0.0);
color: rgba(255, 255, 255, 1);
}
.base-container { .base-container {
margin: 10px; margin: 10px;
} }
@ -128,3 +143,4 @@ XfdesktopIconView.view:active {
.mw_transparency_97 { background: rgba(39, 43, 52, 0.97); } .mw_transparency_97 { background: rgba(39, 43, 52, 0.97); }
.mw_transparency_98 { background: rgba(39, 43, 52, 0.98); } .mw_transparency_98 { background: rgba(39, 43, 52, 0.98); }
.mw_transparency_99 { background: rgba(39, 43, 52, 0.99); } .mw_transparency_99 { background: rgba(39, 43, 52, 0.99); }
.mw_transparency_100 { background: rgba(39, 43, 52, 1.00); }