Import refactor, deb build update, docstring additions

This commit is contained in:
itdominator 2022-02-20 01:32:51 -06:00
parent 312a782a87
commit 56b8ee6117
101 changed files with 1117 additions and 817 deletions

Binary file not shown.

View File

@ -1,12 +0,0 @@
#!/bin/bash
# set -o xtrace ## To debug scripts
# set -o errexit ## To exit on error
# set -o errunset ## To exit if a variable is referenced but not set
function main() {
find . -name "__pycache__" -exec rm -rf $1 {} \;
find . -name "*.pyc" -exec rm -rf $1 {} \;
}
main

View File

@ -4,19 +4,22 @@ import builtins
# Lib imports
# Application imports
from signal_classes import IPCServerMixin
from context.ipc_server_mixin import IPCServerMixin
class Builtins(IPCServerMixin):
"""Docstring for __builtins__ extender"""
""" Inheret IPCServerMixin. Create an pub/sub systems. """
def __init__(self):
# NOTE: The format used is list of [type, target, data]
# NOTE: The format used is list of [type, target, (data,)] Where:
# type is useful context for control flow,
# target is the method to call,
# data is the method parameters to give
# Where data may be any kind of data
self._gui_events = []
self._fm_events = []
self._module_events = []
self.is_ipc_alive = False
self.ipc_authkey = b'solarfm-ipc'
self.ipc_address = '127.0.0.1'
@ -30,9 +33,9 @@ class Builtins(IPCServerMixin):
return self._gui_events.pop(0)
return None
def _pop_fm_event(self):
if len(self._fm_events) > 0:
return self._fm_events.pop(0)
def _pop_module_event(self):
if len(self._module_events) > 0:
return self._module_events.pop(0)
return None
@ -41,32 +44,32 @@ class Builtins(IPCServerMixin):
self._gui_events.append(event)
return None
raise Exception("Invald event format! Please do: [type, target, data]")
raise Exception("Invald event format! Please do: [type, target, (data,)]")
def push_fm_event(self, event):
def push_module_event(self, event):
if len(event) == 3:
self._fm_events.append(event)
self._module_events.append(event)
return None
raise Exception("Invald event format! Please do: [type, target, data]")
raise Exception("Invald event format! Please do: [type, target, (data,)]")
def read_gui_event(self):
return self._gui_events[0]
def read_fm_event(self):
return self._fm_events[0]
def read_module_event(self):
return self._module_events[0]
def consume_gui_event(self):
return self._pop_gui_event()
def consume_fm_event(self):
return self._pop_fm_event()
def consume_module_event(self):
return self._pop_module_event()
# NOTE: Just reminding myself we can add to builtins two different ways...
# __builtins__.update({"event_system": Builtins()})
builtins.app_name = "SolarFM"
builtins.app_name = "SolarFM"
builtins.event_system = Builtins()
builtins.event_sleep_time = 0.2
builtins.debug = False

View File

@ -4,20 +4,22 @@ import os, inspect, time
# Lib imports
# Application imports
from utils import Settings
from signal_classes import Controller
from utils.settings import Settings
from context.controller import Controller
from __builtins__ import Builtins
class Main(Builtins):
""" Create Settings and Controller classes. Bind signal to Builder. Inherit from Builtins to bind global methods and classes. """
def __init__(self, args, unknownargs):
if not debug:
event_system.create_ipc_server()
time.sleep(0.2)
if not trace_debug:
if not trace_debug and not debug:
if not event_system.is_ipc_alive:
if unknownargs:
for arg in unknownargs:

View File

@ -19,6 +19,8 @@ from __init__ import Main
if __name__ == "__main__":
""" Set process title, get arguments, and create GTK main thread. """
try:
# import web_pdb
# web_pdb.set_trace()

View File

@ -0,0 +1,3 @@
"""
Gtk Bound Signal Module
"""

View File

@ -7,9 +7,11 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib
# Application imports
from .mixins import ExceptionHookMixin, UIMixin
from .signals import IPCSignalsMixin, KeyboardSignalsMixin
from . import Controller_Data
from .mixins.exception_hook_mixin import ExceptionHookMixin
from .mixins.ui_mixin import UIMixin
from .signals.ipc_signals_mixin import IPCSignalsMixin
from .signals.keyboard_signals_mixin import KeyboardSignalsMixin
from .controller_data import Controller_Data
def threaded(fn):
@ -21,7 +23,7 @@ def threaded(fn):
class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMixin, Controller_Data):
''' Controller coordinates the mixins and is somewhat the root hub of it all. '''
""" Controller coordinates the mixins and is somewhat the root hub of it all. """
def __init__(self, args, unknownargs, _settings):
self.setup_controller_data(_settings)
self.window.show()

View File

@ -7,13 +7,13 @@ from gi.repository import GLib
# Application imports
from trasher.xdgtrash import XDGTrash
from shellfm.windows.controller import WindowController
from plugins import Plugins
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. '''
""" 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):
self.trashman = XDGTrash()

View File

@ -16,7 +16,7 @@ def threaded(fn):
class IPCServerMixin:
''' Create a listener so that other SolarFM instances send requests back to existing instance. '''
""" Create a listener so that other SolarFM instances send requests back to existing instance. """
@threaded
def create_ipc_server(self):

View File

@ -16,7 +16,7 @@ def threaded(fn):
class ExceptionHookMixin:
''' ExceptionHookMixin custom exception hook to reroute to a Gtk text area. '''
""" ExceptionHookMixin custom exception hook to reroute to a Gtk text area. """
def custom_except_hook(self, exec_type, value, _traceback):
trace = ''.join(traceback.format_tb(_traceback))

View File

@ -7,7 +7,7 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
from . import WidgetMixin
from .widget_mixin import WidgetMixin

View File

@ -16,6 +16,8 @@ def threaded(fn):
class WidgetFileActionMixin:
"""docstring for WidgetFileActionMixin"""
def sizeof_fmt(self, num, suffix="B"):
for unit in ["", "K", "M", "G", "T", "Pi", "Ei", "Zi"]:
if abs(num) < 1024.0:

View File

@ -20,6 +20,8 @@ def threaded(fn):
class WidgetMixin:
"""docstring for WidgetMixin"""
def load_store(self, view, store, save_state=False):
store.clear()
dir = view.get_current_directory()

View File

@ -9,13 +9,15 @@ gi.require_version('Gdk', '3.0')
from gi.repository import Gdk, Gio
# Application imports
from . import TabMixin, WidgetMixin
from .tab_mixin import TabMixin
from .widget_mixin import WidgetMixin
class WindowMixin(TabMixin):
"""docstring for WindowMixin"""
def generate_windows(self, session_json = None):
if session_json:
for j, value in enumerate(session_json):

View File

@ -0,0 +1,14 @@
# Python imports
# Gtk imports
# Application imports
from .show_hide_mixin import ShowHideMixin
from .ui.widget_file_action_mixin import WidgetFileActionMixin
from .ui.pane_mixin import PaneMixin
from .ui.window_mixin import WindowMixin
from .show_hide_mixin import ShowHideMixin
class UIMixin(WidgetFileActionMixin, PaneMixin, WindowMixin, ShowHideMixin):
pass

View File

@ -6,6 +6,8 @@
class IPCSignalsMixin:
""" IPCSignalsMixin handle messages from another starting solarfm process. """
def print_to_console(self, message=None):
print(self)
print(message)

View File

@ -14,6 +14,8 @@ valid_keyvalue_pat = re.compile(r"[a-z0-9A-Z-_\[\]\(\)\| ]")
class KeyboardSignalsMixin:
""" KeyboardSignalsMixin keyboard hooks controller. """
def unset_keys_and_data(self, widget=None, eve=None):
self.ctrlDown = False
self.shiftDown = False

View File

@ -0,0 +1,3 @@
"""
Gtk Bound Plugins Module
"""

View File

@ -19,7 +19,8 @@ class Plugin:
class Plugins:
"""docstring for Plugins"""
"""Plugins controller"""
def __init__(self, settings):
self._settings = settings
self._plugin_list_widget = self._settings.get_builder().get_object("plugin_list")

View File

@ -1 +0,0 @@
from .windows import WindowController

View File

@ -1,66 +0,0 @@
# Python imports
from random import randint
# Lib imports
# Application imports
from .view import View
class Window:
def __init__(self):
self.id_length = 10
self.id = ""
self.name = ""
self.nickname = ""
self.isHidden = False
self.views = []
self.generate_id()
def random_with_N_digits(self, n):
range_start = 10**(n-1)
range_end = (10**n)-1
return randint(range_start, range_end)
def generate_id(self):
self.id = str(self.random_with_N_digits(self.id_length))
def get_window_id(self):
return self.id
def create_view(self):
view = View()
self.views.append(view)
return view
def pop_view(self):
self.views.pop()
def delete_view_by_id(self, vid):
for view in self.views:
if view.id == vid:
self.views.remove(view)
break
def get_view_by_id(self, vid):
for view in self.views:
if view.id == vid:
return view
def get_view_by_index(self, index):
return self.views[index]
def get_views_count(self):
return len(self.views)
def get_all_views(self):
return self.views
def list_files_from_views(self):
for view in self.views:
print(view.files)

View File

@ -1,181 +0,0 @@
# Python imports
import threading, subprocess, time, json
from os import path
# Lib imports
# Application imports
from . import Window
def threaded(fn):
def wrapper(*args, **kwargs):
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start()
return wrapper
class WindowController:
def __init__(self):
USER_HOME = path.expanduser('~')
CONFIG_PATH = USER_HOME + "/.config/solarfm"
self.session_file = CONFIG_PATH + "/session.json"
self._event_sleep_time = 1
self.active_window_id = ""
self.active_tab_id = ""
self.windows = []
if not trace_debug:
self.fm_event_observer()
@threaded
def fm_event_observer(self):
while True:
time.sleep(event_sleep_time)
event = event_system.consume_fm_event()
if event:
print(event)
def set_active_data(self, wid, tid):
self.active_window_id = str(wid)
self.active_tab_id = str(tid)
def get_active_data(self):
return self.active_window_id, self.active_tab_id
def create_window(self):
window = Window()
window.name = "window_" + window.id
window.nickname = "window_" + str(len(self.windows) + 1)
self.windows.append(window)
return window
def add_view_for_window(self, win_id):
for window in self.windows:
if window.id == win_id:
return window.create_view()
def add_view_for_window_by_name(self, name):
for window in self.windows:
if window.name == name:
return window.create_view()
def add_view_for_window_by_nickname(self, nickname):
for window in self.windows:
if window.nickname == nickname:
return window.create_view()
def pop_window(self):
self.windows.pop()
def delete_window_by_id(self, win_id):
for window in self.windows:
if window.id == win_id:
self.windows.remove(window)
break
def delete_window_by_name(self, name):
for window in self.windows:
if window.name == name:
self.windows.remove(window)
break
def delete_window_by_nickname(self, nickname):
for window in self.windows:
if window.nickname == nickname:
self.windows.remove(window)
break
def get_window_by_id(self, win_id):
for window in self.windows:
if window.id == win_id:
return window
raise(f"No Window by ID {win_id} found!")
def get_window_by_name(self, name):
for window in self.windows:
if window.name == name:
return window
raise(f"No Window by Name {name} found!")
def get_window_by_nickname(self, nickname):
for window in self.windows:
if window.nickname == nickname:
return window
raise(f"No Window by Nickname {nickname} found!")
def get_window_by_index(self, index):
return self.windows[index]
def get_all_windows(self):
return self.windows
def set_window_nickname(self, win_id = None, nickname = ""):
for window in self.windows:
if window.id == win_id:
window.nickname = nickname
def list_windows(self):
print("\n[ ---- Windows ---- ]\n")
for window in self.windows:
print(f"\nID: {window.id}")
print(f"Name: {window.name}")
print(f"Nickname: {window.nickname}")
print(f"Is Hidden: {window.isHidden}")
print(f"View Count: {window.get_views_count()}")
print("\n-------------------------\n")
def list_files_from_views_of_window(self, win_id):
for window in self.windows:
if window.id == win_id:
window.list_files_from_views()
break
def get_views_count(self, win_id):
for window in self.windows:
if window.id == win_id:
return window.get_views_count()
def get_views_from_window(self, win_id):
for window in self.windows:
if window.id == win_id:
return window.get_all_views()
def save_state(self):
windows = []
for window in self.windows:
views = []
for view in window.views:
views.append(view.get_current_directory())
windows.append(
[
{
'window':{
"ID": window.id,
"Name": window.name,
"Nickname": window.nickname,
"isHidden": f"{window.isHidden}",
'views': views
}
}
]
)
with open(self.session_file, 'w') as outfile:
json.dump(windows, outfile, separators=(',', ':'), indent=4)
def load_state(self):
if path.isfile(self.session_file):
with open(self.session_file) as infile:
return json.load(infile)

View File

@ -1,2 +0,0 @@
from .Window import Window
from .WindowController import WindowController

View File

@ -0,0 +1,185 @@
# Python imports
import threading, json
from os import path
# Lib imports
# Application imports
from .window import Window
def threaded(fn):
def wrapper(*args, **kwargs):
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start()
return wrapper
class WindowController:
def __init__(self):
USER_HOME = path.expanduser('~')
CONFIG_PATH = USER_HOME + "/.config/solarfm"
self._session_file = CONFIG_PATH + "/session.json"
self._event_sleep_time = 1
self._active_window_id = ""
self._active_tab_id = ""
self._windows = []
def set__wid_and_tid(self, wid, tid):
self._active_window_id = str(wid)
self._active_tab_id = str(tid)
def get_active_wid_and_tid(self):
return self._active_window_id, self._active_tab_id
def create_window(self):
window = Window()
window.set_nickname(f"window_{str(len(self._windows) + 1)}")
self._windows.append(window)
return window
def add_view_for_window(self, win_id):
for window in self._windows:
if window.get_id() == win_id:
return window.create_view()
def add_view_for_window_by_name(self, name):
for window in self._windows:
if window.get_name() == name:
return window.create_view()
def add_view_for_window_by_nickname(self, nickname):
for window in self._windows:
if window.get_nickname() == nickname:
return window.create_view()
def pop_window(self):
self._windows.pop()
def delete_window_by_id(self, win_id):
for window in self._windows:
if window.get_id() == win_id:
self._windows.remove(window)
break
def delete_window_by_name(self, name):
for window in self._windows:
if window.get_name() == name:
self._windows.remove(window)
break
def delete_window_by_nickname(self, nickname):
for window in self._windows:
if window.get_nickname() == nickname:
self._windows.remove(window)
break
def get_window_by_id(self, win_id):
for window in self._windows:
if window.get_id() == win_id:
return window
raise(f"No Window by ID {win_id} found!")
def get_window_by_name(self, name):
for window in self._windows:
if window.get_name() == name:
return window
raise(f"No Window by Name {name} found!")
def get_window_by_nickname(self, nickname):
for window in self._windows:
if window.get_nickname() == nickname:
return window
raise(f"No Window by Nickname {nickname} found!")
def get_window_by_index(self, index):
return self._windows[index]
def get_all_windows(self):
return self._windows
def set_window_nickname(self, win_id = None, nickname = ""):
for window in self._windows:
if window.get_id() == win_id:
window.set_nickname(nickname)
def list_windows(self):
print("\n[ ---- Windows ---- ]\n")
for window in self._windows:
print(f"\nID: {window.get_id()}")
print(f"Name: {window.get_name()}")
print(f"Nickname: {window.get_nickname()}")
print(f"Is Hidden: {window.is_hidden()}")
print(f"View Count: {window.get_views_count()}")
print("\n-------------------------\n")
def list_files_from_views_of_window(self, win_id):
for window in self._windows:
if window.get_id() == win_id:
window.list_files_from_views()
break
def get_views_count(self, win_id):
for window in self._windows:
if window.get_id() == win_id:
return window.get_views_count()
def get_views_from_window(self, win_id):
for window in self._windows:
if window.get_id() == win_id:
return window.get_all_views()
def unload_views_and_windows(self):
for window in self._windows:
window.get_all_views().clear()
self._windows.clear()
def save_state(self, session_file = None):
if not session_file:
session_file = self._session_file
if len(self._windows) > 0:
windows = []
for window in self._windows:
views = []
for view in window.get_all_views():
views.append(view.get_current_directory())
windows.append(
[
{
'window':{
"ID": window.get_id(),
"Name": window.get_name(),
"Nickname": window.get_nickname(),
"isHidden": f"{window.is_hidden()}",
'views': views
}
}
]
)
with open(session_file, 'w') as outfile:
json.dump(windows, outfile, separators=(',', ':'), indent=4)
else:
raise Exception("Window data corrupted! Can not save session!")
def load_state(self, session_file = None):
if not session_file:
session_file = self._session_file
if path.isfile(session_file):
with open(session_file) as infile:
return json.load(infile)

View File

@ -1,5 +0,0 @@
from .utils import *
from .icons import *
from .Path import Path
from .View import View

View File

@ -1,4 +0,0 @@
from .mixins import DesktopIconMixin
from .mixins import VideoIconMixin
from .Icon import Icon

View File

@ -1,4 +0,0 @@
from . import xdg
from .VideoIconMixin import VideoIconMixin
from .DesktopIconMixin import DesktopIconMixin

View File

@ -1,3 +0,0 @@
from .Settings import Settings
from .Launcher import Launcher
from .FileHandler import FileHandler

View File

@ -3,10 +3,13 @@ import os, subprocess, threading, hashlib
from os.path import isfile
# Gtk imports
import gi
gi.require_version('GdkPixbuf', '2.0')
from gi.repository import GdkPixbuf
# Application imports
from .mixins import *
from .mixins.desktopiconmixin import DesktopIconMixin
from .mixins.videoiconmixin import VideoIconMixin
def threaded(fn):

View File

@ -3,9 +3,6 @@ import os, subprocess, hashlib
from os.path import isfile
# Gtk imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
from .xdg.DesktopEntry import DesktopEntry

View File

@ -1,5 +1,5 @@
# Python imports
import os, shutil, subprocess, threading
import os, shutil
# Lib imports

View File

@ -1,6 +1,5 @@
# Python imports
import hashlib
import os
import hashlib, re
from os import listdir
from os.path import isdir, isfile, join
@ -11,64 +10,43 @@ from random import randint
# Application imports
from .utils import Settings, Launcher, FileHandler
from .icons import Icon
from . import Path
from .utils.settings import Settings
from .utils.launcher import Launcher
from .utils.filehandler import FileHandler
from .icons.icon import Icon
from .path import Path
class View(Settings, FileHandler, Launcher, Icon, Path):
def __init__(self):
self. logger = None
self.id_length = 10
self.logger = None
self._id_length = 10
self.id = ""
self.wid = None
self.dir_watcher = None
self.hide_hidden = self.HIDE_HIDDEN_FILES
self.files = []
self.dirs = []
self.vids = []
self.images = []
self.desktop = []
self.ungrouped = []
self.hidden = []
self._id = ""
self._wid = None
self._dir_watcher = None
self._hide_hidden = self.HIDE_HIDDEN_FILES
self._files = []
self._dirs = []
self._vids = []
self._images = []
self._desktop = []
self._ungrouped = []
self._hidden = []
self.generate_id()
self._generate_id()
self.set_to_home()
def random_with_N_digits(self, n):
range_start = 10**(n-1)
range_end = (10**n)-1
return randint(range_start, range_end)
def generate_id(self):
self.id = str(self.random_with_N_digits(self.id_length))
def get_tab_id(self):
return self.id
def set_wid(self, _wid):
self.wid = _wid
def get_wid(self):
return self.wid
def set_dir_watcher(self, watcher):
self.dir_watcher = watcher
def get_dir_watcher(self):
return self.dir_watcher
def load_directory(self):
path = self.get_path()
self.dirs = []
self.vids = []
self.images = []
self.desktop = []
self.ungrouped = []
self.hidden = []
self.files = []
path = self.get_path()
self._dirs = []
self._vids = []
self._images = []
self._desktop = []
self._ungrouped = []
self._hidden = []
self._files = []
if not isdir(path):
self.set_to_home()
@ -76,40 +54,31 @@ class View(Settings, FileHandler, Launcher, Icon, Path):
for f in listdir(path):
file = join(path, f)
if self.hide_hidden:
if self._hide_hidden:
if f.startswith('.'):
self.hidden.append(f)
self._hidden.append(f)
continue
if isfile(file):
lowerName = file.lower()
if lowerName.endswith(self.fvideos):
self.vids.append(f)
self._vids.append(f)
elif lowerName.endswith(self.fimages):
self.images.append(f)
self._images.append(f)
elif lowerName.endswith((".desktop",)):
self.desktop.append(f)
self._desktop.append(f)
else:
self.ungrouped.append(f)
self._ungrouped.append(f)
else:
self.dirs.append(f)
self._dirs.append(f)
self.dirs.sort()
self.vids.sort()
self.images.sort()
self.desktop.sort()
self.ungrouped.sort()
self._dirs.sort(key=self._natural_keys)
self._vids.sort(key=self._natural_keys)
self._images.sort(key=self._natural_keys)
self._desktop.sort(key=self._natural_keys)
self._ungrouped.sort(key=self._natural_keys)
self.files = self.dirs + self.vids + self.images + self.desktop + self.ungrouped
def hash_text(self, text):
return hashlib.sha256(str.encode(text)).hexdigest()[:18]
def hash_set(self, arry):
data = []
for arr in arry:
data.append([arr, self.hash_text(arr)])
return data
self._files = self._dirs + self._vids + self._images + self._desktop + self._ungrouped
def is_folder_locked(self, hash):
if self.lock_folder:
@ -129,18 +98,18 @@ class View(Settings, FileHandler, Launcher, Icon, Path):
def get_not_hidden_count(self):
return len(self.files) + \
len(self.dirs) + \
len(self.vids) + \
len(self.images) + \
len(self.desktop) + \
len(self.ungrouped)
return len(self._files) + \
len(self._dirs) + \
len(self._vids) + \
len(self._images) + \
len(self._desktop) + \
len(self._ungrouped)
def get_hidden_count(self):
return len(self.hidden)
return len(self._hidden)
def get_files_count(self):
return len(self.files)
return len(self._files)
def get_path_part_from_hash(self, hash):
files = self.get_files()
@ -154,13 +123,13 @@ class View(Settings, FileHandler, Launcher, Icon, Path):
return file
def get_files_formatted(self):
files = self.hash_set(self.files),
dirs = self.hash_set(self.dirs),
files = self._hash_set(self._files),
dirs = self._hash_set(self._dirs),
videos = self.get_videos(),
images = self.hash_set(self.images),
desktops = self.hash_set(self.desktop),
ungrouped = self.hash_set(self.ungrouped)
hidden = self.hash_set(self.hidden)
images = self._hash_set(self._images),
desktops = self._hash_set(self._desktop),
ungrouped = self._hash_set(self._ungrouped)
hidden = self._hash_set(self._hidden)
return {
'path_head': self.get_path(),
@ -178,7 +147,7 @@ class View(Settings, FileHandler, Launcher, Icon, Path):
def get_pixbuf_icon_str_combo(self):
data = []
dir = self.get_current_directory()
for file in self.files:
for file in self._files:
icon = self.create_icon(dir, file).get_pixbuf()
data.append([icon, file])
@ -188,7 +157,7 @@ class View(Settings, FileHandler, Launcher, Icon, Path):
def get_gtk_icon_str_combo(self):
data = []
dir = self.get_current_directory()
for file in self.files:
for file in self._files:
icon = self.create_icon(dir, file)
data.append([icon, file[0]])
@ -207,23 +176,71 @@ class View(Settings, FileHandler, Launcher, Icon, Path):
size = len(parts)
return parts[size - 1]
def set_hiding_hidden(self, state):
self._hide_hidden = state
def is_hiding_hidden(self):
return self._hide_hidden
def get_dot_dots(self):
return self.hash_set(['.', '..'])
return self._hash_set(['.', '..'])
def get_files(self):
return self.hash_set(self.files)
return self._hash_set(self._files)
def get_dirs(self):
return self.hash_set(self.dirs)
return self._hash_set(self._dirs)
def get_videos(self):
return self.hash_set(self.vids)
return self._hash_set(self._vids)
def get_images(self):
return self.hash_set(self.images)
return self._hash_set(self._images)
def get_desktops(self):
return self.hash_set(self.desktop)
return self._hash_set(self._desktop)
def get_ungrouped(self):
return self.hash_set(self.ungrouped)
return self._hash_set(self._ungrouped)
def get_hidden(self):
return self._hash_set(self._hidden)
def get_id(self):
return self._id
def set_wid(self, _wid):
self._wid = _wid
def get_wid(self):
return self._wid
def set_dir_watcher(self, watcher):
self._dir_watcher = watcher
def get_dir_watcher(self):
return self._dir_watcher
def _atoi(self, text):
return int(text) if text.isdigit() else text
def _natural_keys(self, text):
return [ self._atoi(c) for c in re.split('(\d+)',text) ]
def _hash_text(self, text):
return hashlib.sha256(str.encode(text)).hexdigest()[:18]
def _hash_set(self, arry):
data = []
for arr in arry:
data.append([arr, self._hash_text(arr)])
return data
def _random_with_N_digits(self, n):
range_start = 10**(n-1)
range_end = (10**n)-1
return randint(range_start, range_end)
def _generate_id(self):
self._id = str(self._random_with_N_digits(self._id_length))

View File

@ -0,0 +1,89 @@
# Python imports
from random import randint
# Lib imports
# Application imports
from .views.view import View
class Window:
def __init__(self):
self._id_length = 10
self._id = ""
self._name = ""
self._nickname = ""
self._isHidden = False
self._views = []
self._generate_id()
self._set_name()
def create_view(self):
view = View()
self._views.append(view)
return view
def pop_view(self):
self._views.pop()
def delete_view_by_id(self, vid):
for view in self._views:
if view.get_id() == vid:
self._views.remove(view)
break
def get_view_by_id(self, vid):
for view in self._views:
if view.get_id() == vid:
return view
def get_view_by_index(self, index):
return self._views[index]
def get_views_count(self):
return len(self._views)
def get_all_views(self):
return self._views
def list_files_from_views(self):
for view in self._views:
print(view.get_files())
def get_id(self):
return self._id
def get_name(self):
return self._name
def get_nickname(self):
return self._nickname
def is_hidden(self):
return self._isHidden
def set_nickname(self, nickname):
self._nickname = f"{nickname}"
def set_is_hidden(self, state):
self._isHidden = f"{state}"
def _set_name(self):
self._name = "window_" + self.get_id()
def _random_with_N_digits(self, n):
range_start = 10**(n-1)
range_end = (10**n)-1
return randint(range_start, range_end)
def _generate_id(self):
self._id = str(self._random_with_N_digits(self._id_length))

View File

@ -1,165 +0,0 @@
# Python imports
import sys, traceback, threading, inspect, os, time
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib
# Application imports
from .mixins.ui import *
from .mixins import ShowHideMixin, KeyboardSignalsMixin
from . import Controller_Data
def threaded(fn):
def wrapper(*args, **kwargs):
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start()
return wrapper
class Controller(WidgetFileActionMixin, PaneMixin, WindowMixin, ShowHideMixin, \
KeyboardSignalsMixin, Controller_Data):
def __init__(self, args, unknownargs, _settings):
# sys.excepthook = self.custom_except_hook
self.setup_controller_data(_settings)
self.window.show()
self.generate_windows(self.state)
self.plugins.launch_plugins()
if not trace_debug:
self.gui_event_observer()
if unknownargs:
for arg in unknownargs:
if os.path.isdir(arg):
message = f"FILE|{arg}"
event_system.send_ipc_message(message)
if args.new_tab and os.path.isdir(args.new_tab):
message = f"FILE|{args.new_tab}"
event_system.send_ipc_message(message)
def tear_down(self, widget=None, eve=None):
event_system.send_ipc_message("close server")
self.window_controller.save_state()
time.sleep(event_sleep_time)
Gtk.main_quit()
@threaded
def gui_event_observer(self):
while True:
time.sleep(event_sleep_time)
event = event_system.consume_gui_event()
if event:
try:
type, target, data = event
method = getattr(self.__class__, type)
GLib.idle_add(method, (self, data,))
except Exception as e:
print(repr(e))
def custom_except_hook(self, exctype, value, _traceback):
trace = ''.join(traceback.format_tb(_traceback))
data = f"Exectype: {exctype} <--> Value: {value}\n\n{trace}\n\n\n\n"
start_itr = self.message_buffer.get_start_iter()
self.message_buffer.place_cursor(start_itr)
self.display_message(self.error, data)
def display_message(self, type, text, seconds=None):
self.message_buffer.insert_at_cursor(text)
self.message_widget.popup()
if seconds:
self.hide_message_timeout(seconds)
@threaded
def hide_message_timeout(self, seconds=3):
time.sleep(seconds)
GLib.idle_add(self.message_widget.popdown)
def save_debug_alerts(self, widget=None, eve=None):
start_itr, end_itr = self.message_buffer.get_bounds()
save_location_prompt = Gtk.FileChooserDialog("Choose Save Folder", self.window, \
action = Gtk.FileChooserAction.SAVE, \
buttons = (Gtk.STOCK_CANCEL, \
Gtk.ResponseType.CANCEL, \
Gtk.STOCK_SAVE, \
Gtk.ResponseType.OK))
text = self.message_buffer.get_text(start_itr, end_itr, False)
resp = save_location_prompt.run()
if (resp == Gtk.ResponseType.CANCEL) or (resp == Gtk.ResponseType.DELETE_EVENT):
pass
elif resp == Gtk.ResponseType.OK:
target = save_location_prompt.get_filename();
with open(target, "w") as f:
f.write(text)
save_location_prompt.destroy()
def set_arc_buffer_text(self, widget=None, eve=None):
id = widget.get_active_id()
self.arc_command_buffer.set_text(self.arc_commands[int(id)])
def clear_children(self, widget):
for child in widget.get_children():
widget.remove(child)
def get_current_state(self):
wid, tid = self.window_controller.get_active_data()
view = self.get_fm_window(wid).get_view_by_id(tid)
iconview = self.builder.get_object(f"{wid}|{tid}|iconview")
store = iconview.get_model()
return wid, tid, view, iconview, store
def do_action_from_menu_controls(self, widget, eventbutton):
action = widget.get_name()
self.ctrlDown = True
self.hide_context_menu()
self.hide_new_file_menu()
self.hide_edit_file_menu()
if action == "open":
self.open_files()
if action == "open_with":
self.show_appchooser_menu()
if action == "execute":
self.execute_files()
if action == "execute_in_terminal":
self.execute_files(in_terminal=True)
if action == "rename":
self.rename_files()
if action == "cut":
self.to_copy_files.clear()
self.cut_files()
if action == "copy":
self.to_cut_files.clear()
self.copy_files()
if action == "paste":
self.paste_files()
if action == "archive":
self.show_archiver_dialogue()
if action == "delete":
self.delete_files()
if action == "trash":
self.trash_files()
if action == "go_to_trash":
self.builder.get_object("path_entry").set_text(self.trash_files_path)
if action == "restore_from_trash":
self.restore_trash_files()
if action == "empty_trash":
self.empty_trash()
if action == "create":
self.create_files()
self.hide_new_file_menu()
self.ctrlDown = False

View File

@ -1,41 +0,0 @@
# Python imports
import importlib
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gio
# Application imports
class Plugins:
"""docstring for Plugins"""
def __init__(self, settings):
self._settings = settings
self._plugins_path = self._settings.get_plugins_path()
self._plugins_dir_watcher = None
self._socket = Gtk.Socket().new()
def launch_plugins(self):
self._set_plugins_watcher()
self.load_plugins()
def _set_plugins_watcher(self):
self._plugins_dir_watcher = Gio.File.new_for_path(self._plugins_path) \
.monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, Gio.Cancellable())
self._plugins_dir_watcher.connect("changed", self._on_plugins_changed, ())
def _on_plugins_changed(self, file_monitor, file, other_file=None, eve_type=None, data=None):
if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED,
Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN,
Gio.FileMonitorEvent.MOVED_OUT]:
self.load_plugins(file)
def load_plugins(self, file=None):
print(f"(Re)loading plugins...")
print(locals())
# importlib.reload(stl_utils)

View File

@ -1,8 +0,0 @@
"""
Gtk Bound Signal Module
"""
from .mixins import *
from .IPCServerMixin import IPCServerMixin
from .Plugins import Plugins
from .Controller_Data import Controller_Data
from .Controller import Controller

View File

@ -1,2 +0,0 @@
from .KeyboardSignalsMixin import KeyboardSignalsMixin
from .ShowHideMixin import ShowHideMixin

View File

@ -1,5 +0,0 @@
from .PaneMixin import PaneMixin
from .WidgetMixin import WidgetMixin
from .TabMixin import TabMixin
from .WindowMixin import WindowMixin
from .WidgetFileActionMixin import WidgetFileActionMixin

View File

@ -21,7 +21,7 @@ class Trash(object):
if os.path.isfile(item):
size = size + os.path.getsize(item)
elif os.path.isdir(item):
size = size + size_dir(item)
size = size + self.size_dir(item)
return size

View File

@ -1,6 +0,0 @@
"""
Utils module
"""
from .Logger import Logger
from .Settings import Settings

View File

@ -12,7 +12,7 @@ from gi.repository import Gdk as gdk
# Application imports
from . import Logger
from .logger import Logger
class Settings:

View File

@ -4,7 +4,7 @@ import builtins
# Lib imports
# Application imports
from controller import IPCServerMixin
from context.ipc_server_mixin import IPCServerMixin

View File

@ -4,15 +4,15 @@ import os, inspect, time
# Lib imports
# Application imports
from utils import Settings
from controller import Controller
from utils.settings import Settings
from context.controller import Controller
from __builtins__ import Builtins
class Main(Builtins):
''' Create Settings and Controller classes. Bind signal to Builder. Inherit from Builtins to bind global methods and classes.'''
""" Create Settings and Controller classes. Bind signal to Builder. Inherit from Builtins to bind global methods and classes. """
def __init__(self, args, unknownargs):
if not debug:

View File

@ -19,7 +19,7 @@ from __init__ import Main
if __name__ == "__main__":
''' Set process title, get arguments, and create GTK main thread. '''
""" Set process title, get arguments, and create GTK main thread. """
try:
# import web_pdb

View File

@ -0,0 +1,3 @@
"""
Gtk Bound Signal Module
"""

View File

@ -0,0 +1,172 @@
# Python imports
import os, gc, threading, time
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib
# Application imports
from .mixins.exception_hook_mixin import ExceptionHookMixin
from .mixins.ui_mixin import UIMixin
from .signals.ipc_signals_mixin import IPCSignalsMixin
from .signals.keyboard_signals_mixin import KeyboardSignalsMixin
from .controller_data import Controller_Data
def threaded(fn):
def wrapper(*args, **kwargs):
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start()
return wrapper
class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, ExceptionHookMixin, Controller_Data):
""" Controller coordinates the mixins and is somewhat the root hub of it all. """
def __init__(self, args, unknownargs, _settings):
self.setup_controller_data(_settings)
self.window.show()
self.generate_windows(self.state)
self.plugins.launch_plugins()
if debug:
self.window.set_interactive_debugging(True)
if not trace_debug:
self.gui_event_observer()
if unknownargs:
for arg in unknownargs:
if os.path.isdir(arg):
message = f"FILE|{arg}"
event_system.send_ipc_message(message)
if args.new_tab and os.path.isdir(args.new_tab):
message = f"FILE|{args.new_tab}"
event_system.send_ipc_message(message)
def tear_down(self, widget=None, eve=None):
event_system.send_ipc_message("close server")
self.window_controller.save_state()
time.sleep(event_sleep_time)
Gtk.main_quit()
@threaded
def gui_event_observer(self):
while True:
time.sleep(event_sleep_time)
event = event_system.consume_gui_event()
if event:
try:
type, target, data = event
if type:
method = getattr(self.__class__, "handle_gui_event_and_set_message")
GLib.idle_add(method, *(self, type, target, data))
else:
method = getattr(self.__class__, target)
GLib.idle_add(method, *(self, *data,))
except Exception as e:
print(repr(e))
def handle_gui_event_and_set_message(self, type, target, parameters):
method = getattr(self.__class__, f"{target}")
data = method(*(self, *parameters))
self.plugins.set_message_on_plugin(type, data)
def open_terminal(self, widget=None, eve=None):
wid, tid = self.window_controller.get_active_wid_and_tid()
view = self.get_fm_window(wid).get_view_by_id(tid)
dir = view.get_current_directory()
view.execute(f"{view.terminal_app}", dir)
def save_load_session(self, action="save_session"):
wid, tid = self.window_controller.get_active_wid_and_tid()
view = self.get_fm_window(wid).get_view_by_id(tid)
save_load_dialog = self.builder.get_object("save_load_dialog")
if action == "save_session":
self.window_controller.save_state()
return
elif action == "save_session_as":
save_load_dialog.set_action(Gtk.FileChooserAction.SAVE)
elif action == "load_session":
save_load_dialog.set_action(Gtk.FileChooserAction.OPEN)
else:
raise Exception(f"Unknown action given: {action}")
save_load_dialog.set_current_folder(view.get_current_directory())
save_load_dialog.set_current_name("session.json")
response = save_load_dialog.run()
if response == Gtk.ResponseType.OK:
if action == "save_session":
path = f"{save_load_dialog.get_current_folder()}/{save_load_dialog.get_current_name()}"
self.window_controller.save_state(path)
elif action == "load_session":
path = f"{save_load_dialog.get_file().get_path()}"
session_json = self.window_controller.load_state(path)
self.load_session(session_json)
if (response == Gtk.ResponseType.CANCEL) or (response == Gtk.ResponseType.DELETE_EVENT):
pass
save_load_dialog.hide()
def load_session(self, session_json):
if debug:
print(f"Session Data: {session_json}")
self.ctrlDown = False
self.shiftDown = False
self.altDown = False
for notebook in self.notebooks:
self.clear_children(notebook)
self.window_controller.unload_views_and_windows()
self.generate_windows(session_json)
gc.collect()
def do_action_from_menu_controls(self, widget, event_button):
action = widget.get_name()
self.hide_context_menu()
self.hide_new_file_menu()
self.hide_edit_file_menu()
if action == "open":
self.open_files()
if action == "open_with":
self.show_appchooser_menu()
if action == "execute":
self.execute_files()
if action == "execute_in_terminal":
self.execute_files(in_terminal=True)
if action == "rename":
self.rename_files()
if action == "cut":
self.to_copy_files.clear()
self.cut_files()
if action == "copy":
self.to_cut_files.clear()
self.copy_files()
if action == "paste":
self.paste_files()
if action == "archive":
self.show_archiver_dialogue()
if action == "delete":
self.delete_files()
if action == "trash":
self.trash_files()
if action == "go_to_trash":
self.path_entry.set_text(self.trash_files_path)
if action == "restore_from_trash":
self.restore_trash_files()
if action == "empty_trash":
self.empty_trash()
if action == "create":
self.show_new_file_menu()
if action in ["save_session", "save_session_as", "load_session"]:
self.save_load_session(action)

View File

@ -1,20 +1,19 @@
# Python imports
import signal
import sys, os, signal
# Lib imports
from gi.repository import GLib
# Application imports
from shellfm import WindowController
from trasher.xdgtrash import XDGTrash
from . import Plugins
from shellfm.windows.controller import WindowController
from plugins.plugins import Plugins
class Controller_Data:
def has_method(self, o, name):
return callable(getattr(o, name, None))
""" 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):
self.trashman = XDGTrash()
@ -37,13 +36,14 @@ class Controller_Data:
self.message_buffer = self.builder.get_object("message_buffer")
self.arc_command_buffer = self.builder.get_object("arc_command_buffer")
self.exists_file_rename_bttn = self.builder.get_object("exists_file_rename_bttn")
self.warning_alert = self.builder.get_object("warning_alert")
self.edit_file_menu = self.builder.get_object("edit_file_menu")
self.file_exists_dialog = self.builder.get_object("file_exists_dialog")
self.exists_file_label = self.builder.get_object("exists_file_label")
self.exists_file_field = self.builder.get_object("exists_file_field")
self.path_menu = self.builder.get_object("path_menu")
self.exists_file_rename_bttn = self.builder.get_object("exists_file_rename_bttn")
self.path_entry = self.builder.get_object("path_entry")
self.bottom_size_label = self.builder.get_object("bottom_size_label")
self.bottom_file_count_label = self.builder.get_object("bottom_file_count_label")
@ -82,6 +82,7 @@ class Controller_Data:
self.selected_files = []
self.to_copy_files = []
self.to_cut_files = []
self.soft_update_lock = {}
self.single_click_open = False
self.is_pane1_hidden = False
@ -104,6 +105,53 @@ class Controller_Data:
self.warning = "#ffa800"
self.error = "#ff0000"
sys.excepthook = self.custom_except_hook
self.window.connect("delete-event", self.tear_down)
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.tear_down)
def get_current_state(self):
'''
Returns the state info most useful for any given context and action intent.
Parameters:
a (obj): self
Returns:
wid, tid, view, iconview, store
'''
wid, tid = self.window_controller.get_active_wid_and_tid()
view = self.get_fm_window(wid).get_view_by_id(tid)
iconview = self.builder.get_object(f"{wid}|{tid}|iconview")
store = iconview.get_model()
return wid, tid, view, iconview, store
def clear_console(self):
''' Clears the terminal screen. '''
os.system('cls' if os.name == 'nt' else 'clear')
def call_method(self, _method_name, data = None):
'''
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, name):
''' Checks if a given method exists. '''
return callable(getattr(obj, name, None))
def clear_children(self, widget):
''' Clear children of a gtk widget. '''
for child in widget.get_children():
widget.remove(child)

View File

@ -1,5 +1,5 @@
# Python imports
import threading, socket, time
import threading, time
from multiprocessing.connection import Listener, Client
# Lib imports
@ -16,6 +16,7 @@ def threaded(fn):
class IPCServerMixin:
""" Create a listener so that other SolarFM instances send requests back to existing instance. """
@threaded
def create_ipc_server(self):
@ -34,7 +35,7 @@ class IPCServerMixin:
if "FILE|" in msg:
file = msg.split("FILE|")[1].strip()
if file:
event_system.push_gui_event(["create_tab_from_ipc", None, file])
event_system.push_gui_event([None, "handle_file_from_ipc", (file,)])
conn.close()
break
@ -47,7 +48,7 @@ class IPCServerMixin:
conn.close()
break
# NOTE: Not perfect but insures we don't lockup the connection for too long.
# NOTE: Not perfect but insures we don't lock up the connection for too long.
end_time = time.time()
if (end - start) > self.ipc_timeout:
conn.close()

View File

@ -0,0 +1,62 @@
# Python imports
import traceback, threading, time
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib
# Application imports
def threaded(fn):
def wrapper(*args, **kwargs):
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start()
return wrapper
class ExceptionHookMixin:
""" ExceptionHookMixin custom exception hook to reroute to a Gtk text area. """
def custom_except_hook(self, exec_type, value, _traceback):
trace = ''.join(traceback.format_tb(_traceback))
data = f"Exec Type: {exec_type} <--> Value: {value}\n\n{trace}\n\n\n\n"
start_itr = self.message_buffer.get_start_iter()
self.message_buffer.place_cursor(start_itr)
self.display_message(self.error, data)
def display_message(self, type, text, seconds=None):
self.message_buffer.insert_at_cursor(text)
self.message_widget.popup()
if seconds:
self.hide_message_timeout(seconds)
@threaded
def hide_message_timeout(self, seconds=3):
time.sleep(seconds)
GLib.idle_add(self.message_widget.popdown)
def save_debug_alerts(self, widget=None, eve=None):
start_itr, end_itr = self.message_buffer.get_bounds()
save_location_prompt = Gtk.FileChooserDialog("Choose Save Folder", self.window, \
action = Gtk.FileChooserAction.SAVE, \
buttons = (Gtk.STOCK_CANCEL, \
Gtk.ResponseType.CANCEL, \
Gtk.STOCK_SAVE, \
Gtk.ResponseType.OK))
text = self.message_buffer.get_text(start_itr, end_itr, False)
resp = save_location_prompt.run()
if (resp == Gtk.ResponseType.CANCEL) or (resp == Gtk.ResponseType.DELETE_EVENT):
pass
elif resp == Gtk.ResponseType.OK:
target = save_location_prompt.get_filename();
with open(target, "w") as f:
f.write(text)
save_location_prompt.destroy()
def set_arc_buffer_text(self, widget=None, eve=None):
sid = widget.get_active_id()
self.arc_command_buffer.set_text(self.arc_commands[int(sid)])

View File

@ -4,7 +4,7 @@
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
from gi.repository import Gtk, Gdk, Gio
from gi.repository import Gtk, Gdk
# Application imports
@ -16,7 +16,6 @@ class ShowHideMixin:
def stop_file_searching(self, widget=None, eve=None):
self.is_searching = False
def show_exists_page(self, widget=None, eve=None):
response = self.file_exists_dialog.run()
self.file_exists_dialog.hide()
@ -57,7 +56,7 @@ class ShowHideMixin:
def show_archiver_dialogue(self, widget=None, eve=None):
wid, tid = self.window_controller.get_active_data()
wid, tid = self.window_controller.get_active_wid_and_tid()
view = self.get_fm_window(wid).get_view_by_id(tid)
archiver_dialogue = self.builder.get_object("archiver_dialogue")
archiver_dialogue.set_action(Gtk.FileChooserAction.SAVE)
@ -81,12 +80,14 @@ class ShowHideMixin:
appchooser_widget = self.builder.get_object("appchooser_widget")
response = appchooser_menu.run()
if response == Gtk.ResponseType.CANCEL:
self.hide_appchooser_menu()
if response == Gtk.ResponseType.OK:
self.open_with_files(appchooser_widget)
self.hide_appchooser_menu()
if response == Gtk.ResponseType.CANCEL:
self.hide_appchooser_menu()
def hide_appchooser_menu(self, widget=None, eve=None):
self.builder.get_object("appchooser_menu").hide()
@ -95,6 +96,12 @@ class ShowHideMixin:
dialog.response(Gtk.ResponseType.OK)
def show_plugins_popup(self, widget=None, eve=None):
self.builder.get_object("plugin_list").popup()
def hide_plugins_popup(self, widget=None, eve=None):
self.builder.get_object("plugin_list").hide()
def show_context_menu(self, widget=None, eve=None):
self.builder.get_object("context_menu").run()
@ -103,12 +110,18 @@ class ShowHideMixin:
def show_new_file_menu(self, widget=None, eve=None):
self.builder.get_object("new_file_menu").run()
self.builder.get_object("context_menu_fname").set_text("")
new_file_menu = self.builder.get_object("new_file_menu")
response = new_file_menu.run()
if response == Gtk.ResponseType.APPLY:
self.create_files()
if response == Gtk.ResponseType.CANCEL:
self.hide_new_file_menu()
def hide_new_file_menu(self, widget=None, eve=None):
self.builder.get_object("new_file_menu").hide()
def show_edit_file_menu(self, widget=None, eve=None):
if widget:
widget.grab_focus()

View File

@ -61,5 +61,5 @@ class PaneMixin:
def _save_state(self, state, pane_index):
window = self.window_controller.get_window_by_index(pane_index - 1)
window.isHidden = state
window.set_is_hidden(state)
self.window_controller.save_state()

View File

@ -4,11 +4,10 @@ import os
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
from gi.repository import Gtk, Gdk
from gi.repository import Gtk
# Application imports
from . import WidgetMixin
from .widget_mixin import WidgetMixin
@ -16,24 +15,6 @@ from . import WidgetMixin
class TabMixin(WidgetMixin):
"""docstring for TabMixin"""
def create_tab_from_ipc(data):
self, path = data
wid, tid = self.window_controller.get_active_data()
notebook = self.builder.get_object(f"window_{wid}")
if notebook.is_visible():
self.create_tab(wid, path)
return
if not self.is_pane4_hidden:
self.create_tab(4, path)
elif not self.is_pane3_hidden:
self.create_tab(3, path)
elif not self.is_pane2_hidden:
self.create_tab(2, path)
elif not self.is_pane1_hidden:
self.create_tab(1, path)
def create_tab(self, wid, path=None):
notebook = self.builder.get_object(f"window_{wid}")
path_entry = self.builder.get_object(f"path_entry")
@ -48,7 +29,7 @@ class TabMixin(WidgetMixin):
# scroll, store = self.create_grid_treeview_widget(view, wid)
index = notebook.append_page(scroll, tab)
self.window_controller.set_active_data(wid, view.get_tab_id())
self.window_controller.set__wid_and_tid(wid, view.get_id())
path_entry.set_text(view.get_current_directory())
notebook.show_all()
notebook.set_current_page(index)
@ -65,7 +46,7 @@ class TabMixin(WidgetMixin):
def close_tab(self, button, eve=None):
notebook = button.get_parent().get_parent()
tid = self.get_tab_id_from_tab_box(button.get_parent())
tid = self.get_id_from_tab_box(button.get_parent())
wid = int(notebook.get_name()[-1])
scroll = self.builder.get_object(f"{wid}|{tid}")
page = notebook.page_num(scroll)
@ -83,12 +64,12 @@ class TabMixin(WidgetMixin):
window = self.get_fm_window(wid)
view = None
for i, view in enumerate(window.views):
if view.id == tid:
for i, view in enumerate(window.get_all_views()):
if view.get_id() == tid:
_view = window.get_view_by_id(tid)
watcher = _view.get_dir_watcher()
watcher.cancel()
window.views.insert(new_index, window.views.pop(i))
window.get_all_views().insert(new_index, window.get_all_views().pop(i))
view = window.get_view_by_id(tid)
self.set_file_watcher(view)
@ -97,11 +78,11 @@ class TabMixin(WidgetMixin):
def on_tab_switch_update(self, notebook, content=None, index=None):
self.selected_files.clear()
wid, tid = content.get_children()[0].get_name().split("|")
self.window_controller.set_active_data(wid, tid)
self.window_controller.set__wid_and_tid(wid, tid)
self.set_path_text(wid, tid)
self.set_window_title()
def get_tab_id_from_tab_box(self, tab_box):
def get_id_from_tab_box(self, tab_box):
tid = tab_box.get_children()[2]
return tid.get_text()
@ -132,7 +113,7 @@ class TabMixin(WidgetMixin):
def do_action_from_bar_controls(self, widget, eve=None):
action = widget.get_name()
wid, tid = self.window_controller.get_active_data()
wid, tid = self.window_controller.get_active_wid_and_tid()
notebook = self.builder.get_object(f"window_{wid}")
store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}")
view = self.get_fm_window(wid).get_view_by_id(tid)
@ -156,11 +137,11 @@ class TabMixin(WidgetMixin):
if isinstance(focused_obj, Gtk.Entry):
button_box = self.path_menu.get_children()[0].get_children()[0].get_children()[0]
query = widget.get_text().replace(dir, "")
files = view.files + view.hidden
files = view.get_files() + view.get_hidden()
self.clear_children(button_box)
show_path_menu = False
for file in files:
for file, hash in files:
if os.path.isdir(f"{dir}{file}"):
if query.lower() in file.lower():
button = Gtk.Button(label=file)
@ -201,7 +182,7 @@ class TabMixin(WidgetMixin):
self.path_menu.popdown()
def keyboard_close_tab(self):
wid, tid = self.window_controller.get_active_data()
wid, tid = self.window_controller.get_active_wid_and_tid()
notebook = self.builder.get_object(f"window_{wid}")
scroll = self.builder.get_object(f"{wid}|{tid}")
page = notebook.page_num(scroll)
@ -216,8 +197,8 @@ class TabMixin(WidgetMixin):
# File control events
def show_hide_hidden_files(self):
wid, tid = self.window_controller.get_active_data()
wid, tid = self.window_controller.get_active_wid_and_tid()
view = self.get_fm_window(wid).get_view_by_id(tid)
view.hide_hidden = not view.hide_hidden
view.set_hiding_hidden(not view.is_hiding_hidden())
view.load_directory()
self.builder.get_object("refresh_view").released()

View File

@ -1,17 +1,23 @@
# Python imports
import os
import os, time, threading
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GObject, Gio
from gi.repository import Gtk, GObject, GLib, Gio
# Application imports
def threaded(fn):
def wrapper(*args, **kwargs):
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start()
return wrapper
class WidgetFileActionMixin:
"""docstring for WidgetFileActionMixin"""
def sizeof_fmt(self, num, suffix="B"):
for unit in ["", "K", "M", "G", "T", "Pi", "Ei", "Zi"]:
if abs(num) < 1024.0:
@ -52,29 +58,60 @@ class WidgetFileActionMixin:
.monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, Gio.Cancellable())
wid = view.get_wid()
tid = view.get_tab_id()
tid = view.get_id()
dir_watcher.connect("changed", self.dir_watch_updates, (f"{wid}|{tid}",))
view.set_dir_watcher(dir_watcher)
# NOTE: Too lazy to impliment a proper update handler and so just regen store and update view.
# Use a lock system to prevent too many update calls for certain instances but user can manually refresh if they have urgency
def dir_watch_updates(self, file_monitor, file, other_file=None, eve_type=None, data=None):
if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED,
Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN,
Gio.FileMonitorEvent.MOVED_OUT]:
Gio.FileMonitorEvent.MOVED_OUT]:
if debug:
print(eve_type)
wid, tid = data[0].split("|")
notebook = self.builder.get_object(f"window_{wid}")
view = self.get_fm_window(wid).get_view_by_id(tid)
iconview = self.builder.get_object(f"{wid}|{tid}|iconview")
store = iconview.get_model()
_store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}")
if eve_type in [Gio.FileMonitorEvent.MOVED_IN, Gio.FileMonitorEvent.MOVED_OUT]:
self.update_on_end_soft_lock(data[0])
elif data[0] in self.soft_update_lock.keys():
self.soft_update_lock[data[0]]["last_update_time"] = time.time()
else:
self.soft_lock_countdown(data[0])
view.load_directory()
self.load_store(view, store)
tab_label.set_label(view.get_end_of_path())
self.set_bottom_labels(view)
@threaded
def soft_lock_countdown(self, tab):
self.soft_update_lock[tab] = { "last_update_time": time.time()}
lock = True
while lock:
time.sleep(0.6)
last_update_time = self.soft_update_lock[tab]["last_update_time"]
current_time = time.time()
if (current_time - last_update_time) > 0.6:
lock = False
self.soft_update_lock.pop(tab, None)
GLib.idle_add(self.update_on_end_soft_lock, *(tab,))
def update_on_end_soft_lock(self, tab):
wid, tid = tab.split("|")
notebook = self.builder.get_object(f"window_{wid}")
view = self.get_fm_window(wid).get_view_by_id(tid)
iconview = self.builder.get_object(f"{wid}|{tid}|iconview")
store = iconview.get_model()
_store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}")
view.load_directory()
self.load_store(view, store)
tab_label.set_label(view.get_end_of_path())
_wid, _tid, _view, _iconview, _store = self.get_current_state()
if [wid, tid] in [_wid, _tid]:
self.set_bottom_labels(view)
def popup_search_files(self, wid, keyname):
@ -87,8 +124,8 @@ class WidgetFileActionMixin:
def do_file_search(self, widget, eve=None):
query = widget.get_text()
self.search_iconview.unselect_all()
for i, file in enumerate(self.search_view.files):
if query and query in file.lower():
for i, file in enumerate(self.search_view.get_files()):
if query and query in file[0].lower():
path = Gtk.TreePath().new_from_indices([i])
self.search_iconview.select_path(path)
@ -174,7 +211,7 @@ class WidgetFileActionMixin:
self.to_copy_files = uris
def paste_files(self):
wid, tid = self.window_controller.get_active_data()
wid, tid = self.window_controller.get_active_wid_and_tid()
view = self.get_fm_window(wid).get_view_by_id(tid)
target = f"{view.get_current_directory()}"
@ -227,7 +264,7 @@ class WidgetFileActionMixin:
file_name = fname_field.get_text().strip()
type = self.builder.get_object("context_menu_type_toggle").get_state()
wid, tid = self.window_controller.get_active_data()
wid, tid = self.window_controller.get_active_wid_and_tid()
view = self.get_fm_window(wid).get_view_by_id(tid)
target = f"{view.get_current_directory()}"
@ -239,12 +276,12 @@ class WidgetFileActionMixin:
else: # Create Folder
self.handle_files([path], "create_dir")
fname_field.set_text("")
self.hide_new_file_menu()
def move_files(self, files, target):
self.handle_files(files, "move", target)
# NOTE: Gtk recommends using fail flow than pre check existence which is more
# NOTE: Gtk recommends using fail flow than pre check which is more
# race condition proof. They're right; but, they can't even delete
# directories properly. So... f**k them. I'll do it my way.
def handle_files(self, paths, action, _target_path=None):
@ -273,8 +310,7 @@ class WidgetFileActionMixin:
if _file.query_exists():
if not overwrite_all and not rename_auto_all:
self.exists_file_label.set_label(_file.get_basename())
self.exists_file_field.set_text(_file.get_basename())
self.setup_exists_data(file, _file)
response = self.show_exists_page()
if response == "overwrite_all":
@ -295,7 +331,7 @@ class WidgetFileActionMixin:
type = _file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE)
if type == Gio.FileType.DIRECTORY:
wid, tid = self.window_controller.get_active_data()
wid, tid = self.window_controller.get_active_wid_and_tid()
view = self.get_fm_window(wid).get_view_by_id(tid)
view.delete_file( _file.get_path() )
else:
@ -322,7 +358,7 @@ class WidgetFileActionMixin:
type = file.query_file_type(flags=Gio.FileQueryInfoFlags.NONE)
if type == Gio.FileType.DIRECTORY:
wid, tid = self.window_controller.get_active_data()
wid, tid = self.window_controller.get_active_wid_and_tid()
view = self.get_fm_window(wid).get_view_by_id(tid)
fPath = file.get_path()
tPath = target.get_path()
@ -344,6 +380,45 @@ class WidgetFileActionMixin:
self.exists_file_rename_bttn.set_sensitive(False)
def setup_exists_data(self, from_file, to_file):
from_info = from_file.query_info("standard::*,time::modified", 0, cancellable=None)
to_info = to_file.query_info("standard::*,time::modified", 0, cancellable=None)
exists_file_diff_from = self.builder.get_object("exists_file_diff_from")
exists_file_diff_to = self.builder.get_object("exists_file_diff_to")
exists_file_from = self.builder.get_object("exists_file_from")
exists_file_to = self.builder.get_object("exists_file_to")
from_date = from_info.get_modification_date_time()
to_date = to_info.get_modification_date_time()
from_size = from_info.get_size()
to_size = to_info.get_size()
exists_file_from.set_label(from_file.get_parent().get_path())
exists_file_to.set_label(to_file.get_parent().get_path())
self.exists_file_label.set_label(to_file.get_basename())
self.exists_file_field.set_text(to_file.get_basename())
# Returns: -1, 0 or 1 if dt1 is less than, equal to or greater than dt2.
age = GLib.DateTime.compare(from_date, to_date)
age_text = "( same time )"
if age == -1:
age_text = "older"
if age == 1:
age_text = "newer"
size_text = "( same size )"
if from_size < to_size:
size_text = "smaller"
if from_size > to_size:
size_text = "larger"
from_label_text = f"{age_text} & {size_text}"
if age_text != "( same time )" or size_text != "( same size )":
from_label_text = f"{from_date.format('%F %R')} {self.sizeof_fmt(from_size)} ( {from_size} bytes ) ( {age_text} & {size_text} )"
to_label_text = f"{to_date.format('%F %R')} {self.sizeof_fmt(to_size)} ( {to_size} bytes )"
exists_file_diff_from.set_text(from_label_text)
exists_file_diff_to.set_text(to_label_text)
def rename_proc(self, gio_file):
full_path = gio_file.get_path()

View File

@ -20,6 +20,8 @@ def threaded(fn):
class WidgetMixin:
"""docstring for WidgetMixin"""
def load_store(self, view, store, save_state=False):
store.clear()
dir = view.get_current_directory()
@ -100,7 +102,7 @@ class WidgetMixin:
label.set_label(f"{view.get_end_of_path()}")
label.set_width_chars(len(view.get_end_of_path()))
label.set_xalign(0.0)
tid.set_label(f"{view.id}")
tid.set_label(f"{view.get_id()}")
close.add(icon)
tab.add(label)
@ -133,8 +135,6 @@ class WidgetMixin:
grid.connect("button_release_event", self.grid_icon_single_click)
grid.connect("item-activated", self.grid_icon_double_click)
# grid.connect("toggle-cursor-item", self.grid_cursor_toggled)
# grid.connect("notify", self.grid_cursor_toggled)
grid.connect("selection-changed", self.grid_set_selected_items)
grid.connect("drag-data-get", self.grid_on_drag_set)
grid.connect("drag-data-received", self.grid_on_drag_data_received)
@ -150,10 +150,10 @@ class WidgetMixin:
grid.show_all()
scroll.add(grid)
grid.set_name(f"{wid}|{view.id}")
scroll.set_name(f"{wid}|{view.id}")
self.builder.expose_object(f"{wid}|{view.id}|iconview", grid)
self.builder.expose_object(f"{wid}|{view.id}", scroll)
grid.set_name(f"{wid}|{view.get_id()}")
scroll.set_name(f"{wid}|{view.get_id()}")
self.builder.expose_object(f"{wid}|{view.get_id()}|iconview", grid)
self.builder.expose_object(f"{wid}|{view.get_id()}", scroll)
return scroll, store
def create_grid_treeview_widget(self, view, wid):
@ -199,10 +199,10 @@ class WidgetMixin:
grid.show_all()
scroll.add(grid)
grid.set_name(f"{wid}|{view.id}")
scroll.set_name(f"{wid}|{view.id}")
grid.set_name(f"{wid}|{view.get_id()}")
scroll.set_name(f"{wid}|{view.get_id()}")
grid.columns_autosize()
self.builder.expose_object(f"{wid}|{view.id}", scroll)
self.builder.expose_object(f"{wid}|{view.get_id()}", scroll)
return scroll, store

View File

@ -9,16 +9,18 @@ gi.require_version('Gdk', '3.0')
from gi.repository import Gdk, Gio
# Application imports
from . import TabMixin, WidgetMixin
from .tab_mixin import TabMixin
from .widget_mixin import WidgetMixin
class WindowMixin(TabMixin):
"""docstring for WindowMixin"""
def generate_windows(self, data = None):
if data:
for j, value in enumerate(data):
def generate_windows(self, session_json = None):
if session_json:
for j, value in enumerate(session_json):
i = j + 1
isHidden = True if value[0]["window"]["isHidden"] == "True" else False
object = self.builder.get_object(f"tggl_notebook_{i}")
@ -79,8 +81,8 @@ class WindowMixin(TabMixin):
def set_bottom_labels(self, view):
_wid, _tid, _view, iconview, store = self.get_current_state()
selected_files = iconview.get_selected_items()
_wid, _tid, _view, icon_view, store = self.get_current_state()
selected_files = icon_view.get_selected_items()
current_directory = view.get_current_directory()
path_file = Gio.File.new_for_path(current_directory)
mount_file = path_file.query_filesystem_info(attributes="filesystem::*", cancellable=None)
@ -113,7 +115,7 @@ class WindowMixin(TabMixin):
formatted_size = self.sizeof_fmt(combined_size)
if view.hide_hidden:
if view.get_hidden():
self.bottom_path_label.set_label(f" {len(uris)} / {view.get_files_count()} ({formatted_size})")
else:
self.bottom_path_label.set_label(f" {len(uris)} / {view.get_not_hidden_count()} ({formatted_size})")
@ -121,7 +123,7 @@ class WindowMixin(TabMixin):
return
# If nothing selected
if view.hide_hidden:
if view.get_hidden():
if view.get_hidden_count() > 0:
self.bottom_file_count_label.set_label(f"{view.get_not_hidden_count()} visible ({view.get_hidden_count()} hidden)")
else:
@ -132,7 +134,7 @@ class WindowMixin(TabMixin):
def set_window_title(self):
wid, tid = self.window_controller.get_active_data()
wid, tid = self.window_controller.get_active_wid_and_tid()
notebook = self.builder.get_object(f"window_{wid}")
view = self.get_fm_window(wid).get_view_by_id(tid)
dir = view.get_current_directory()
@ -146,7 +148,7 @@ class WindowMixin(TabMixin):
ctx.remove_class("notebook-unselected-focus")
ctx.add_class("notebook-selected-focus")
self.window.set_title("SolarFM ~ " + dir)
self.window.set_title(f"SolarFM ~ {dir}")
self.set_bottom_labels(view)
def set_path_text(self, wid, tid):
@ -164,7 +166,7 @@ class WindowMixin(TabMixin):
try:
self.path_menu.popdown()
wid, tid = iconview.get_name().split("|")
self.window_controller.set_active_data(wid, tid)
self.window_controller.set__wid_and_tid(wid, tid)
self.set_path_text(wid, tid)
self.set_window_title()
@ -182,9 +184,11 @@ class WindowMixin(TabMixin):
def grid_icon_double_click(self, iconview, item, data=None):
try:
if self.ctrlDown and self.shiftDown:
self.unset_keys_and_data()
self.execute_files(in_terminal=True)
return
elif self.ctrlDown:
self.unset_keys_and_data()
self.execute_files()
return
@ -221,7 +225,7 @@ class WindowMixin(TabMixin):
data.set_text(uris_text, -1)
def grid_on_drag_motion(self, iconview, drag_context, x, y, data):
current = '|'.join(self.window_controller.get_active_data())
current = '|'.join(self.window_controller.get_active_wid_and_tid())
target = iconview.get_name()
wid, tid = target.split("|")
store = iconview.get_model()
@ -232,12 +236,12 @@ class WindowMixin(TabMixin):
self.override_drop_dest = uri if isdir(uri) else None
if target not in current:
self.window_controller.set_active_data(wid, tid)
self.window_controller.set__wid_and_tid(wid, tid)
def grid_on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
if info == 80:
wid, tid = self.window_controller.get_active_data()
wid, tid = self.window_controller.get_active_wid_and_tid()
notebook = self.builder.get_object(f"window_{wid}")
store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}")
view = self.get_fm_window(wid).get_view_by_id(tid)

View File

@ -0,0 +1,14 @@
# Python imports
# Gtk imports
# Application imports
from .show_hide_mixin import ShowHideMixin
from .ui.widget_file_action_mixin import WidgetFileActionMixin
from .ui.pane_mixin import PaneMixin
from .ui.window_mixin import WindowMixin
from .show_hide_mixin import ShowHideMixin
class UIMixin(WidgetFileActionMixin, PaneMixin, WindowMixin, ShowHideMixin):
pass

View File

@ -0,0 +1,29 @@
# Python imports
# Lib imports
# Application imports
class IPCSignalsMixin:
""" IPCSignalsMixin handle messages from another starting solarfm process. """
def print_to_console(self, message=None):
print(self)
print(message)
def handle_file_from_ipc(self, path):
wid, tid = self.window_controller.get_active_wid_and_tid()
notebook = self.builder.get_object(f"window_{wid}")
if notebook.is_visible():
self.create_tab(wid, path)
return
if not self.is_pane4_hidden:
self.create_tab(4, path)
elif not self.is_pane3_hidden:
self.create_tab(3, path)
elif not self.is_pane2_hidden:
self.create_tab(2, path)
elif not self.is_pane1_hidden:
self.create_tab(1, path)

View File

@ -14,6 +14,8 @@ valid_keyvalue_pat = re.compile(r"[a-z0-9A-Z-_\[\]\(\)\| ]")
class KeyboardSignalsMixin:
""" KeyboardSignalsMixin keyboard hooks controller. """
def unset_keys_and_data(self, widget=None, eve=None):
self.ctrlDown = False
self.shiftDown = False
@ -47,6 +49,7 @@ class KeyboardSignalsMixin:
if self.ctrlDown and self.shiftDown and keyname == "t":
self.unset_keys_and_data()
self.trash_files()
@ -57,6 +60,7 @@ class KeyboardSignalsMixin:
if isinstance(focused_obj, Gtk.IconView):
self.is_searching = True
wid, tid, self.search_view, self.search_iconview, store = self.get_current_state()
self.unset_keys_and_data()
self.popup_search_files(wid, keyname)
return
@ -79,26 +83,30 @@ class KeyboardSignalsMixin:
if (self.ctrlDown and keyname == "up") or (self.ctrlDown and keyname == "u"):
self.builder.get_object("go_up").released()
if self.ctrlDown and keyname == "l":
self.unset_keys_and_data()
self.builder.get_object("path_entry").grab_focus()
if self.ctrlDown and keyname == "t":
self.builder.get_object("create_tab").released()
if self.ctrlDown and keyname == "o":
self.unset_keys_and_data()
self.open_files()
if self.ctrlDown and keyname == "w":
self.keyboard_close_tab()
if self.ctrlDown and keyname == "h":
self.show_hide_hidden_files()
if (self.ctrlDown and keyname == "e"):
self.edit_files()
self.unset_keys_and_data()
self.rename_files()
if self.ctrlDown and keyname == "c":
self.to_cut_files.clear()
self.copy_files()
self.to_cut_files.clear()
if self.ctrlDown and keyname == "x":
self.to_copy_files.clear()
self.cut_files()
if self.ctrlDown and keyname == "v":
self.paste_files()
if self.ctrlDown and keyname == "n":
self.unset_keys_and_data()
self.show_new_file_menu()
@ -110,11 +118,11 @@ class KeyboardSignalsMixin:
else:
top_main_menubar.show()
if keyname == "delete":
self.unset_keys_and_data()
self.delete_files()
if keyname == "f2":
self.unset_keys_and_data()
self.rename_files()
if keyname == "f4":
wid, tid = self.window_controller.get_active_data()
view = self.get_fm_window(wid).get_view_by_id(tid)
dir = view.get_current_directory()
view.execute(f"{view.terminal_app}", dir)
self.unset_keys_and_data()
self.open_terminal()

View File

@ -1,7 +0,0 @@
"""
Gtk Bound Signal Module
"""
from .mixins import *
from .IPCServerMixin import IPCServerMixin
from .Controller_Data import Controller_Data
from .Controller import Controller

View File

@ -1,11 +0,0 @@
# Python imports
# Gtk imports
# Application imports
from . import ShowHideMixin
from .ui import *
class UIMixin(WidgetFileActionMixin, PaneMixin, WindowMixin, ShowHideMixin):
pass

View File

@ -1,3 +0,0 @@
from .ShowHideMixin import ShowHideMixin
from .ExceptionHookMixin import ExceptionHookMixin
from .UIMixin import UIMixin

View File

@ -1,5 +0,0 @@
from .PaneMixin import PaneMixin
from .WidgetMixin import WidgetMixin
from .TabMixin import TabMixin
from .WindowMixin import WindowMixin
from .WidgetFileActionMixin import WidgetFileActionMixin

View File

@ -1,2 +0,0 @@
from .KeyboardSignalsMixin import KeyboardSignalsMixin
from .IPCSignalsMixin import IPCSignalsMixin

View File

@ -1,4 +1,3 @@
"""
Gtk Bound Plugins Module
"""
from .Plugins import Plugins

View File

@ -0,0 +1,99 @@
# Python imports
import os, sys, importlib, traceback
from os.path import join, isdir
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gio
# Application imports
class Plugin:
name = None
module = None
gtk_socket_id = None
gtk_socket = None
reference = None
class Plugins:
"""Plugins controller"""
def __init__(self, settings):
self._settings = settings
self._plugin_list_widget = self._settings.get_builder().get_object("plugin_list")
self._plugin_list_socket = self._settings.get_builder().get_object("plugin_socket")
self._plugins_path = self._settings.get_plugins_path()
self._plugins_dir_watcher = None
self._plugin_collection = []
def launch_plugins(self):
self._set_plugins_watcher()
self.load_plugins()
def _set_plugins_watcher(self):
self._plugins_dir_watcher = Gio.File.new_for_path(self._plugins_path) \
.monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, Gio.Cancellable())
self._plugins_dir_watcher.connect("changed", self._on_plugins_changed, ())
def _on_plugins_changed(self, file_monitor, file, other_file=None, eve_type=None, data=None):
if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED,
Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN,
Gio.FileMonitorEvent.MOVED_OUT]:
self.reload_plugins(file)
# @threaded
def load_plugins(self, file=None):
print(f"Loading plugins...")
parent_path = os.getcwd()
for file in os.listdir(self._plugins_path):
try:
path = join(self._plugins_path, file)
if isdir(path):
os.chdir(path)
gtk_socket = Gtk.Socket().new()
self._plugin_list_socket.add(gtk_socket)
# NOTE: Must get ID after adding socket to window. Else issues....
gtk_socket_id = gtk_socket.get_id()
sys.path.insert(0, path)
spec = importlib.util.spec_from_file_location(file, join(path, "__main__.py"))
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
ref = module.Main(gtk_socket_id, event_system)
plugin = Plugin()
plugin.name = ref.get_plugin_name()
plugin.module = path
plugin.gtk_socket_id = gtk_socket_id
plugin.gtk_socket = gtk_socket
plugin.reference = ref
self._plugin_collection.append(plugin)
gtk_socket.show_all()
except Exception as e:
print("Malformed plugin! Not loading!")
traceback.print_exc()
os.chdir(parent_path)
def reload_plugins(self, file=None):
print(f"Reloading plugins...")
# if self._plugin_collection:
# to_unload = []
# for dir in self._plugin_collection:
# if not os.path.isdir(os.path.join(self._plugins_path, dir)):
# to_unload.append(dir)
def set_message_on_plugin(self, type, data):
print("Trying to send message to plugin...")
for plugin in self._plugin_collection:
if type in plugin.name:
print('Found plugin; posting message...')
plugin.reference.set_message(data)

View File

@ -1,6 +0,0 @@
"""
Utils module
"""
from .Logger import Logger
from .Settings import Settings

View File

@ -12,7 +12,7 @@ from gi.repository import Gdk as gdk
# Application imports
from . import Logger
from .logger import Logger
class Settings:
@ -25,10 +25,10 @@ class Settings:
self.PLUGINS_PATH = f"{self.CONFIG_PATH}/plugins"
self.USR_SOLARFM = f"/usr/share/{app_name.lower()}"
self.cssFile = f"{self.CONFIG_PATH}/stylesheet.css"
self.windows_glade = f"{self.CONFIG_PATH}/Main_Window.glade"
self.CSS_FILE = f"{self.CONFIG_PATH}/stylesheet.css"
self.WINDOWS_GLADE = f"{self.CONFIG_PATH}/Main_Window.glade"
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"
self.main_window = None
if not os.path.exists(self.CONFIG_PATH):
@ -36,17 +36,17 @@ class Settings:
if not os.path.exists(self.PLUGINS_PATH):
os.mkdir(self.PLUGINS_PATH)
if not os.path.exists(self.windows_glade):
self.windows_glade = f"{self.USR_SOLARFM}/Main_Window.glade"
if not os.path.exists(self.cssFile):
self.cssFile = f"{self.USR_SOLARFM}/stylesheet.css"
if not os.path.exists(self.window_icon):
self.window_icon = f"{self.USR_SOLARFM}/icons/{app_name.lower()}.png"
if not os.path.exists(self.WINDOWS_GLADE):
self.WINDOWS_GLADE = f"{self.USR_SOLARFM}/Main_Window.glade"
if not os.path.exists(self.CSS_FILE):
self.CSS_FILE = f"{self.USR_SOLARFM}/stylesheet.css"
if not os.path.exists(self.WINDOW_ICON):
self.WINDOW_ICON = f"{self.USR_SOLARFM}/icons/{app_name.lower()}.png"
if not os.path.exists(self.DEFAULT_ICONS):
self.DEFAULT_ICONS = f"{self.USR_SOLARFM}/icons"
self.logger = Logger(self.CONFIG_PATH).get_logger()
self.builder.add_from_file(self.windows_glade)
self.builder.add_from_file(self.WINDOWS_GLADE)
@ -56,7 +56,7 @@ class Settings:
self._set_window_data()
def _set_window_data(self):
self.main_window.set_icon_from_file(self.window_icon)
self.main_window.set_icon_from_file(self.WINDOW_ICON)
screen = self.main_window.get_screen()
visual = screen.get_rgba_visual()
@ -67,7 +67,7 @@ class Settings:
# bind css file
cssProvider = gtk.CssProvider()
cssProvider.load_from_path(self.cssFile)
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)

View File

@ -1784,6 +1784,21 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="path_entry">
<property name="name">path_entry</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="hexpand">True</property>
<property name="placeholder-text" translatable="yes">Path...</property>
<signal name="changed" handler="do_action_from_bar_controls" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="refresh_view">
<property name="label">gtk-refresh</property>
@ -1798,7 +1813,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
<property name="position">2</property>
</packing>
</child>
<child>
@ -1815,21 +1830,6 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="path_entry">
<property name="name">path_entry</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="hexpand">True</property>
<property name="placeholder-text" translatable="yes">Path...</property>
<signal name="changed" handler="do_action_from_bar_controls" swapped="no"/>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
@ -1907,7 +1907,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
</child>
</object>
<packing>
<property name="resize">True</property>
<property name="resize">False</property>
<property name="shrink">True</property>
</packing>
</child>
@ -1945,7 +1945,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
</child>
</object>
<packing>
<property name="resize">True</property>
<property name="resize">False</property>
<property name="shrink">True</property>
</packing>
</child>
@ -1997,7 +1997,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
</child>
</object>
<packing>
<property name="resize">True</property>
<property name="resize">False</property>
<property name="shrink">True</property>
</packing>
</child>
@ -2034,7 +2034,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
</child>
</object>
<packing>
<property name="resize">True</property>
<property name="resize">False</property>
<property name="shrink">True</property>
</packing>
</child>

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Some files were not shown because too many files have changed in this diff Show More