Compare commits

1 Commits

Author SHA1 Message Date
09c5af3821 Gtk4 convert work. 2021-12-31 18:38:22 -06:00
65 changed files with 419 additions and 894 deletions

View File

@@ -14,6 +14,7 @@ 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

Binary file not shown.

View File

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

@@ -1,37 +0,0 @@
# 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 Normal file → Executable file
View File

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

View File

@@ -4,12 +4,12 @@ import builtins
# Lib imports # Lib imports
# Application imports # Application imports
from signal_classes import IPCServerMixin from signal_classes.DBusControllerMixin import DBusControllerMixin
class Builtins(IPCServerMixin): class Builtins(DBusControllerMixin):
"""Docstring for __builtins__ extender""" """Docstring for __builtins__ extender"""
def __init__(self): def __init__(self):
@@ -18,11 +18,6 @@ class Builtins(IPCServerMixin):
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):
@@ -66,8 +61,6 @@ class Builtins(IPCServerMixin):
# 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.2 builtins.event_sleep_time = 0.5
builtins.debug = False builtins.debug = False
builtins.trace_debug = False

View File

@@ -13,27 +13,24 @@ from __builtins__ import Builtins
class Main(Builtins): class Main(Builtins):
def __init__(self, args, unknownargs): def __init__(self, args, unknownargs):
if not debug: event_system.create_ipc_server()
event_system.create_ipc_server() time.sleep(0.5)
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)
time.sleep(0.2) if args.new_tab and os.path.isdir(args.new_tab):
if not trace_debug: message = f"FILE|{args.new_tab}"
if not event_system.is_ipc_alive: event_system.send_ipc_message(message)
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): raise Exception("IPC Server Exists: Will send path(s) to it and close...")
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.create_window() settings.createWindow()
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,9 +24,7 @@ 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 f"/{'/'.join(self.path)}" if self.path else f"/{''.join(self.path)}" return "/" + "/".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):
try: if len(self.path) > 1:
self.path.pop() self.path.pop()
if not self.go_past_home: if not self.go_past_home:
@@ -29,8 +29,6 @@ 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 = f"{self.get_home()}/" home = 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 = f"{dir}/{file}" full_path = 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,32 +36,29 @@ class Icon(DesktopIconMixin, VideoIconMixin):
return None return None
def create_thumbnail(self, dir, file): def create_thumbnail(self, dir, file):
full_path = f"{dir}/{file}" full_path = 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 = f"{self.ABS_THUMBS_PTH}/{file_hash}.jpg" hash_img_pth = 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(f"{self.DEFAULT_ICONS}/video.png") thumbnl = GdkPixbuf.Pixbuf.new_from_file(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(f"{self.DEFAULT_ICONS}/video.png") return GdkPixbuf.Pixbuf.new_from_file(self.DEFAULT_ICONS + "/video.png")
def create_scaled_image(self, path, wxh): def create_scaled_image(self, path, wxh):
try: try:
if path.lower().endswith(".gif"): pixbuf = GdkPixbuf.Pixbuf.new_from_file(path)
return GdkPixbuf.PixbufAnimation.new_from_file(path) \ scaled_pixbuf = pixbuf.scale_simple(wxh[0], wxh[1], 2) # 2 = BILINEAR and is best by default
.get_static_image() \ return scaled_pixbuf
.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"] == "" else settings["go_past_home"] go_past_home = True if settings["go_past_home"] == "true" else False
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, inspect, os, time import sys, traceback, threading, signal, inspect, os, time
# Lib imports # Lib imports
import gi import gi
@@ -7,9 +7,8 @@ gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib from gi.repository import Gtk, GLib
# Application imports # Application imports
from .mixins.ui import * from .mixins import *
from .mixins import ShowHideMixin, KeyboardSignalsMixin from . import ShowHideMixin, KeyboardSignalsMixin, Controller_Data
from . import Controller_Data
def threaded(fn): def threaded(fn):
@@ -24,23 +23,25 @@ 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.setup_controller_data(_settings) self.settings = _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()
if not trace_debug: self.window.connect("delete-event", self.tear_down)
self.gui_event_observer() GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.tear_down)
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,5 +1,4 @@
# Python imports # Python imports
import signal
# Lib imports # Lib imports
from gi.repository import GLib from gi.repository import GLib
@@ -7,7 +6,6 @@ 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
@@ -16,18 +14,16 @@ 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, _settings): def setup_controller_data(self):
self.trashman = XDGTrash()
self.window_controller = WindowController() self.window_controller = WindowController()
self.plugins = Plugins(_settings) self.trashman = XDGTrash()
self.state = self.window_controller.load_state()
self.trashman.regenerate() self.trashman.regenerate()
self.settings = _settings self.state = self.window_controller.load_state()
self.builder = self.settings.get_builder() self.builder = self.settings.builder
self.logger = self.settings.get_logger() self.logger = self.settings.logger
self.window = self.settings.get_main_window() self.window = self.settings.getMainWindow()
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")
@@ -89,10 +85,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.override_drop_dest = None self.is_searching = False
self.is_searching = False self.search_iconview = None
self.search_iconview = None self.search_view = None
self.search_view = None
self.skip_edit = False self.skip_edit = False
self.cancel_edit = False self.cancel_edit = False
@@ -103,7 +99,3 @@ 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 IPCServerMixin: class DBusControllerMixin:
@threaded @threaded
def create_ipc_server(self): def create_ipc_server(self):
listener = Listener((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey) listener = Listener(('127.0.0.1', 4848), authkey=b'solarfm-ipc')
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 IPCServerMixin:
# 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) > self.ipc_timeout: if (end - start) > 15.0:
conn.close() conn.close()
listener.close() listener.close()
@@ -57,7 +57,7 @@ class IPCServerMixin:
def send_ipc_message(self, message="Empty Data..."): def send_ipc_message(self, message="Empty Data..."):
try: try:
conn = Client((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey) conn = Client(('127.0.0.1', 4848), authkey=b'solarfm-ipc')
conn.send(message) conn.send(message)
conn.send('close connection') conn.send('close connection')
except Exception as e: except Exception as e:

View File

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

View File

@@ -2,7 +2,8 @@
Gtk Bound Signal Module Gtk Bound Signal Module
""" """
from .mixins import * from .mixins import *
from .IPCServerMixin import IPCServerMixin from .DBusControllerMixin import DBusControllerMixin
from .Plugins import Plugins from .KeyboardSignalsMixin import KeyboardSignalsMixin
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

@@ -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) uris = self.format_to_uris(store, wid, tid, self.selected_files, True)
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, time import os, threading, subprocess
# Lib imports # Lib imports
import gi import gi
@@ -20,13 +20,15 @@ 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([None, file[0]]) store.append([icon, 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
@@ -48,14 +50,10 @@ class WidgetMixin:
try: try:
itr = store.get_iter(i) itr = store.get_iter(i)
except Exception as e: except Exception as e:
try: print(":Invalid Itr detected: (Potential race condition...)")
time.sleep(0.2) print(f"Index Requested: {i}")
itr = store.get_iter(i) print(f"Store Size: {len(store)}")
except Exception as e: return
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])
@@ -115,7 +113,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 or GdkPixbuf.PixbufAnimation or None, str) store = Gtk.ListStore(GdkPixbuf.Pixbuf, str)
grid.set_model(store) grid.set_model(store)
grid.set_pixbuf_column(0) grid.set_pixbuf_column(0)
@@ -133,14 +131,11 @@ 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 ]
@@ -159,8 +154,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 or GdkPixbuf.PixbufAnimation or None, str) store = Gtk.ListStore(GdkPixbuf.Pixbuf, str)
# store = Gtk.TreeStore(GdkPixbuf.Pixbuf or None, str) # store = Gtk.TreeStore(GdkPixbuf.Pixbuf, 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,16 +101,11 @@ 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:
try: file_info = Gio.File.new_for_path(uri).query_info(attributes="standard::size",
file_info = Gio.File.new_for_path(uri).query_info(attributes="standard::size", flags=Gio.FileQueryInfoFlags.NONE,
flags=Gio.FileQueryInfoFlags.NONE, cancellable=None)
cancellable=None) file_size = file_info.get_size()
file_size = file_info.get_size() combined_size += file_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:
@@ -157,9 +152,6 @@ 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()
@@ -221,19 +213,8 @@ 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):
current = '|'.join(self.window_controller.get_active_data()) wid, tid = iconview.get_name().split("|")
target = iconview.get_name() self.window_controller.set_active_data(wid, tid)
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:
@@ -243,14 +224,12 @@ 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()}" if not self.override_drop_dest else self.override_drop_dest dest = f"{view.get_current_directory()}"
if len(uris) == 0: if len(uris) > 0:
uris = data.get_text().split("\n") self.move_files(uris, dest)
else:
from_uri = '/'.join(uris[0].replace("file://", "").split("/")[:-1]) uris = data.get_text().split("\n")
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

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

View File

View File

View File

View File

@@ -5,8 +5,8 @@ import os, logging
class Logger: class Logger:
def __init__(self, config_path): def __init__(self):
self._CONFIG_PATH = config_path pass
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 = self._CONFIG_PATH folder = "logs"
file = f"{folder}/application.log" file = folder + "/application.log"
if not os.path.exists(folder): if not os.path.exists(folder):
os.mkdir(folder) os.mkdir(folder)

View File

@@ -17,45 +17,39 @@ 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/{app_name.lower()}" self.CONFIG_PATH = f"{self.USER_HOME}/.config/solarfm"
self.PLUGINS_PATH = f"{self.CONFIG_PATH}/plugins" self.USR_SOLARFM = "/usr/share/solarfm"
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}/{app_name.lower()}.png" self.window_icon = f"{self.DEFAULT_ICONS}/solarfm.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/{app_name.lower()}.png" self.window_icon = f"{self.USR_SOLARFM}/icons/solarfm.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 create_window(self): def createWindow(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._set_window_data() self.setWindowData()
def _set_window_data(self): def setWindowData(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()
@@ -63,7 +57,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()
@@ -72,13 +66,16 @@ 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 get_monitor_data(self): def getMainWindow(self): return self.main_window
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()):
@@ -88,8 +85,3 @@ 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,28 +4,20 @@ import builtins
# Lib imports # Lib imports
# Application imports # Application imports
from controller import IPCServerMixin from signal_classes.DBusControllerMixin import DBusControllerMixin
class Builtins(IPCServerMixin): class Builtins(DBusControllerMixin):
"""Docstring for __builtins__ extender""" """Docstring for __builtins__ extender"""
def __init__(self): def __init__(self):
# NOTE: The format used is list of [type, target, data] Where: # NOTE: The format used is list of [type, target, data]
# 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):
@@ -69,8 +61,6 @@ class Builtins(IPCServerMixin):
# 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.2 builtins.event_sleep_time = 0.5
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 controller import Controller from signal_classes import Controller
from __builtins__ import Builtins from __builtins__ import Builtins
@@ -13,27 +13,24 @@ from __builtins__ import Builtins
class Main(Builtins): class Main(Builtins):
def __init__(self, args, unknownargs): def __init__(self, args, unknownargs):
if not debug: event_system.create_ipc_server()
event_system.create_ipc_server() time.sleep(0.5)
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)
time.sleep(0.2) if args.new_tab and os.path.isdir(args.new_tab):
if not trace_debug: message = f"FILE|{args.new_tab}"
if not event_system.is_ipc_alive: event_system.send_ipc_message(message)
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): raise Exception("IPC Server Exists: Will send path(s) to it and close...")
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.create_window() settings.createWindow()
controller = Controller(args, unknownargs, settings) controller = Controller(args, unknownargs, settings)
if not controller: if not controller:

View File

@@ -11,7 +11,7 @@ tracemalloc.start()
# Lib imports # Lib imports
import gi import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '4.0')
from gi.repository import Gtk from gi.repository import Gtk
# Application imports # Application imports
@@ -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,7 +0,0 @@
"""
Gtk Bound Signal Module
"""
from .mixins import *
from .IPCServerMixin import IPCServerMixin
from .Controller_Data import Controller_Data
from .Controller import Controller

View File

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

View File

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

View File

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

View File

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

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

View File

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

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

View File

@@ -24,9 +24,7 @@ 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 f"/{'/'.join(self.path)}" if self.path else f"/{''.join(self.path)}" return "/" + "/".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):
try: if len(self.path) > 1:
self.path.pop() self.path.pop()
if not self.go_past_home: if not self.go_past_home:
@@ -29,8 +29,6 @@ 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 = f"{self.get_home()}/" home = 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 = f"{dir}/{file}" full_path = 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,32 +36,29 @@ class Icon(DesktopIconMixin, VideoIconMixin):
return None return None
def create_thumbnail(self, dir, file): def create_thumbnail(self, dir, file):
full_path = f"{dir}/{file}" full_path = 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 = f"{self.ABS_THUMBS_PTH}/{file_hash}.jpg" hash_img_pth = 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(f"{self.DEFAULT_ICONS}/video.png") thumbnl = GdkPixbuf.Pixbuf.new_from_file(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(f"{self.DEFAULT_ICONS}/video.png") return GdkPixbuf.Pixbuf.new_from_file(self.DEFAULT_ICONS + "/video.png")
def create_scaled_image(self, path, wxh): def create_scaled_image(self, path, wxh):
try: try:
if path.lower().endswith(".gif"): pixbuf = GdkPixbuf.Pixbuf.new_from_file(path)
return GdkPixbuf.PixbufAnimation.new_from_file(path) \ scaled_pixbuf = pixbuf.scale_simple(wxh[0], wxh[1], 2) # 2 = BILINEAR and is best by default
.get_static_image() \ return scaled_pixbuf
.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

@@ -4,7 +4,7 @@ from os.path import isfile
# Gtk imports # Gtk imports
import gi import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '4.0')
from gi.repository import Gtk from gi.repository import Gtk
# Application imports # Application imports
@@ -21,7 +21,7 @@ class DesktopIconMixin:
if "steam" in icon: if "steam" in icon:
name = xdgObj.getName() name = xdgObj.getName()
file_hash = hashlib.sha256(str.encode(name)).hexdigest() file_hash = hashlib.sha256(str.encode(name)).hexdigest()
hash_img_pth = self.STEAM_ICONS_PTH + "/" + file_hash + ".jpg" hash_img_pth = f"{self.STEAM_ICONS_PTH}/{file_hash}.jpg"
if isfile(hash_img_pth) == True: if isfile(hash_img_pth) == True:
# Use video sizes since headers are bigger # Use video sizes since headers are bigger
@@ -30,7 +30,7 @@ class DesktopIconMixin:
exec_str = xdgObj.getExec() exec_str = xdgObj.getExec()
parts = exec_str.split("steam://rungameid/") parts = exec_str.split("steam://rungameid/")
id = parts[len(parts) - 1] id = parts[len(parts) - 1]
imageLink = self.STEAM_BASE_URL + id + "/header.jpg" imageLink = f"{self.STEAM_BASE_URL}{id}/header.jpg"
proc = subprocess.Popen(["wget", "-O", hash_img_pth, imageLink]) proc = subprocess.Popen(["wget", "-O", hash_img_pth, imageLink])
proc.wait() proc.wait()
@@ -57,9 +57,9 @@ class DesktopIconMixin:
for (dirpath, dirnames, filenames) in os.walk(path): for (dirpath, dirnames, filenames) in os.walk(path):
for file in filenames: for file in filenames:
appNM = "application-x-" + icon appNM = f"application-x-{icon}"
if icon in file or appNM in file: if icon in file or appNM in file:
alt_icon_path = dirpath + "/" + file alt_icon_path = "{dirpath}/{file}"
break break
return alt_icon_path return alt_icon_path

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"] == "" else settings["go_past_home"] go_past_home = True if settings["go_past_home"] == "true" else False
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,15 +1,14 @@
# Python imports # Python imports
import sys, traceback, threading, inspect, os, time import sys, traceback, threading, signal, inspect, os, time
# Lib imports # Lib imports
import gi import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '4.0')
from gi.repository import Gtk, GLib from gi.repository import Gtk, GLib
# Application imports # Application imports
from .mixins import UIMixin from .mixins import *
from .signals import IPCSignalsMixin, KeyboardSignalsMixin from . import ShowHideMixin, KeyboardSignalsMixin, Controller_Data
from . import Controller_Data
def threaded(fn): def threaded(fn):
@@ -20,26 +19,29 @@ def threaded(fn):
class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, Controller_Data): class Controller(WidgetFileActionMixin, PaneMixin, WindowMixin, ShowHideMixin, \
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.setup_controller_data(_settings) self.settings = _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()
if not trace_debug: self.window.connect("delete-event", self.tear_down)
self.gui_event_observer() GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.tear_down)
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):
@@ -57,8 +59,8 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, Controller_Data
if event: if event:
try: try:
type, target, data = event type, target, data = event
method = getattr(self.__class__, target) method = getattr(self.__class__, type)
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))
@@ -156,7 +158,9 @@ class Controller(UIMixin, KeyboardSignalsMixin, IPCSignalsMixin, Controller_Data
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,13 +1,11 @@
# 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 trasher.xdgtrash import XDGTrash
from shellfm import WindowController from shellfm import WindowController
from plugins import Plugins from trasher.xdgtrash import XDGTrash
@@ -16,18 +14,16 @@ 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, _settings): def setup_controller_data(self):
self.trashman = XDGTrash()
self.window_controller = WindowController() self.window_controller = WindowController()
self.plugins = Plugins(_settings) self.trashman = XDGTrash()
self.state = self.window_controller.load_state()
self.trashman.regenerate() self.trashman.regenerate()
self.settings = _settings self.state = self.window_controller.load_state()
self.builder = self.settings.get_builder() self.builder = self.settings.builder
self.logger = self.settings.get_logger() self.logger = self.settings.logger
self.window = self.settings.get_main_window() self.window = self.settings.getMainWindow()
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")
@@ -89,10 +85,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.override_drop_dest = None self.is_searching = False
self.is_searching = False self.search_iconview = None
self.search_iconview = None self.search_view = None
self.search_view = None
self.skip_edit = False self.skip_edit = False
self.cancel_edit = False self.cancel_edit = False
@@ -103,7 +99,3 @@ 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 IPCServerMixin: class DBusControllerMixin:
@threaded @threaded
def create_ipc_server(self): def create_ipc_server(self):
listener = Listener((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey) listener = Listener(('127.0.0.1', 4848), authkey=b'solarfm-ipc')
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 IPCServerMixin:
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([None, "handle_file_from_ipc", file]) event_system.push_gui_event(["create_tab_from_ipc", None, file])
conn.close() conn.close()
break break
@@ -49,7 +49,7 @@ class IPCServerMixin:
# 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) > self.ipc_timeout: if (end - start) > 15.0:
conn.close() conn.close()
listener.close() listener.close()
@@ -57,7 +57,7 @@ class IPCServerMixin:
def send_ipc_message(self, message="Empty Data..."): def send_ipc_message(self, message="Empty Data..."):
try: try:
conn = Client((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey) conn = Client(('127.0.0.1', 4848), authkey=b'solarfm-ipc')
conn.send(message) conn.send(message)
conn.send('close connection') conn.send('close connection')
except Exception as e: except Exception as e:

View File

@@ -3,8 +3,8 @@ import re
# Lib imports # Lib imports
import gi import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '4.0')
gi.require_version('Gdk', '3.0') gi.require_version('Gdk', '4.0')
from gi.repository import Gtk, Gdk from gi.repository import Gtk, Gdk
# Application imports # Application imports

View File

@@ -2,8 +2,8 @@
# Gtk imports # Gtk imports
import gi import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '4.0')
gi.require_version('Gdk', '3.0') gi.require_version('Gdk', '4.0')
from gi.repository import Gtk, Gdk, Gio from gi.repository import Gtk, Gdk, Gio
# Application imports # Application imports
@@ -16,6 +16,7 @@ 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()
@@ -80,14 +81,12 @@ 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()
@@ -104,18 +103,12 @@ 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("context_menu_fname").set_text("") self.builder.get_object("new_file_menu").run()
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,9 @@
"""
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

@@ -3,8 +3,8 @@ import os
# Lib imports # Lib imports
import gi import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '4.0')
gi.require_version('Gdk', '3.0') gi.require_version('Gdk', '4.0')
from gi.repository import Gtk, Gdk from gi.repository import Gtk, Gdk
# Application imports # Application imports
@@ -16,6 +16,24 @@ 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

@@ -3,8 +3,8 @@ import os
# Lib imports # Lib imports
import gi import gi
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '4.0')
from gi.repository import Gtk, GObject, GLib, Gio from gi.repository import Gtk, GObject, 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) uris = self.format_to_uris(store, wid, tid, self.selected_files, True)
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")
self.hide_new_file_menu() fname_field.set_text("")
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 which is more # NOTE: Gtk recommends using fail flow than pre check existence 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,7 +273,8 @@ 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.setup_exists_data(file, _file) self.exists_file_label.set_label(_file.get_basename())
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":
@@ -343,45 +344,6 @@ 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,11 +1,11 @@
# Python imports # Python imports
import os, threading, subprocess, time import os, threading, subprocess
# Lib imports # Lib imports
import gi import gi
gi.require_version("Gtk", "3.0") gi.require_version("Gtk", "4.0")
gi.require_version('Gdk', '3.0') gi.require_version('Gdk', '4.0')
from gi.repository import Gtk, Gdk, GLib, Gio, GdkPixbuf from gi.repository import Gtk, Gdk, GLib, Gio, GdkPixbuf
# Application imports # Application imports
@@ -20,13 +20,15 @@ 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([None, file[0]]) store.append([icon, 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
@@ -48,14 +50,10 @@ class WidgetMixin:
try: try:
itr = store.get_iter(i) itr = store.get_iter(i)
except Exception as e: except Exception as e:
try: print(":Invalid Itr detected: (Potential race condition...)")
time.sleep(0.2) print(f"Index Requested: {i}")
itr = store.get_iter(i) print(f"Store Size: {len(store)}")
except Exception as e: return
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])
@@ -115,7 +113,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 or GdkPixbuf.PixbufAnimation or None, str) store = Gtk.ListStore(GdkPixbuf.Pixbuf, str)
grid.set_model(store) grid.set_model(store)
grid.set_pixbuf_column(0) grid.set_pixbuf_column(0)
@@ -133,14 +131,11 @@ 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 ]
@@ -159,8 +154,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 or GdkPixbuf.PixbufAnimation or None, str) store = Gtk.ListStore(GdkPixbuf.Pixbuf, str)
# store = Gtk.TreeStore(GdkPixbuf.Pixbuf or None, str) # store = Gtk.TreeStore(GdkPixbuf.Pixbuf, str)
column = Gtk.TreeViewColumn("Icons") column = Gtk.TreeViewColumn("Icons")
icon = Gtk.CellRendererPixbuf() icon = Gtk.CellRendererPixbuf()
name = Gtk.CellRendererText() name = Gtk.CellRendererText()

View File

@@ -5,7 +5,7 @@ from os.path import isdir, isfile
# Lib imports # Lib imports
import gi import gi
gi.require_version('Gdk', '3.0') gi.require_version('Gdk', '4.0')
from gi.repository import Gdk, Gio from gi.repository import Gdk, Gio
# Application imports # Application imports
@@ -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,16 +101,11 @@ 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:
try: file_info = Gio.File.new_for_path(uri).query_info(attributes="standard::size",
file_info = Gio.File.new_for_path(uri).query_info(attributes="standard::size", flags=Gio.FileQueryInfoFlags.NONE,
flags=Gio.FileQueryInfoFlags.NONE, cancellable=None)
cancellable=None) file_size = file_info.get_size()
file_size = file_info.get_size() combined_size += file_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:
@@ -157,9 +152,6 @@ 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()
@@ -221,19 +213,8 @@ 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):
current = '|'.join(self.window_controller.get_active_data()) wid, tid = iconview.get_name().split("|")
target = iconview.get_name() self.window_controller.set_active_data(wid, tid)
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:
@@ -243,14 +224,12 @@ 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()}" if not self.override_drop_dest else self.override_drop_dest dest = f"{view.get_current_directory()}"
if len(uris) == 0: if len(uris) > 0:
uris = data.get_text().split("\n") self.move_files(uris, dest)
else:
from_uri = '/'.join(uris[0].replace("file://", "").split("/")[:-1]) uris = data.get_text().split("\n")
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)

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

View File

@@ -5,8 +5,8 @@ import os, logging
class Logger: class Logger:
def __init__(self, config_path): def __init__(self):
self._CONFIG_PATH = config_path pass
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 = self._CONFIG_PATH folder = "logs"
file = f"{folder}/application.log" file = folder + "/application.log"
if not os.path.exists(folder): if not os.path.exists(folder):
os.mkdir(folder) os.mkdir(folder)

View File

@@ -4,8 +4,8 @@ from os import path
# Gtk imports # Gtk imports
import gi, cairo import gi, cairo
gi.require_version('Gtk', '3.0') gi.require_version('Gtk', '4.0')
gi.require_version('Gdk', '3.0') gi.require_version('Gdk', '4.0')
from gi.repository import Gtk as gtk from gi.repository import Gtk as gtk
from gi.repository import Gdk as gdk from gi.repository import Gdk as gdk
@@ -17,68 +17,65 @@ 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/{app_name.lower()}" self.CONFIG_PATH = f"{self.USER_HOME}/.config/solarfm"
self.PLUGINS_PATH = f"{self.CONFIG_PATH}/plugins" self.USR_SOLARFM = "/usr/share/solarfm"
self.USR_SOLARFM = f"/usr/share/{app_name.lower()}"
self.CSS_FILE = 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}/{app_name.lower()}.png" self.window_icon = f"{self.DEFAULT_ICONS}/solarfm.png"
self.main_window = None self.main_window = None
if not os.path.exists(self.CONFIG_PATH): if not os.path.exists(self.windows_glade):
os.mkdir(self.CONFIG_PATH) self.windows_glade = f"{self.USR_SOLARFM}/Main_Window.glade"
if not os.path.exists(self.PLUGINS_PATH): if not os.path.exists(self.cssFile):
os.mkdir(self.PLUGINS_PATH) self.cssFile = f"{self.USR_SOLARFM}/stylesheet.css"
if not os.path.exists(self.window_icon):
if not os.path.exists(self.WINDOWS_GLADE): self.window_icon = f"{self.USR_SOLARFM}/icons/solarfm.png"
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.logger = Logger(self.CONFIG_PATH).get_logger() self.builder.add_from_file(self.windows_glade)
self.builder.add_from_file(self.WINDOWS_GLADE)
def create_window(self): def createWindow(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._set_window_data() self.setWindowData()
def _set_window_data(self): def setWindowData(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.CSS_FILE) cssProvider.load_from_path(self.cssFile)
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 get_monitor_data(self): def getMainWindow(self): return self.main_window
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()):
@@ -88,8 +85,3 @@ 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

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 --> <!-- Generated with glade 3.38.2 -->
<interface> <interface>
<requires lib="gtk+" version="3.22"/> <requires lib="gtk+" version="3.24"/>
<object class="GtkAboutDialog" id="about_page"> <object class="GtkAboutDialog" id="about_page">
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="border-width">5</property> <property name="border-width">5</property>
@@ -644,36 +644,10 @@ 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>
<object class="GtkButton" id="button10"> <placeholder/>
<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>
<object class="GtkButton" id="button9"> <placeholder/>
<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>
@@ -749,7 +723,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">False</property> <property name="fill">True</property>
<property name="position">0</property> <property name="position">0</property>
</packing> </packing>
</child> </child>
@@ -766,26 +740,40 @@ 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">False</property> <property name="expand">True</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">False</property> <property name="expand">True</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>
@@ -903,103 +891,6 @@ 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>
@@ -1027,9 +918,12 @@ 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">5</property> <property name="position">1</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>
@@ -1039,7 +933,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">6</property> <property name="position">3</property>
</packing> </packing>
</child> </child>
<child> <child>
@@ -1102,7 +996,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">7</property> <property name="position">4</property>
</packing> </packing>
</child> </child>
</object> </object>
@@ -1130,6 +1024,60 @@ 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>
@@ -1343,7 +1291,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" id="menubar1"> <object class="GtkMenuBar">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<child> <child>
@@ -1359,25 +1307,19 @@ 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>
@@ -1955,61 +1897,6 @@ 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>
@@ -2181,6 +2068,25 @@ 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>
@@ -2320,6 +2226,25 @@ 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>
@@ -2332,65 +2257,12 @@ 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>
@@ -2406,25 +2278,8 @@ 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">12</property> <property name="pack-type">end</property>
</packing> <property name="position">11</property>
</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>