Compare commits

23 Commits

Author SHA1 Message Date
7737e3ad6d Merge pull request 'develop' (#2) from develop into master
Reviewed-on: #2
2022-01-31 02:34:18 +00:00
628740fd31 Added glade updates for last commit 2022-01-30 20:31:33 -06:00
ed2a27ed9a Added example, further plugin work 2022-01-30 20:29:57 -06:00
3bedd83793 Refactoring and plugin work 2022-01-30 18:09:00 -06:00
ad70e8c819 removed build zip 2022-01-29 22:13:51 -06:00
ecfb586f53 added diff compare on duplicate found 2022-01-29 22:11:03 -06:00
b0991cb776 Added glade update for last commit 2022-01-29 19:35:20 -06:00
3d719ad6f6 fixed new file ui 2022-01-29 19:32:56 -06:00
3c914e64dd Merge pull request 'develop' (#1) from develop into master
Reviewed-on: #1
2022-01-30 00:22:36 +00:00
63c41d5e2a deb update, improved dnd 2022-01-29 18:18:21 -06:00
2a0fe9eb15 Fix dnd same zone issues 2022-01-29 16:47:42 -06:00
a380c01573 Path handling and settings changes 2022-01-29 15:14:50 -06:00
a1c27792ee Added back proper gif thumbnailing 2022-01-27 21:56:16 -06:00
353ee2a966 Restructuring Plugins pipe. Changed settings stuff 2022-01-26 22:29:22 -06:00
8a0057f78e Added plugin pipeing 2022-01-26 19:47:59 -06:00
f2314500b7 reverts and refactors 2022-01-24 10:24:55 -06:00
2c258d470b Resolving root path issues 2022-01-20 21:21:04 -06:00
59d67874ad New trace debug flag for when using trace module 2022-01-19 23:49:19 -06:00
bee66ee001 Updated signals on glade file 2022-01-19 20:48:43 -06:00
5bf6d04fdd file rename 2022-01-19 20:14:44 -06:00
216cc9d34c Thumbnail speed improvements 2022-01-19 16:10:43 -06:00
5a9fa8253b Thumbnail generation changes 2022-01-19 12:09:01 -06:00
9b578859e0 Fixed broken execute flow 2021-12-31 19:10:29 -06:00
63 changed files with 872 additions and 397 deletions

View File

@@ -14,7 +14,6 @@ sudo apt-get install python3.8 wget python3-setproctitle python3-gi ffmpegthumbn
# TODO # TODO
<ul> <ul>
<li>Add simpleish plugin system to run bash/python scripts.</li> <li>Add simpleish plugin system to run bash/python scripts.</li>
<li>Add DnD context awareness for over folder drop.</li>
</ul> </ul>
# Images # Images

BIN
bin/solarfm-0-0-1-x64.deb Normal file

Binary file not shown.

2
plugins/README.txt Normal file
View File

@@ -0,0 +1,2 @@
### 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.

View File

@@ -0,0 +1,37 @@
# Python imports
import sys, traceback, threading, inspect, os, time
# Gtk imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
def threaded(fn):
def wrapper(*args, **kwargs):
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start()
return wrapper
class Main:
def __init__(self, socket_id, event_system):
self._socket_id = socket_id
self._event_system = event_system
self._gtk_plug = Gtk.Plug.new(self._socket_id)
self.start_loop()
@threaded
def start_loop(self):
i = 0
cycles = 5
alive = True
while alive:
if i == cycles:
alive = False
self._event_system.push_gui_event(["some_type", "display_message", ("warning", str(i), None)])
i += 1
time.sleep(1)

0
src/debs/clear_pycache_dirs.sh Executable file → Normal file
View File

0
src/debs/solarfm-0-0-1-x64/bin/solarfm Executable file → Normal file
View File

View File

@@ -4,12 +4,12 @@ import builtins
# Lib imports # Lib imports
# Application imports # Application imports
from signal_classes.DBusControllerMixin import DBusControllerMixin from signal_classes import IPCServerMixin
class Builtins(DBusControllerMixin): class Builtins(IPCServerMixin):
"""Docstring for __builtins__ extender""" """Docstring for __builtins__ extender"""
def __init__(self): def __init__(self):
@@ -18,6 +18,11 @@ class Builtins(DBusControllerMixin):
self._gui_events = [] self._gui_events = []
self._fm_events = [] self._fm_events = []
self.is_ipc_alive = False self.is_ipc_alive = False
self.ipc_authkey = b'solarfm-ipc'
self.ipc_address = '127.0.0.1'
self.ipc_port = 4848
self.ipc_timeout = 15.0
# Makeshift fake "events" type system FIFO # Makeshift fake "events" type system FIFO
def _pop_gui_event(self): def _pop_gui_event(self):
@@ -61,6 +66,8 @@ class Builtins(DBusControllerMixin):
# 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 = "SolarFM"
builtins.event_system = Builtins() builtins.event_system = Builtins()
builtins.event_sleep_time = 0.5 builtins.event_sleep_time = 0.2
builtins.debug = False builtins.debug = False
builtins.trace_debug = False

View File

@@ -13,24 +13,27 @@ from __builtins__ import Builtins
class Main(Builtins): class Main(Builtins):
def __init__(self, args, unknownargs): def __init__(self, args, unknownargs):
event_system.create_ipc_server() if not debug:
time.sleep(0.5) event_system.create_ipc_server()
if not event_system.is_ipc_alive:
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): time.sleep(0.2)
message = f"FILE|{args.new_tab}" if not trace_debug:
event_system.send_ipc_message(message) if not event_system.is_ipc_alive:
if unknownargs:
for arg in unknownargs:
if os.path.isdir(arg):
message = f"FILE|{arg}"
event_system.send_ipc_message(message)
raise Exception("IPC Server Exists: Will send path(s) to it and close...") if args.new_tab and os.path.isdir(args.new_tab):
message = f"FILE|{args.new_tab}"
event_system.send_ipc_message(message)
raise Exception("IPC Server Exists: Will send path(s) to it and close...")
settings = Settings() settings = Settings()
settings.createWindow() settings.create_window()
controller = Controller(args, unknownargs, settings) controller = Controller(args, unknownargs, settings)
if not controller: if not controller:

View File

@@ -23,7 +23,7 @@ if __name__ == "__main__":
# import web_pdb # import web_pdb
# web_pdb.set_trace() # web_pdb.set_trace()
setproctitle('solarfm') setproctitle('SolarFM')
faulthandler.enable() # For better debug info faulthandler.enable() # For better debug info
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
# Add long and short arguments # Add long and short arguments

View File

@@ -24,7 +24,9 @@ class WindowController:
self.active_window_id = "" self.active_window_id = ""
self.active_tab_id = "" self.active_tab_id = ""
self.windows = [] self.windows = []
self.fm_event_observer()
if not trace_debug:
self.fm_event_observer()
@threaded @threaded
def fm_event_observer(self): def fm_event_observer(self):

View File

@@ -11,7 +11,7 @@ class Path:
return os.path.expanduser("~") + self.subpath return os.path.expanduser("~") + self.subpath
def get_path(self): def get_path(self):
return "/" + "/".join(self.path) return f"/{'/'.join(self.path)}" if self.path else f"/{''.join(self.path)}"
def get_path_list(self): def get_path_list(self):
return self.path return self.path
@@ -21,7 +21,7 @@ class Path:
self.load_directory() self.load_directory()
def pop_from_path(self): def pop_from_path(self):
if len(self.path) > 1: try:
self.path.pop() self.path.pop()
if not self.go_past_home: if not self.go_past_home:
@@ -29,6 +29,8 @@ class Path:
self.set_to_home() self.set_to_home()
self.load_directory() self.load_directory()
except Exception as e:
pass
def set_path(self, path): def set_path(self, path):
if path == self.get_path(): if path == self.get_path():

View File

@@ -199,7 +199,7 @@ class View(Settings, FileHandler, Launcher, Icon, Path):
def get_current_sub_path(self): def get_current_sub_path(self):
path = self.get_path() path = self.get_path()
home = self.get_home() + "/" home = f"{self.get_home()}/"
return path.replace(home, "") return path.replace(home, "")
def get_end_of_path(self): def get_end_of_path(self):

View File

@@ -17,7 +17,7 @@ def threaded(fn):
class Icon(DesktopIconMixin, VideoIconMixin): class Icon(DesktopIconMixin, VideoIconMixin):
def create_icon(self, dir, file): def create_icon(self, dir, file):
full_path = dir + "/" + file full_path = f"{dir}/{file}"
return self.get_icon_image(dir, file, full_path) return self.get_icon_image(dir, file, full_path)
def get_icon_image(self, dir, file, full_path): def get_icon_image(self, dir, file, full_path):
@@ -36,29 +36,32 @@ class Icon(DesktopIconMixin, VideoIconMixin):
return None return None
def create_thumbnail(self, dir, file): def create_thumbnail(self, dir, file):
full_path = dir + "/" + file full_path = f"{dir}/{file}"
try: try:
file_hash = hashlib.sha256(str.encode(full_path)).hexdigest() file_hash = hashlib.sha256(str.encode(full_path)).hexdigest()
hash_img_pth = self.ABS_THUMBS_PTH + "/" + file_hash + ".jpg" hash_img_pth = f"{self.ABS_THUMBS_PTH}/{file_hash}.jpg"
if isfile(hash_img_pth) == False: if isfile(hash_img_pth) == False:
self.generate_video_thumbnail(full_path, hash_img_pth) self.generate_video_thumbnail(full_path, hash_img_pth)
thumbnl = self.create_scaled_image(hash_img_pth, self.VIDEO_ICON_WH) thumbnl = self.create_scaled_image(hash_img_pth, self.VIDEO_ICON_WH)
if thumbnl == None: # If no icon whatsoever, return internal default if thumbnl == None: # If no icon whatsoever, return internal default
thumbnl = GdkPixbuf.Pixbuf.new_from_file(self.DEFAULT_ICONS + "/video.png") thumbnl = GdkPixbuf.Pixbuf.new_from_file(f"{self.DEFAULT_ICONS}/video.png")
return thumbnl return thumbnl
except Exception as e: except Exception as e:
print("Thumbnail generation issue:") print("Thumbnail generation issue:")
print( repr(e) ) print( repr(e) )
return GdkPixbuf.Pixbuf.new_from_file(self.DEFAULT_ICONS + "/video.png") return GdkPixbuf.Pixbuf.new_from_file(f"{self.DEFAULT_ICONS}/video.png")
def create_scaled_image(self, path, wxh): def create_scaled_image(self, path, wxh):
try: try:
pixbuf = GdkPixbuf.Pixbuf.new_from_file(path) if path.lower().endswith(".gif"):
scaled_pixbuf = pixbuf.scale_simple(wxh[0], wxh[1], 2) # 2 = BILINEAR and is best by default return GdkPixbuf.PixbufAnimation.new_from_file(path) \
return scaled_pixbuf .get_static_image() \
.scale_simple(wxh[0], wxh[1], GdkPixbuf.InterpType.BILINEAR)
else:
return GdkPixbuf.Pixbuf.new_from_file_at_scale(path, wxh[0], wxh[1], True)
except Exception as e: except Exception as e:
print("Image Scaling Issue:") print("Image Scaling Issue:")
print( repr(e) ) print( repr(e) )

View File

@@ -59,7 +59,7 @@ class Settings:
subpath = settings["base_of_home"] subpath = settings["base_of_home"]
HIDE_HIDDEN_FILES = True if settings["hide_hidden_files"] == "true" else False HIDE_HIDDEN_FILES = True if settings["hide_hidden_files"] == "true" else False
FFMPG_THUMBNLR = FFMPG_THUMBNLR if settings["thumbnailer_path"] == "" else settings["thumbnailer_path"] FFMPG_THUMBNLR = FFMPG_THUMBNLR if settings["thumbnailer_path"] == "" else settings["thumbnailer_path"]
go_past_home = True if settings["go_past_home"] == "true" else False go_past_home = True if settings["go_past_home"] == "" else settings["go_past_home"]
lock_folder = True if settings["lock_folder"] == "true" else False lock_folder = True if settings["lock_folder"] == "true" else False
locked_folders = settings["locked_folders"].split("::::") locked_folders = settings["locked_folders"].split("::::")
mplayer_options = settings["mplayer_options"].split() mplayer_options = settings["mplayer_options"].split()

View File

@@ -1,5 +1,5 @@
# Python imports # Python imports
import sys, traceback, threading, signal, inspect, os, time import sys, traceback, threading, inspect, os, time
# Lib imports # Lib imports
import gi import gi
@@ -7,8 +7,9 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib from gi.repository import Gtk, GLib
# Application imports # Application imports
from .mixins import * from .mixins.ui import *
from . import ShowHideMixin, KeyboardSignalsMixin, Controller_Data from .mixins import ShowHideMixin, KeyboardSignalsMixin
from . import Controller_Data
def threaded(fn): def threaded(fn):
@@ -23,25 +24,23 @@ class Controller(WidgetFileActionMixin, PaneMixin, WindowMixin, ShowHideMixin, \
KeyboardSignalsMixin, Controller_Data): KeyboardSignalsMixin, Controller_Data):
def __init__(self, args, unknownargs, _settings): def __init__(self, args, unknownargs, _settings):
# sys.excepthook = self.custom_except_hook # sys.excepthook = self.custom_except_hook
self.settings = _settings self.setup_controller_data(_settings)
self.setup_controller_data()
self.window.show() self.window.show()
self.generate_windows(self.state) self.generate_windows(self.state)
self.plugins.launch_plugins()
self.window.connect("delete-event", self.tear_down) if not trace_debug:
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.tear_down) self.gui_event_observer()
self.gui_event_observer()
if unknownargs: if unknownargs:
for arg in unknownargs: for arg in unknownargs:
if os.path.isdir(arg): if os.path.isdir(arg):
message = f"FILE|{arg}" message = f"FILE|{arg}"
event_system.send_ipc_message(message) event_system.send_ipc_message(message)
if args.new_tab and os.path.isdir(args.new_tab): if args.new_tab and os.path.isdir(args.new_tab):
message = f"FILE|{args.new_tab}" message = f"FILE|{args.new_tab}"
event_system.send_ipc_message(message) event_system.send_ipc_message(message)
def tear_down(self, widget=None, eve=None): def tear_down(self, widget=None, eve=None):

View File

@@ -1,4 +1,5 @@
# Python imports # Python imports
import signal
# Lib imports # Lib imports
from gi.repository import GLib from gi.repository import GLib
@@ -6,6 +7,7 @@ from gi.repository import GLib
# Application imports # Application imports
from shellfm import WindowController from shellfm import WindowController
from trasher.xdgtrash import XDGTrash from trasher.xdgtrash import XDGTrash
from . import Plugins
@@ -14,16 +16,18 @@ class Controller_Data:
def has_method(self, o, name): def has_method(self, o, name):
return callable(getattr(o, name, None)) return callable(getattr(o, name, None))
def setup_controller_data(self): def setup_controller_data(self, _settings):
self.window_controller = WindowController()
self.trashman = XDGTrash() self.trashman = XDGTrash()
self.window_controller = WindowController()
self.plugins = Plugins(_settings)
self.state = self.window_controller.load_state()
self.trashman.regenerate() self.trashman.regenerate()
self.state = self.window_controller.load_state() self.settings = _settings
self.builder = self.settings.builder self.builder = self.settings.get_builder()
self.logger = self.settings.logger self.logger = self.settings.get_logger()
self.window = self.settings.getMainWindow() self.window = self.settings.get_main_window()
self.window1 = self.builder.get_object("window_1") self.window1 = self.builder.get_object("window_1")
self.window2 = self.builder.get_object("window_2") self.window2 = self.builder.get_object("window_2")
self.window3 = self.builder.get_object("window_3") self.window3 = self.builder.get_object("window_3")
@@ -85,10 +89,10 @@ class Controller_Data:
self.is_pane3_hidden = False self.is_pane3_hidden = False
self.is_pane4_hidden = False self.is_pane4_hidden = False
self.is_searching = False self.override_drop_dest = None
self.search_iconview = None self.is_searching = False
self.search_view = None self.search_iconview = None
self.search_view = None
self.skip_edit = False self.skip_edit = False
self.cancel_edit = False self.cancel_edit = False
@@ -99,3 +103,7 @@ class Controller_Data:
self.success = "#88cc27" self.success = "#88cc27"
self.warning = "#ffa800" self.warning = "#ffa800"
self.error = "#ff0000" self.error = "#ff0000"
self.window.connect("delete-event", self.tear_down)
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.tear_down)

View File

@@ -15,11 +15,11 @@ def threaded(fn):
class DBusControllerMixin: class IPCServerMixin:
@threaded @threaded
def create_ipc_server(self): def create_ipc_server(self):
listener = Listener(('127.0.0.1', 4848), authkey=b'solarfm-ipc') listener = Listener((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey)
self.is_ipc_alive = True self.is_ipc_alive = True
while True: while True:
conn = listener.accept() conn = listener.accept()
@@ -49,7 +49,7 @@ class DBusControllerMixin:
# NOTE: Not perfect but insures we don't lockup the connection for too long. # NOTE: Not perfect but insures we don't lockup the connection for too long.
end_time = time.time() end_time = time.time()
if (end - start) > 15.0: if (end - start) > self.ipc_timeout:
conn.close() conn.close()
listener.close() listener.close()
@@ -57,7 +57,7 @@ class DBusControllerMixin:
def send_ipc_message(self, message="Empty Data..."): def send_ipc_message(self, message="Empty Data..."):
try: try:
conn = Client(('127.0.0.1', 4848), authkey=b'solarfm-ipc') conn = Client((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey)
conn.send(message) conn.send(message)
conn.send('close connection') conn.send('close connection')
except Exception as e: except Exception as e:

View File

@@ -0,0 +1,41 @@
# 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

@@ -2,8 +2,7 @@
Gtk Bound Signal Module Gtk Bound Signal Module
""" """
from .mixins import * from .mixins import *
from .DBusControllerMixin import DBusControllerMixin from .IPCServerMixin import IPCServerMixin
from .KeyboardSignalsMixin import KeyboardSignalsMixin from .Plugins import Plugins
from .ShowHideMixin import ShowHideMixin
from .Controller_Data import Controller_Data from .Controller_Data import Controller_Data
from .Controller import Controller from .Controller import Controller

View File

@@ -1,5 +1,2 @@
from .PaneMixin import PaneMixin from .KeyboardSignalsMixin import KeyboardSignalsMixin
from .WidgetMixin import WidgetMixin from .ShowHideMixin import ShowHideMixin
from .TabMixin import TabMixin
from .WindowMixin import WindowMixin
from .WidgetFileActionMixin import WidgetFileActionMixin

View File

@@ -107,7 +107,7 @@ class WidgetFileActionMixin:
def open_with_files(self, appchooser_widget): def open_with_files(self, appchooser_widget):
wid, tid, view, iconview, store = self.get_current_state() wid, tid, view, iconview, store = self.get_current_state()
app_info = appchooser_widget.get_app_info() app_info = appchooser_widget.get_app_info()
uris = self.format_to_uris(store, wid, tid, self.selected_files, True) uris = self.format_to_uris(store, wid, tid, self.selected_files)
view.app_chooser_exec(app_info, uris) view.app_chooser_exec(app_info, uris)

View File

@@ -1,5 +1,5 @@
# Python imports # Python imports
import os, threading, subprocess import os, threading, subprocess, time
# Lib imports # Lib imports
import gi import gi
@@ -20,15 +20,13 @@ def threaded(fn):
class WidgetMixin: class WidgetMixin:
def load_store(self, view, store, save_state=False): def load_store(self, view, store, save_state=False):
store.clear() store.clear()
dir = view.get_current_directory() dir = view.get_current_directory()
files = view.get_files() files = view.get_files()
icon = GdkPixbuf.Pixbuf.new_from_file(view.DEFAULT_ICON)
for i, file in enumerate(files): for i, file in enumerate(files):
store.append([icon, file[0]]) store.append([None, file[0]])
self.create_icon(i, view, store, dir, file[0]) self.create_icon(i, view, store, dir, file[0])
# NOTE: Not likely called often from here but it could be useful # NOTE: Not likely called often from here but it could be useful
@@ -50,10 +48,14 @@ class WidgetMixin:
try: try:
itr = store.get_iter(i) itr = store.get_iter(i)
except Exception as e: except Exception as e:
print(":Invalid Itr detected: (Potential race condition...)") try:
print(f"Index Requested: {i}") time.sleep(0.2)
print(f"Store Size: {len(store)}") itr = store.get_iter(i)
return except Exception as e:
print(":Invalid Itr detected: (Potential race condition...)")
print(f"Index Requested: {i}")
print(f"Store Size: {len(store)}")
return
if not icon: if not icon:
icon = self.get_system_thumbnail(fpath, view.SYS_ICON_WH[0]) icon = self.get_system_thumbnail(fpath, view.SYS_ICON_WH[0])
@@ -113,7 +115,7 @@ class WidgetMixin:
def create_grid_iconview_widget(self, view, wid): def create_grid_iconview_widget(self, view, wid):
scroll = Gtk.ScrolledWindow() scroll = Gtk.ScrolledWindow()
grid = Gtk.IconView() grid = Gtk.IconView()
store = Gtk.ListStore(GdkPixbuf.Pixbuf, str) store = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str)
grid.set_model(store) grid.set_model(store)
grid.set_pixbuf_column(0) grid.set_pixbuf_column(0)
@@ -131,11 +133,14 @@ class WidgetMixin:
grid.connect("button_release_event", self.grid_icon_single_click) grid.connect("button_release_event", self.grid_icon_single_click)
grid.connect("item-activated", self.grid_icon_double_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("selection-changed", self.grid_set_selected_items)
grid.connect("drag-data-get", self.grid_on_drag_set) grid.connect("drag-data-get", self.grid_on_drag_set)
grid.connect("drag-data-received", self.grid_on_drag_data_received) grid.connect("drag-data-received", self.grid_on_drag_data_received)
grid.connect("drag-motion", self.grid_on_drag_motion) grid.connect("drag-motion", self.grid_on_drag_motion)
URI_TARGET_TYPE = 80 URI_TARGET_TYPE = 80
uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE) uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE)
targets = [ uri_target ] targets = [ uri_target ]
@@ -154,8 +159,8 @@ class WidgetMixin:
def create_grid_treeview_widget(self, view, wid): def create_grid_treeview_widget(self, view, wid):
scroll = Gtk.ScrolledWindow() scroll = Gtk.ScrolledWindow()
grid = Gtk.TreeView() grid = Gtk.TreeView()
store = Gtk.ListStore(GdkPixbuf.Pixbuf, str) store = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str)
# store = Gtk.TreeStore(GdkPixbuf.Pixbuf, str) # store = Gtk.TreeStore(GdkPixbuf.Pixbuf or None, str)
column = Gtk.TreeViewColumn("Icons") column = Gtk.TreeViewColumn("Icons")
icon = Gtk.CellRendererPixbuf() icon = Gtk.CellRendererPixbuf()
name = Gtk.CellRendererText() name = Gtk.CellRendererText()

View File

@@ -82,7 +82,7 @@ class WindowMixin(TabMixin):
_wid, _tid, _view, iconview, store = self.get_current_state() _wid, _tid, _view, iconview, store = self.get_current_state()
selected_files = iconview.get_selected_items() selected_files = iconview.get_selected_items()
current_directory = view.get_current_directory() current_directory = view.get_current_directory()
path_file = Gio.File.new_for_path( current_directory) path_file = Gio.File.new_for_path(current_directory)
mount_file = path_file.query_filesystem_info(attributes="filesystem::*", cancellable=None) mount_file = path_file.query_filesystem_info(attributes="filesystem::*", cancellable=None)
formatted_mount_free = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::free")) ) formatted_mount_free = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::free")) )
formatted_mount_size = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::size")) ) formatted_mount_size = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::size")) )
@@ -101,11 +101,16 @@ class WindowMixin(TabMixin):
uris = self.format_to_uris(store, _wid, _tid, selected_files, True) uris = self.format_to_uris(store, _wid, _tid, selected_files, True)
combined_size = 0 combined_size = 0
for uri in uris: for uri in uris:
file_info = Gio.File.new_for_path(uri).query_info(attributes="standard::size", try:
flags=Gio.FileQueryInfoFlags.NONE, file_info = Gio.File.new_for_path(uri).query_info(attributes="standard::size",
cancellable=None) flags=Gio.FileQueryInfoFlags.NONE,
file_size = file_info.get_size() cancellable=None)
combined_size += file_size file_size = file_info.get_size()
combined_size += file_size
except Exception as e:
if debug:
print(repr(e))
formatted_size = self.sizeof_fmt(combined_size) formatted_size = self.sizeof_fmt(combined_size)
if view.hide_hidden: if view.hide_hidden:
@@ -152,6 +157,9 @@ class WindowMixin(TabMixin):
def grid_set_selected_items(self, iconview): def grid_set_selected_items(self, iconview):
self.selected_files = iconview.get_selected_items() self.selected_files = iconview.get_selected_items()
def grid_cursor_toggled(self, iconview):
print("wat...")
def grid_icon_single_click(self, iconview, eve): def grid_icon_single_click(self, iconview, eve):
try: try:
self.path_menu.popdown() self.path_menu.popdown()
@@ -213,8 +221,19 @@ class WindowMixin(TabMixin):
data.set_text(uris_text, -1) data.set_text(uris_text, -1)
def grid_on_drag_motion(self, iconview, drag_context, x, y, data): def grid_on_drag_motion(self, iconview, drag_context, x, y, data):
wid, tid = iconview.get_name().split("|") current = '|'.join(self.window_controller.get_active_data())
self.window_controller.set_active_data(wid, tid) target = iconview.get_name()
wid, tid = target.split("|")
store = iconview.get_model()
treePath = iconview.get_drag_dest_item().path
if treePath:
uri = self.format_to_uris(store, wid, tid, treePath)[0].replace("file://", "")
self.override_drop_dest = uri if isdir(uri) else None
if target not in current:
self.window_controller.set_active_data(wid, tid)
def grid_on_drag_data_received(self, widget, drag_context, x, y, data, info, time): def grid_on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
if info == 80: if info == 80:
@@ -224,12 +243,14 @@ class WindowMixin(TabMixin):
view = self.get_fm_window(wid).get_view_by_id(tid) view = self.get_fm_window(wid).get_view_by_id(tid)
uris = data.get_uris() uris = data.get_uris()
dest = f"{view.get_current_directory()}" dest = f"{view.get_current_directory()}" if not self.override_drop_dest else self.override_drop_dest
if len(uris) > 0: if len(uris) == 0:
self.move_files(uris, dest)
else:
uris = data.get_text().split("\n") uris = data.get_text().split("\n")
from_uri = '/'.join(uris[0].replace("file://", "").split("/")[:-1])
if from_uri != dest:
self.move_files(uris, dest) self.move_files(uris, dest)
def create_new_view_notebook(self, widget=None, wid=None, path=None): def create_new_view_notebook(self, widget=None, wid=None, path=None):
self.create_tab(wid, path) self.create_tab(wid, path)

View File

View File

View File

View File

@@ -5,8 +5,8 @@ import os, logging
class Logger: class Logger:
def __init__(self): def __init__(self, config_path):
pass self._CONFIG_PATH = config_path
def get_logger(self, loggerName = "NO_LOGGER_NAME_PASSED", createFile = True): def get_logger(self, loggerName = "NO_LOGGER_NAME_PASSED", createFile = True):
""" """
@@ -42,8 +42,8 @@ class Logger:
log.addHandler(ch) log.addHandler(ch)
if createFile: if createFile:
folder = "logs" folder = self._CONFIG_PATH
file = folder + "/application.log" file = f"{folder}/application.log"
if not os.path.exists(folder): if not os.path.exists(folder):
os.mkdir(folder) os.mkdir(folder)

View File

@@ -17,39 +17,45 @@ from . import Logger
class Settings: class Settings:
def __init__(self): def __init__(self):
self.logger = Logger().get_logger()
self.builder = gtk.Builder() self.builder = gtk.Builder()
self.SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__)) self.SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__))
self.USER_HOME = path.expanduser('~') self.USER_HOME = path.expanduser('~')
self.CONFIG_PATH = f"{self.USER_HOME}/.config/solarfm" self.CONFIG_PATH = f"{self.USER_HOME}/.config/{app_name.lower()}"
self.USR_SOLARFM = "/usr/share/solarfm" 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.cssFile = f"{self.CONFIG_PATH}/stylesheet.css"
self.windows_glade = f"{self.CONFIG_PATH}/Main_Window.glade" self.windows_glade = f"{self.CONFIG_PATH}/Main_Window.glade"
self.DEFAULT_ICONS = f"{self.CONFIG_PATH}/icons" self.DEFAULT_ICONS = f"{self.CONFIG_PATH}/icons"
self.window_icon = f"{self.DEFAULT_ICONS}/solarfm.png" self.window_icon = f"{self.DEFAULT_ICONS}/{app_name.lower()}.png"
self.main_window = None self.main_window = None
if not os.path.exists(self.CONFIG_PATH):
os.mkdir(self.CONFIG_PATH)
if not os.path.exists(self.PLUGINS_PATH):
os.mkdir(self.PLUGINS_PATH)
if not os.path.exists(self.windows_glade): if not os.path.exists(self.windows_glade):
self.windows_glade = f"{self.USR_SOLARFM}/Main_Window.glade" self.windows_glade = f"{self.USR_SOLARFM}/Main_Window.glade"
if not os.path.exists(self.cssFile): if not os.path.exists(self.cssFile):
self.cssFile = f"{self.USR_SOLARFM}/stylesheet.css" self.cssFile = f"{self.USR_SOLARFM}/stylesheet.css"
if not os.path.exists(self.window_icon): if not os.path.exists(self.window_icon):
self.window_icon = f"{self.USR_SOLARFM}/icons/solarfm.png" self.window_icon = f"{self.USR_SOLARFM}/icons/{app_name.lower()}.png"
if not os.path.exists(self.DEFAULT_ICONS): if not os.path.exists(self.DEFAULT_ICONS):
self.DEFAULT_ICONS = f"{self.USR_SOLARFM}/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)
def createWindow(self): def create_window(self):
# Get window and connect signals # Get window and connect signals
self.main_window = self.builder.get_object("Main_Window") self.main_window = self.builder.get_object("Main_Window")
self.setWindowData() self._set_window_data()
def setWindowData(self): 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() screen = self.main_window.get_screen()
visual = screen.get_rgba_visual() visual = screen.get_rgba_visual()
@@ -57,7 +63,7 @@ class Settings:
if visual != None and screen.is_composited(): if visual != None and screen.is_composited():
self.main_window.set_visual(visual) self.main_window.set_visual(visual)
self.main_window.set_app_paintable(True) self.main_window.set_app_paintable(True)
self.main_window.connect("draw", self.area_draw) self.main_window.connect("draw", self._area_draw)
# bind css file # bind css file
cssProvider = gtk.CssProvider() cssProvider = gtk.CssProvider()
@@ -66,16 +72,13 @@ class Settings:
styleContext = gtk.StyleContext() styleContext = gtk.StyleContext()
styleContext.add_provider_for_screen(screen, cssProvider, gtk.STYLE_PROVIDER_PRIORITY_USER) styleContext.add_provider_for_screen(screen, cssProvider, gtk.STYLE_PROVIDER_PRIORITY_USER)
def area_draw(self, widget, cr): def _area_draw(self, widget, cr):
cr.set_source_rgba(0, 0, 0, 0.54) cr.set_source_rgba(0, 0, 0, 0.54)
cr.set_operator(cairo.OPERATOR_SOURCE) cr.set_operator(cairo.OPERATOR_SOURCE)
cr.paint() cr.paint()
cr.set_operator(cairo.OPERATOR_OVER) cr.set_operator(cairo.OPERATOR_OVER)
def getMainWindow(self): return self.main_window def get_monitor_data(self):
def getMonitorData(self):
screen = self.builder.get_object("Main_Window").get_screen() screen = self.builder.get_object("Main_Window").get_screen()
monitors = [] monitors = []
for m in range(screen.get_n_monitors()): for m in range(screen.get_n_monitors()):
@@ -85,3 +88,8 @@ class Settings:
print("{}x{}+{}+{}".format(monitor.width, monitor.height, monitor.x, monitor.y)) print("{}x{}+{}+{}".format(monitor.width, monitor.height, monitor.x, monitor.y))
return monitors return monitors
def get_builder(self): return self.builder
def get_logger(self): return self.logger
def get_main_window(self): return self.main_window
def get_plugins_path(self): return self.PLUGINS_PATH

View File

@@ -4,20 +4,28 @@ import builtins
# Lib imports # Lib imports
# Application imports # Application imports
from signal_classes.DBusControllerMixin import DBusControllerMixin from controller import IPCServerMixin
class Builtins(DBusControllerMixin): class Builtins(IPCServerMixin):
"""Docstring for __builtins__ extender""" """Docstring for __builtins__ extender"""
def __init__(self): 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 # Where data may be any kind of data
self._gui_events = [] self._gui_events = []
self._fm_events = [] self._fm_events = []
self.is_ipc_alive = False self.is_ipc_alive = False
self.ipc_authkey = b'solarfm-ipc'
self.ipc_address = '127.0.0.1'
self.ipc_port = 4848
self.ipc_timeout = 15.0
# Makeshift fake "events" type system FIFO # Makeshift fake "events" type system FIFO
def _pop_gui_event(self): def _pop_gui_event(self):
@@ -61,6 +69,8 @@ class Builtins(DBusControllerMixin):
# 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 = "SolarFM"
builtins.event_system = Builtins() builtins.event_system = Builtins()
builtins.event_sleep_time = 0.5 builtins.event_sleep_time = 0.2
builtins.debug = False builtins.debug = False
builtins.trace_debug = False

View File

@@ -5,7 +5,7 @@ import os, inspect, time
# Application imports # Application imports
from utils import Settings from utils import Settings
from signal_classes import Controller from controller import Controller
from __builtins__ import Builtins from __builtins__ import Builtins
@@ -13,24 +13,27 @@ from __builtins__ import Builtins
class Main(Builtins): class Main(Builtins):
def __init__(self, args, unknownargs): def __init__(self, args, unknownargs):
event_system.create_ipc_server() if not debug:
time.sleep(0.5) event_system.create_ipc_server()
if not event_system.is_ipc_alive:
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): time.sleep(0.2)
message = f"FILE|{args.new_tab}" if not trace_debug:
event_system.send_ipc_message(message) if not event_system.is_ipc_alive:
if unknownargs:
for arg in unknownargs:
if os.path.isdir(arg):
message = f"FILE|{arg}"
event_system.send_ipc_message(message)
raise Exception("IPC Server Exists: Will send path(s) to it and close...") if args.new_tab and os.path.isdir(args.new_tab):
message = f"FILE|{args.new_tab}"
event_system.send_ipc_message(message)
raise Exception("IPC Server Exists: Will send path(s) to it and close...")
settings = Settings() settings = Settings()
settings.createWindow() settings.create_window()
controller = Controller(args, unknownargs, settings) controller = Controller(args, unknownargs, settings)
if not controller: if not controller:

View File

@@ -23,7 +23,7 @@ if __name__ == "__main__":
# import web_pdb # import web_pdb
# web_pdb.set_trace() # web_pdb.set_trace()
setproctitle('solarfm') setproctitle('SolarFM')
faulthandler.enable() # For better debug info faulthandler.enable() # For better debug info
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
# Add long and short arguments # Add long and short arguments

View File

@@ -1,5 +1,5 @@
# Python imports # Python imports
import sys, traceback, threading, signal, inspect, os, time import sys, traceback, threading, inspect, os, time
# Lib imports # Lib imports
import gi import gi
@@ -7,8 +7,9 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib from gi.repository import Gtk, GLib
# Application imports # Application imports
from .mixins import * from .mixins import UIMixin
from . import ShowHideMixin, KeyboardSignalsMixin, Controller_Data from .signals import IPCSignalsMixin, KeyboardSignalsMixin
from . import Controller_Data
def threaded(fn): def threaded(fn):
@@ -19,29 +20,26 @@ def threaded(fn):
class Controller(WidgetFileActionMixin, PaneMixin, WindowMixin, ShowHideMixin, \ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, Controller_Data):
KeyboardSignalsMixin, Controller_Data):
def __init__(self, args, unknownargs, _settings): def __init__(self, args, unknownargs, _settings):
# sys.excepthook = self.custom_except_hook sys.excepthook = self.custom_except_hook
self.settings = _settings self.setup_controller_data(_settings)
self.setup_controller_data()
self.window.show() self.window.show()
self.generate_windows(self.state) self.generate_windows(self.state)
self.plugins.launch_plugins()
self.window.connect("delete-event", self.tear_down) if not trace_debug:
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.tear_down) self.gui_event_observer()
self.gui_event_observer()
if unknownargs: if unknownargs:
for arg in unknownargs: for arg in unknownargs:
if os.path.isdir(arg): if os.path.isdir(arg):
message = f"FILE|{arg}" message = f"FILE|{arg}"
event_system.send_ipc_message(message) event_system.send_ipc_message(message)
if args.new_tab and os.path.isdir(args.new_tab): if args.new_tab and os.path.isdir(args.new_tab):
message = f"FILE|{args.new_tab}" message = f"FILE|{args.new_tab}"
event_system.send_ipc_message(message) event_system.send_ipc_message(message)
def tear_down(self, widget=None, eve=None): def tear_down(self, widget=None, eve=None):
@@ -59,8 +57,8 @@ class Controller(WidgetFileActionMixin, PaneMixin, WindowMixin, ShowHideMixin, \
if event: if event:
try: try:
type, target, data = event type, target, data = event
method = getattr(self.__class__, type) method = getattr(self.__class__, target)
GLib.idle_add(method, (self, data,)) GLib.idle_add(method, *(self, *data,))
except Exception as e: except Exception as e:
print(repr(e)) print(repr(e))
@@ -158,9 +156,7 @@ class Controller(WidgetFileActionMixin, PaneMixin, WindowMixin, ShowHideMixin, \
if action == "empty_trash": if action == "empty_trash":
self.empty_trash() self.empty_trash()
if action == "create": if action == "create":
self.create_files() self.create_files()
self.hide_new_file_menu()
self.ctrlDown = False self.ctrlDown = False

View File

@@ -1,11 +1,13 @@
# Python imports # Python imports
import signal
# Lib imports # Lib imports
from gi.repository import GLib from gi.repository import GLib
# Application imports # Application imports
from shellfm import WindowController
from trasher.xdgtrash import XDGTrash from trasher.xdgtrash import XDGTrash
from shellfm import WindowController
from plugins import Plugins
@@ -14,16 +16,18 @@ class Controller_Data:
def has_method(self, o, name): def has_method(self, o, name):
return callable(getattr(o, name, None)) return callable(getattr(o, name, None))
def setup_controller_data(self): def setup_controller_data(self, _settings):
self.window_controller = WindowController()
self.trashman = XDGTrash() self.trashman = XDGTrash()
self.window_controller = WindowController()
self.plugins = Plugins(_settings)
self.state = self.window_controller.load_state()
self.trashman.regenerate() self.trashman.regenerate()
self.state = self.window_controller.load_state() self.settings = _settings
self.builder = self.settings.builder self.builder = self.settings.get_builder()
self.logger = self.settings.logger self.logger = self.settings.get_logger()
self.window = self.settings.getMainWindow() self.window = self.settings.get_main_window()
self.window1 = self.builder.get_object("window_1") self.window1 = self.builder.get_object("window_1")
self.window2 = self.builder.get_object("window_2") self.window2 = self.builder.get_object("window_2")
self.window3 = self.builder.get_object("window_3") self.window3 = self.builder.get_object("window_3")
@@ -85,10 +89,10 @@ class Controller_Data:
self.is_pane3_hidden = False self.is_pane3_hidden = False
self.is_pane4_hidden = False self.is_pane4_hidden = False
self.is_searching = False self.override_drop_dest = None
self.search_iconview = None self.is_searching = False
self.search_view = None self.search_iconview = None
self.search_view = None
self.skip_edit = False self.skip_edit = False
self.cancel_edit = False self.cancel_edit = False
@@ -99,3 +103,7 @@ class Controller_Data:
self.success = "#88cc27" self.success = "#88cc27"
self.warning = "#ffa800" self.warning = "#ffa800"
self.error = "#ff0000" self.error = "#ff0000"
self.window.connect("delete-event", self.tear_down)
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.tear_down)

View File

@@ -15,11 +15,11 @@ def threaded(fn):
class DBusControllerMixin: class IPCServerMixin:
@threaded @threaded
def create_ipc_server(self): def create_ipc_server(self):
listener = Listener(('127.0.0.1', 4848), authkey=b'solarfm-ipc') listener = Listener((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey)
self.is_ipc_alive = True self.is_ipc_alive = True
while True: while True:
conn = listener.accept() conn = listener.accept()
@@ -34,7 +34,7 @@ class DBusControllerMixin:
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.push_gui_event(["create_tab_from_ipc", None, file]) event_system.push_gui_event([None, "handle_file_from_ipc", file])
conn.close() conn.close()
break break
@@ -49,7 +49,7 @@ class DBusControllerMixin:
# NOTE: Not perfect but insures we don't lockup the connection for too long. # NOTE: Not perfect but insures we don't lockup the connection for too long.
end_time = time.time() end_time = time.time()
if (end - start) > 15.0: if (end - start) > self.ipc_timeout:
conn.close() conn.close()
listener.close() listener.close()
@@ -57,7 +57,7 @@ class DBusControllerMixin:
def send_ipc_message(self, message="Empty Data..."): def send_ipc_message(self, message="Empty Data..."):
try: try:
conn = Client(('127.0.0.1', 4848), authkey=b'solarfm-ipc') conn = Client((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey)
conn.send(message) conn.send(message)
conn.send('close connection') conn.send('close connection')
except Exception as e: except Exception as e:

View File

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

View File

@@ -16,7 +16,6 @@ class ShowHideMixin:
def stop_file_searching(self, widget=None, eve=None): def stop_file_searching(self, widget=None, eve=None):
self.is_searching = False self.is_searching = False
def show_exists_page(self, widget=None, eve=None): def show_exists_page(self, widget=None, eve=None):
response = self.file_exists_dialog.run() response = self.file_exists_dialog.run()
self.file_exists_dialog.hide() self.file_exists_dialog.hide()
@@ -81,12 +80,14 @@ class ShowHideMixin:
appchooser_widget = self.builder.get_object("appchooser_widget") appchooser_widget = self.builder.get_object("appchooser_widget")
response = appchooser_menu.run() response = appchooser_menu.run()
if response == Gtk.ResponseType.CANCEL:
self.hide_appchooser_menu()
if response == Gtk.ResponseType.OK: if response == Gtk.ResponseType.OK:
self.open_with_files(appchooser_widget) self.open_with_files(appchooser_widget)
self.hide_appchooser_menu() self.hide_appchooser_menu()
if response == Gtk.ResponseType.CANCEL:
self.hide_appchooser_menu()
def hide_appchooser_menu(self, widget=None, eve=None): def hide_appchooser_menu(self, widget=None, eve=None):
self.builder.get_object("appchooser_menu").hide() self.builder.get_object("appchooser_menu").hide()
@@ -103,12 +104,18 @@ class ShowHideMixin:
def show_new_file_menu(self, widget=None, eve=None): 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): def hide_new_file_menu(self, widget=None, eve=None):
self.builder.get_object("new_file_menu").hide() self.builder.get_object("new_file_menu").hide()
def show_edit_file_menu(self, widget=None, eve=None): def show_edit_file_menu(self, widget=None, eve=None):
if widget: if widget:
widget.grab_focus() widget.grab_focus()

View File

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

View File

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

View File

@@ -16,24 +16,6 @@ from . import WidgetMixin
class TabMixin(WidgetMixin): class TabMixin(WidgetMixin):
"""docstring for TabMixin""" """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): def create_tab(self, wid, path=None):
notebook = self.builder.get_object(f"window_{wid}") notebook = self.builder.get_object(f"window_{wid}")
path_entry = self.builder.get_object(f"path_entry") path_entry = self.builder.get_object(f"path_entry")

View File

@@ -4,7 +4,7 @@ import os
# Lib imports # Lib imports
import gi import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GObject, Gio from gi.repository import Gtk, GObject, GLib, Gio
# Application imports # Application imports
@@ -107,7 +107,7 @@ class WidgetFileActionMixin:
def open_with_files(self, appchooser_widget): def open_with_files(self, appchooser_widget):
wid, tid, view, iconview, store = self.get_current_state() wid, tid, view, iconview, store = self.get_current_state()
app_info = appchooser_widget.get_app_info() app_info = appchooser_widget.get_app_info()
uris = self.format_to_uris(store, wid, tid, self.selected_files, True) uris = self.format_to_uris(store, wid, tid, self.selected_files)
view.app_chooser_exec(app_info, uris) view.app_chooser_exec(app_info, uris)
@@ -239,12 +239,12 @@ class WidgetFileActionMixin:
else: # Create Folder else: # Create Folder
self.handle_files([path], "create_dir") self.handle_files([path], "create_dir")
fname_field.set_text("") self.hide_new_file_menu()
def move_files(self, files, target): def move_files(self, files, target):
self.handle_files(files, "move", 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 # race condition proof. They're right; but, they can't even delete
# directories properly. So... f**k them. I'll do it my way. # directories properly. So... f**k them. I'll do it my way.
def handle_files(self, paths, action, _target_path=None): def handle_files(self, paths, action, _target_path=None):
@@ -273,8 +273,7 @@ class WidgetFileActionMixin:
if _file.query_exists(): if _file.query_exists():
if not overwrite_all and not rename_auto_all: if not overwrite_all and not rename_auto_all:
self.exists_file_label.set_label(_file.get_basename()) self.setup_exists_data(file, _file)
self.exists_file_field.set_text(_file.get_basename())
response = self.show_exists_page() response = self.show_exists_page()
if response == "overwrite_all": if response == "overwrite_all":
@@ -344,6 +343,45 @@ class WidgetFileActionMixin:
self.exists_file_rename_bttn.set_sensitive(False) 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): def rename_proc(self, gio_file):
full_path = gio_file.get_path() full_path = gio_file.get_path()

View File

@@ -1,5 +1,5 @@
# Python imports # Python imports
import os, threading, subprocess import os, threading, subprocess, time
# Lib imports # Lib imports
import gi import gi
@@ -20,15 +20,13 @@ def threaded(fn):
class WidgetMixin: class WidgetMixin:
def load_store(self, view, store, save_state=False): def load_store(self, view, store, save_state=False):
store.clear() store.clear()
dir = view.get_current_directory() dir = view.get_current_directory()
files = view.get_files() files = view.get_files()
icon = GdkPixbuf.Pixbuf.new_from_file(view.DEFAULT_ICON)
for i, file in enumerate(files): for i, file in enumerate(files):
store.append([icon, file[0]]) store.append([None, file[0]])
self.create_icon(i, view, store, dir, file[0]) self.create_icon(i, view, store, dir, file[0])
# NOTE: Not likely called often from here but it could be useful # NOTE: Not likely called often from here but it could be useful
@@ -50,10 +48,14 @@ class WidgetMixin:
try: try:
itr = store.get_iter(i) itr = store.get_iter(i)
except Exception as e: except Exception as e:
print(":Invalid Itr detected: (Potential race condition...)") try:
print(f"Index Requested: {i}") time.sleep(0.2)
print(f"Store Size: {len(store)}") itr = store.get_iter(i)
return except Exception as e:
print(":Invalid Itr detected: (Potential race condition...)")
print(f"Index Requested: {i}")
print(f"Store Size: {len(store)}")
return
if not icon: if not icon:
icon = self.get_system_thumbnail(fpath, view.SYS_ICON_WH[0]) icon = self.get_system_thumbnail(fpath, view.SYS_ICON_WH[0])
@@ -113,7 +115,7 @@ class WidgetMixin:
def create_grid_iconview_widget(self, view, wid): def create_grid_iconview_widget(self, view, wid):
scroll = Gtk.ScrolledWindow() scroll = Gtk.ScrolledWindow()
grid = Gtk.IconView() grid = Gtk.IconView()
store = Gtk.ListStore(GdkPixbuf.Pixbuf, str) store = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str)
grid.set_model(store) grid.set_model(store)
grid.set_pixbuf_column(0) grid.set_pixbuf_column(0)
@@ -131,11 +133,14 @@ class WidgetMixin:
grid.connect("button_release_event", self.grid_icon_single_click) grid.connect("button_release_event", self.grid_icon_single_click)
grid.connect("item-activated", self.grid_icon_double_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("selection-changed", self.grid_set_selected_items)
grid.connect("drag-data-get", self.grid_on_drag_set) grid.connect("drag-data-get", self.grid_on_drag_set)
grid.connect("drag-data-received", self.grid_on_drag_data_received) grid.connect("drag-data-received", self.grid_on_drag_data_received)
grid.connect("drag-motion", self.grid_on_drag_motion) grid.connect("drag-motion", self.grid_on_drag_motion)
URI_TARGET_TYPE = 80 URI_TARGET_TYPE = 80
uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE) uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE)
targets = [ uri_target ] targets = [ uri_target ]
@@ -154,8 +159,8 @@ class WidgetMixin:
def create_grid_treeview_widget(self, view, wid): def create_grid_treeview_widget(self, view, wid):
scroll = Gtk.ScrolledWindow() scroll = Gtk.ScrolledWindow()
grid = Gtk.TreeView() grid = Gtk.TreeView()
store = Gtk.ListStore(GdkPixbuf.Pixbuf, str) store = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str)
# store = Gtk.TreeStore(GdkPixbuf.Pixbuf, str) # store = Gtk.TreeStore(GdkPixbuf.Pixbuf or None, str)
column = Gtk.TreeViewColumn("Icons") column = Gtk.TreeViewColumn("Icons")
icon = Gtk.CellRendererPixbuf() icon = Gtk.CellRendererPixbuf()
name = Gtk.CellRendererText() name = Gtk.CellRendererText()

View File

@@ -82,7 +82,7 @@ class WindowMixin(TabMixin):
_wid, _tid, _view, iconview, store = self.get_current_state() _wid, _tid, _view, iconview, store = self.get_current_state()
selected_files = iconview.get_selected_items() selected_files = iconview.get_selected_items()
current_directory = view.get_current_directory() current_directory = view.get_current_directory()
path_file = Gio.File.new_for_path( current_directory) path_file = Gio.File.new_for_path(current_directory)
mount_file = path_file.query_filesystem_info(attributes="filesystem::*", cancellable=None) mount_file = path_file.query_filesystem_info(attributes="filesystem::*", cancellable=None)
formatted_mount_free = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::free")) ) formatted_mount_free = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::free")) )
formatted_mount_size = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::size")) ) formatted_mount_size = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::size")) )
@@ -101,11 +101,16 @@ class WindowMixin(TabMixin):
uris = self.format_to_uris(store, _wid, _tid, selected_files, True) uris = self.format_to_uris(store, _wid, _tid, selected_files, True)
combined_size = 0 combined_size = 0
for uri in uris: for uri in uris:
file_info = Gio.File.new_for_path(uri).query_info(attributes="standard::size", try:
flags=Gio.FileQueryInfoFlags.NONE, file_info = Gio.File.new_for_path(uri).query_info(attributes="standard::size",
cancellable=None) flags=Gio.FileQueryInfoFlags.NONE,
file_size = file_info.get_size() cancellable=None)
combined_size += file_size file_size = file_info.get_size()
combined_size += file_size
except Exception as e:
if debug:
print(repr(e))
formatted_size = self.sizeof_fmt(combined_size) formatted_size = self.sizeof_fmt(combined_size)
if view.hide_hidden: if view.hide_hidden:
@@ -152,6 +157,9 @@ class WindowMixin(TabMixin):
def grid_set_selected_items(self, iconview): def grid_set_selected_items(self, iconview):
self.selected_files = iconview.get_selected_items() self.selected_files = iconview.get_selected_items()
def grid_cursor_toggled(self, iconview):
print("wat...")
def grid_icon_single_click(self, iconview, eve): def grid_icon_single_click(self, iconview, eve):
try: try:
self.path_menu.popdown() self.path_menu.popdown()
@@ -213,8 +221,19 @@ class WindowMixin(TabMixin):
data.set_text(uris_text, -1) data.set_text(uris_text, -1)
def grid_on_drag_motion(self, iconview, drag_context, x, y, data): def grid_on_drag_motion(self, iconview, drag_context, x, y, data):
wid, tid = iconview.get_name().split("|") current = '|'.join(self.window_controller.get_active_data())
self.window_controller.set_active_data(wid, tid) target = iconview.get_name()
wid, tid = target.split("|")
store = iconview.get_model()
treePath = iconview.get_drag_dest_item().path
if treePath:
uri = self.format_to_uris(store, wid, tid, treePath)[0].replace("file://", "")
self.override_drop_dest = uri if isdir(uri) else None
if target not in current:
self.window_controller.set_active_data(wid, tid)
def grid_on_drag_data_received(self, widget, drag_context, x, y, data, info, time): def grid_on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
if info == 80: if info == 80:
@@ -224,12 +243,14 @@ class WindowMixin(TabMixin):
view = self.get_fm_window(wid).get_view_by_id(tid) view = self.get_fm_window(wid).get_view_by_id(tid)
uris = data.get_uris() uris = data.get_uris()
dest = f"{view.get_current_directory()}" dest = f"{view.get_current_directory()}" if not self.override_drop_dest else self.override_drop_dest
if len(uris) > 0: if len(uris) == 0:
self.move_files(uris, dest)
else:
uris = data.get_text().split("\n") uris = data.get_text().split("\n")
from_uri = '/'.join(uris[0].replace("file://", "").split("/")[:-1])
if from_uri != dest:
self.move_files(uris, dest) self.move_files(uris, dest)
def create_new_view_notebook(self, widget=None, wid=None, path=None): def create_new_view_notebook(self, widget=None, wid=None, path=None):
self.create_tab(wid, path) self.create_tab(wid, path)

View File

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

View File

@@ -0,0 +1,27 @@
# Python imports
# Lib imports
# Application imports
class IPCSignalsMixin:
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_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)

View File

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

View File

@@ -0,0 +1,63 @@
# Python imports
import os, importlib
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 Plugins:
"""docstring for Plugins"""
def __init__(self, settings):
self._settings = settings
self._plugins_path = self._settings.get_plugins_path()
self.gtk_socket = Gtk.Socket().new()
self._plugins_dir_watcher = None
self.gtk_socket_id = None
self._plugin_collection = []
self._settings.get_main_window().add(self.gtk_socket)
self.gtk_socket.show()
self.gtk_socket_id = self.gtk_socket.get_id()
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)
def load_plugins(self, file=None):
print(f"Loading plugins...")
for file in os.listdir(self._plugins_path):
path = join(self._plugins_path, file)
if isdir(path):
spec = importlib.util.spec_from_file_location(file, join(path, "__main__.py"))
module = importlib.util.module_from_spec(spec)
self._plugin_collection.append([file, module])
spec.loader.exec_module(module)
module.Main(self.gtk_socket_id, event_system)
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)

View File

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

View File

@@ -24,7 +24,9 @@ class WindowController:
self.active_window_id = "" self.active_window_id = ""
self.active_tab_id = "" self.active_tab_id = ""
self.windows = [] self.windows = []
self.fm_event_observer()
if not trace_debug:
self.fm_event_observer()
@threaded @threaded
def fm_event_observer(self): def fm_event_observer(self):

View File

@@ -11,7 +11,7 @@ class Path:
return os.path.expanduser("~") + self.subpath return os.path.expanduser("~") + self.subpath
def get_path(self): def get_path(self):
return "/" + "/".join(self.path) return f"/{'/'.join(self.path)}" if self.path else f"/{''.join(self.path)}"
def get_path_list(self): def get_path_list(self):
return self.path return self.path
@@ -21,7 +21,7 @@ class Path:
self.load_directory() self.load_directory()
def pop_from_path(self): def pop_from_path(self):
if len(self.path) > 1: try:
self.path.pop() self.path.pop()
if not self.go_past_home: if not self.go_past_home:
@@ -29,6 +29,8 @@ class Path:
self.set_to_home() self.set_to_home()
self.load_directory() self.load_directory()
except Exception as e:
pass
def set_path(self, path): def set_path(self, path):
if path == self.get_path(): if path == self.get_path():

View File

@@ -199,7 +199,7 @@ class View(Settings, FileHandler, Launcher, Icon, Path):
def get_current_sub_path(self): def get_current_sub_path(self):
path = self.get_path() path = self.get_path()
home = self.get_home() + "/" home = f"{self.get_home()}/"
return path.replace(home, "") return path.replace(home, "")
def get_end_of_path(self): def get_end_of_path(self):

View File

@@ -17,7 +17,7 @@ def threaded(fn):
class Icon(DesktopIconMixin, VideoIconMixin): class Icon(DesktopIconMixin, VideoIconMixin):
def create_icon(self, dir, file): def create_icon(self, dir, file):
full_path = dir + "/" + file full_path = f"{dir}/{file}"
return self.get_icon_image(dir, file, full_path) return self.get_icon_image(dir, file, full_path)
def get_icon_image(self, dir, file, full_path): def get_icon_image(self, dir, file, full_path):
@@ -36,29 +36,32 @@ class Icon(DesktopIconMixin, VideoIconMixin):
return None return None
def create_thumbnail(self, dir, file): def create_thumbnail(self, dir, file):
full_path = dir + "/" + file full_path = f"{dir}/{file}"
try: try:
file_hash = hashlib.sha256(str.encode(full_path)).hexdigest() file_hash = hashlib.sha256(str.encode(full_path)).hexdigest()
hash_img_pth = self.ABS_THUMBS_PTH + "/" + file_hash + ".jpg" hash_img_pth = f"{self.ABS_THUMBS_PTH}/{file_hash}.jpg"
if isfile(hash_img_pth) == False: if isfile(hash_img_pth) == False:
self.generate_video_thumbnail(full_path, hash_img_pth) self.generate_video_thumbnail(full_path, hash_img_pth)
thumbnl = self.create_scaled_image(hash_img_pth, self.VIDEO_ICON_WH) thumbnl = self.create_scaled_image(hash_img_pth, self.VIDEO_ICON_WH)
if thumbnl == None: # If no icon whatsoever, return internal default if thumbnl == None: # If no icon whatsoever, return internal default
thumbnl = GdkPixbuf.Pixbuf.new_from_file(self.DEFAULT_ICONS + "/video.png") thumbnl = GdkPixbuf.Pixbuf.new_from_file(f"{self.DEFAULT_ICONS}/video.png")
return thumbnl return thumbnl
except Exception as e: except Exception as e:
print("Thumbnail generation issue:") print("Thumbnail generation issue:")
print( repr(e) ) print( repr(e) )
return GdkPixbuf.Pixbuf.new_from_file(self.DEFAULT_ICONS + "/video.png") return GdkPixbuf.Pixbuf.new_from_file(f"{self.DEFAULT_ICONS}/video.png")
def create_scaled_image(self, path, wxh): def create_scaled_image(self, path, wxh):
try: try:
pixbuf = GdkPixbuf.Pixbuf.new_from_file(path) if path.lower().endswith(".gif"):
scaled_pixbuf = pixbuf.scale_simple(wxh[0], wxh[1], 2) # 2 = BILINEAR and is best by default return GdkPixbuf.PixbufAnimation.new_from_file(path) \
return scaled_pixbuf .get_static_image() \
.scale_simple(wxh[0], wxh[1], GdkPixbuf.InterpType.BILINEAR)
else:
return GdkPixbuf.Pixbuf.new_from_file_at_scale(path, wxh[0], wxh[1], True)
except Exception as e: except Exception as e:
print("Image Scaling Issue:") print("Image Scaling Issue:")
print( repr(e) ) print( repr(e) )

View File

@@ -59,7 +59,7 @@ class Settings:
subpath = settings["base_of_home"] subpath = settings["base_of_home"]
HIDE_HIDDEN_FILES = True if settings["hide_hidden_files"] == "true" else False HIDE_HIDDEN_FILES = True if settings["hide_hidden_files"] == "true" else False
FFMPG_THUMBNLR = FFMPG_THUMBNLR if settings["thumbnailer_path"] == "" else settings["thumbnailer_path"] FFMPG_THUMBNLR = FFMPG_THUMBNLR if settings["thumbnailer_path"] == "" else settings["thumbnailer_path"]
go_past_home = True if settings["go_past_home"] == "true" else False go_past_home = True if settings["go_past_home"] == "" else settings["go_past_home"]
lock_folder = True if settings["lock_folder"] == "true" else False lock_folder = True if settings["lock_folder"] == "true" else False
locked_folders = settings["locked_folders"].split("::::") locked_folders = settings["locked_folders"].split("::::")
mplayer_options = settings["mplayer_options"].split() mplayer_options = settings["mplayer_options"].split()

View File

@@ -1,9 +0,0 @@
"""
Gtk Bound Signal Module
"""
from .mixins import *
from .DBusControllerMixin import DBusControllerMixin
from .KeyboardSignalsMixin import KeyboardSignalsMixin
from .ShowHideMixin import ShowHideMixin
from .Controller_Data import Controller_Data
from .Controller import Controller

View File

@@ -5,8 +5,8 @@ import os, logging
class Logger: class Logger:
def __init__(self): def __init__(self, config_path):
pass self._CONFIG_PATH = config_path
def get_logger(self, loggerName = "NO_LOGGER_NAME_PASSED", createFile = True): def get_logger(self, loggerName = "NO_LOGGER_NAME_PASSED", createFile = True):
""" """
@@ -42,8 +42,8 @@ class Logger:
log.addHandler(ch) log.addHandler(ch)
if createFile: if createFile:
folder = "logs" folder = self._CONFIG_PATH
file = folder + "/application.log" file = f"{folder}/application.log"
if not os.path.exists(folder): if not os.path.exists(folder):
os.mkdir(folder) os.mkdir(folder)

View File

@@ -17,65 +17,68 @@ from . import Logger
class Settings: class Settings:
def __init__(self): def __init__(self):
self.logger = Logger().get_logger()
self.builder = gtk.Builder() self.builder = gtk.Builder()
self.SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__)) self.SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__))
self.USER_HOME = path.expanduser('~') self.USER_HOME = path.expanduser('~')
self.CONFIG_PATH = f"{self.USER_HOME}/.config/solarfm" self.CONFIG_PATH = f"{self.USER_HOME}/.config/{app_name.lower()}"
self.USR_SOLARFM = "/usr/share/solarfm" 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.CSS_FILE = f"{self.CONFIG_PATH}/stylesheet.css"
self.windows_glade = f"{self.CONFIG_PATH}/Main_Window.glade" self.WINDOWS_GLADE = f"{self.CONFIG_PATH}/Main_Window.glade"
self.DEFAULT_ICONS = f"{self.CONFIG_PATH}/icons" self.DEFAULT_ICONS = f"{self.CONFIG_PATH}/icons"
self.window_icon = f"{self.DEFAULT_ICONS}/solarfm.png" self.WINDOW_ICON = f"{self.DEFAULT_ICONS}/{app_name.lower()}.png"
self.main_window = None self.main_window = None
if not os.path.exists(self.windows_glade): if not os.path.exists(self.CONFIG_PATH):
self.windows_glade = f"{self.USR_SOLARFM}/Main_Window.glade" os.mkdir(self.CONFIG_PATH)
if not os.path.exists(self.cssFile): if not os.path.exists(self.PLUGINS_PATH):
self.cssFile = f"{self.USR_SOLARFM}/stylesheet.css" os.mkdir(self.PLUGINS_PATH)
if not os.path.exists(self.window_icon):
self.window_icon = f"{self.USR_SOLARFM}/icons/solarfm.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): if not os.path.exists(self.DEFAULT_ICONS):
self.DEFAULT_ICONS = f"{self.USR_SOLARFM}/icons" self.DEFAULT_ICONS = f"{self.USR_SOLARFM}/icons"
self.builder.add_from_file(self.windows_glade) self.logger = Logger(self.CONFIG_PATH).get_logger()
self.builder.add_from_file(self.WINDOWS_GLADE)
def createWindow(self): def create_window(self):
# Get window and connect signals # Get window and connect signals
self.main_window = self.builder.get_object("Main_Window") self.main_window = self.builder.get_object("Main_Window")
self.setWindowData() self._set_window_data()
def setWindowData(self): 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() screen = self.main_window.get_screen()
visual = screen.get_rgba_visual() visual = screen.get_rgba_visual()
if visual != None and screen.is_composited(): if visual != None and screen.is_composited():
self.main_window.set_visual(visual) self.main_window.set_visual(visual)
self.main_window.set_app_paintable(True) self.main_window.set_app_paintable(True)
self.main_window.connect("draw", self.area_draw) self.main_window.connect("draw", self._area_draw)
# bind css file # bind css file
cssProvider = gtk.CssProvider() cssProvider = gtk.CssProvider()
cssProvider.load_from_path(self.cssFile) cssProvider.load_from_path(self.CSS_FILE)
screen = gdk.Screen.get_default() screen = gdk.Screen.get_default()
styleContext = gtk.StyleContext() styleContext = gtk.StyleContext()
styleContext.add_provider_for_screen(screen, cssProvider, gtk.STYLE_PROVIDER_PRIORITY_USER) styleContext.add_provider_for_screen(screen, cssProvider, gtk.STYLE_PROVIDER_PRIORITY_USER)
def area_draw(self, widget, cr): def _area_draw(self, widget, cr):
cr.set_source_rgba(0, 0, 0, 0.54) cr.set_source_rgba(0, 0, 0, 0.54)
cr.set_operator(cairo.OPERATOR_SOURCE) cr.set_operator(cairo.OPERATOR_SOURCE)
cr.paint() cr.paint()
cr.set_operator(cairo.OPERATOR_OVER) cr.set_operator(cairo.OPERATOR_OVER)
def getMainWindow(self): return self.main_window def get_monitor_data(self):
def getMonitorData(self):
screen = self.builder.get_object("Main_Window").get_screen() screen = self.builder.get_object("Main_Window").get_screen()
monitors = [] monitors = []
for m in range(screen.get_n_monitors()): for m in range(screen.get_n_monitors()):
@@ -85,3 +88,8 @@ class Settings:
print("{}x{}+{}+{}".format(monitor.width, monitor.height, monitor.x, monitor.y)) print("{}x{}+{}+{}".format(monitor.width, monitor.height, monitor.x, monitor.y))
return monitors return monitors
def get_builder(self): return self.builder
def get_logger(self): return self.logger
def get_main_window(self): return self.main_window
def get_plugins_path(self): return self.PLUGINS_PATH

View File

@@ -644,10 +644,36 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="layout-style">end</property> <property name="layout-style">end</property>
<child> <child>
<placeholder/> <object class="GtkButton" id="button10">
<property name="label" translatable="yes">Create</property>
<property name="name">create</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Create File/Folder...</property>
<property name="image">createImage</property>
<property name="always-show-image">True</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child> </child>
<child> <child>
<placeholder/> <object class="GtkButton" id="button9">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="use-stock">True</property>
<property name="always-show-image">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child> </child>
</object> </object>
<packing> <packing>
@@ -723,7 +749,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">False</property>
<property name="position">0</property> <property name="position">0</property>
</packing> </packing>
</child> </child>
@@ -740,40 +766,26 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
<property name="position">1</property> <property name="position">1</property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Create</property>
<property name="name">create</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Create File/Folder...</property>
<property name="image">createImage</property>
<property name="always-show-image">True</property>
<signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object> </object>
<packing> <packing>
<property name="expand">True</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">1</property> <property name="position">1</property>
</packing> </packing>
</child> </child>
</object> </object>
<packing> <packing>
<property name="expand">True</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">1</property> <property name="position">1</property>
</packing> </packing>
</child> </child>
</object> </object>
</child> </child>
<action-widgets>
<action-widget response="-10">button10</action-widget>
<action-widget response="-6">button9</action-widget>
</action-widgets>
</object> </object>
<object class="GtkImage" id="exec_in_term_img"> <object class="GtkImage" id="exec_in_term_img">
<property name="visible">True</property> <property name="visible">True</property>
@@ -891,6 +903,103 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
<object class="GtkBox"> <object class="GtkBox">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="margin-top">15</property>
<property name="margin-bottom">10</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Moving From:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="exists_file_from">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="exists_file_diff_from">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-top">20</property>
<property name="margin-bottom">10</property>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">Moving To:</property>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="exists_file_to">
<property name="visible">True</property>
<property name="can-focus">False</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="exists_file_diff_to">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-top">20</property>
<child> <child>
<object class="GtkLabel"> <object class="GtkLabel">
<property name="visible">True</property> <property name="visible">True</property>
@@ -918,12 +1027,9 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">1</property> <property name="position">5</property>
</packing> </packing>
</child> </child>
<child>
<placeholder/>
</child>
<child> <child>
<object class="GtkEntry" id="exists_file_field"> <object class="GtkEntry" id="exists_file_field">
<property name="visible">True</property> <property name="visible">True</property>
@@ -933,7 +1039,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">3</property> <property name="position">6</property>
</packing> </packing>
</child> </child>
<child> <child>
@@ -996,7 +1102,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">4</property> <property name="position">7</property>
</packing> </packing>
</child> </child>
</object> </object>
@@ -1024,60 +1130,6 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
<property name="stock">gtk-justify-center</property> <property name="stock">gtk-justify-center</property>
</object> </object>
<object class="GtkTextBuffer" id="message_buffer"/> <object class="GtkTextBuffer" id="message_buffer"/>
<object class="GtkPopover" id="message_widget">
<property name="width-request">320</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<property name="position">bottom</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkButton">
<property name="label">gtk-save-as</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="use-stock">True</property>
<signal name="button-release-event" handler="save_debug_alerts" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="height-request">600</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="hexpand">True</property>
<property name="shadow-type">in</property>
<property name="overlay-scrolling">False</property>
<child>
<object class="GtkTextView" id="message_view">
<property name="name">message_view</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="hexpand">True</property>
<property name="editable">False</property>
<property name="cursor-visible">False</property>
<property name="buffer">message_buffer</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkImage" id="open_with_img"> <object class="GtkImage" id="open_with_img">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
@@ -1291,7 +1343,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<child> <child>
<object class="GtkMenuBar"> <object class="GtkMenuBar" id="menubar1">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<child> <child>
@@ -1307,19 +1359,25 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
<child> <child>
<object class="GtkImageMenuItem"> <object class="GtkImageMenuItem">
<property name="label">gtk-new</property> <property name="label">gtk-new</property>
<property name="name">create</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">New File/Folder...</property>
<property name="use-underline">True</property> <property name="use-underline">True</property>
<property name="use-stock">True</property> <property name="use-stock">True</property>
<signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
</object> </object>
</child> </child>
<child> <child>
<object class="GtkImageMenuItem"> <object class="GtkImageMenuItem">
<property name="label">gtk-open</property> <property name="label">gtk-open</property>
<property name="name">open</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="tooltip-text" translatable="yes">Open...</property>
<property name="use-underline">True</property> <property name="use-underline">True</property>
<property name="use-stock">True</property> <property name="use-stock">True</property>
<signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
</object> </object>
</child> </child>
<child> <child>
@@ -1897,6 +1955,61 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
</object> </object>
</child> </child>
</object> </object>
<object class="GtkPopover" id="message_widget">
<property name="width-request">320</property>
<property name="can-focus">False</property>
<property name="hexpand">True</property>
<property name="relative-to">top_main_menubar</property>
<property name="position">bottom</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkButton">
<property name="label">gtk-save-as</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="use-stock">True</property>
<signal name="button-release-event" handler="save_debug_alerts" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkScrolledWindow">
<property name="height-request">600</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="hexpand">True</property>
<property name="shadow-type">in</property>
<property name="overlay-scrolling">False</property>
<child>
<object class="GtkTextView" id="message_view">
<property name="name">message_view</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="hexpand">True</property>
<property name="editable">False</property>
<property name="cursor-visible">False</property>
<property name="buffer">message_buffer</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkPopover" id="path_menu"> <object class="GtkPopover" id="path_menu">
<property name="width-request">240</property> <property name="width-request">240</property>
<property name="height-request">420</property> <property name="height-request">420</property>
@@ -2068,25 +2181,6 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<child type="center">
<object class="GtkButton">
<property name="label">gtk-delete</property>
<property name="name">delete</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Delete...</property>
<property name="margin-top">20</property>
<property name="use-stock">True</property>
<property name="always-show-image">True</property>
<signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">11</property>
</packing>
</child>
<child> <child>
<object class="GtkButton"> <object class="GtkButton">
<property name="label">gtk-open</property> <property name="label">gtk-open</property>
@@ -2226,25 +2320,6 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
<property name="position">7</property> <property name="position">7</property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Go To Trash</property>
<property name="name">go_to_trash</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Go To Trash...</property>
<property name="image">trash_img2</property>
<property name="always-show-image">True</property>
<signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="pack-type">end</property>
<property name="position">8</property>
</packing>
</child>
<child> <child>
<object class="GtkButton"> <object class="GtkButton">
<property name="label" translatable="yes">Archive</property> <property name="label" translatable="yes">Archive</property>
@@ -2257,12 +2332,65 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
<property name="always-show-image">True</property> <property name="always-show-image">True</property>
<signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/> <signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
</object> </object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">8</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label">gtk-delete</property>
<property name="name">delete</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Delete...</property>
<property name="margin-top">20</property>
<property name="use-stock">True</property>
<property name="always-show-image">True</property>
<signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
</object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">9</property> <property name="position">9</property>
</packing> </packing>
</child> </child>
<child>
<object class="GtkButton" id="restore_from_trash">
<property name="label" translatable="yes">Restore From Trash</property>
<property name="name">restore_from_trash</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Restore From Trash...</property>
<property name="margin-top">20</property>
<signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">10</property>
</packing>
</child>
<child>
<object class="GtkButton" id="empty_trash">
<property name="label" translatable="yes">Empty Trash</property>
<property name="name">empty_trash</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Empty Trash...</property>
<property name="margin-bottom">20</property>
<signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">11</property>
</packing>
</child>
<child> <child>
<object class="GtkButton"> <object class="GtkButton">
<property name="label" translatable="yes">Trash</property> <property name="label" translatable="yes">Trash</property>
@@ -2278,8 +2406,25 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="pack-type">end</property> <property name="position">12</property>
<property name="position">11</property> </packing>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Go To Trash</property>
<property name="name">go_to_trash</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Go To Trash...</property>
<property name="image">trash_img2</property>
<property name="always-show-image">True</property>
<signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">13</property>
</packing> </packing>
</child> </child>
</object> </object>