develop #1
|
@ -14,7 +14,6 @@ sudo apt-get install python3.8 wget python3-setproctitle python3-gi ffmpegthumbn
|
||||||
# TODO
|
# TODO
|
||||||
<ul>
|
<ul>
|
||||||
<li>Add simpleish plugin system to run bash/python scripts.</li>
|
<li>Add simpleish plugin system to run bash/python scripts.</li>
|
||||||
<li>Add DnD context awareness for over folder drop.</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
# Images
|
# Images
|
||||||
|
|
Binary file not shown.
|
@ -4,12 +4,12 @@ import builtins
|
||||||
# Lib imports
|
# Lib imports
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from signal_classes.DBusControllerMixin import DBusControllerMixin
|
from signal_classes import IPCServerMixin
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Builtins(DBusControllerMixin):
|
class Builtins(IPCServerMixin):
|
||||||
"""Docstring for __builtins__ extender"""
|
"""Docstring for __builtins__ extender"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -18,6 +18,11 @@ class Builtins(DBusControllerMixin):
|
||||||
self._gui_events = []
|
self._gui_events = []
|
||||||
self._fm_events = []
|
self._fm_events = []
|
||||||
self.is_ipc_alive = False
|
self.is_ipc_alive = False
|
||||||
|
self.ipc_authkey = b'solarfm-ipc'
|
||||||
|
self.ipc_address = '127.0.0.1'
|
||||||
|
self.ipc_port = 4848
|
||||||
|
self.ipc_timeout = 15.0
|
||||||
|
|
||||||
|
|
||||||
# Makeshift fake "events" type system FIFO
|
# Makeshift fake "events" type system FIFO
|
||||||
def _pop_gui_event(self):
|
def _pop_gui_event(self):
|
||||||
|
@ -61,6 +66,8 @@ class Builtins(DBusControllerMixin):
|
||||||
|
|
||||||
# NOTE: Just reminding myself we can add to builtins two different ways...
|
# NOTE: Just reminding myself we can add to builtins two different ways...
|
||||||
# __builtins__.update({"event_system": Builtins()})
|
# __builtins__.update({"event_system": Builtins()})
|
||||||
|
builtins.app_name = "SolarFM"
|
||||||
builtins.event_system = Builtins()
|
builtins.event_system = Builtins()
|
||||||
builtins.event_sleep_time = 0.5
|
builtins.event_sleep_time = 0.2
|
||||||
builtins.debug = False
|
builtins.debug = False
|
||||||
|
builtins.trace_debug = False
|
||||||
|
|
|
@ -13,24 +13,27 @@ from __builtins__ import Builtins
|
||||||
|
|
||||||
class Main(Builtins):
|
class Main(Builtins):
|
||||||
def __init__(self, args, unknownargs):
|
def __init__(self, args, unknownargs):
|
||||||
event_system.create_ipc_server()
|
if not debug:
|
||||||
time.sleep(0.5)
|
event_system.create_ipc_server()
|
||||||
if not event_system.is_ipc_alive:
|
|
||||||
if unknownargs:
|
|
||||||
for arg in unknownargs:
|
|
||||||
if os.path.isdir(arg):
|
|
||||||
message = f"FILE|{arg}"
|
|
||||||
event_system.send_ipc_message(message)
|
|
||||||
|
|
||||||
if args.new_tab and os.path.isdir(args.new_tab):
|
time.sleep(0.2)
|
||||||
message = f"FILE|{args.new_tab}"
|
if not trace_debug:
|
||||||
event_system.send_ipc_message(message)
|
if not event_system.is_ipc_alive:
|
||||||
|
if unknownargs:
|
||||||
|
for arg in unknownargs:
|
||||||
|
if os.path.isdir(arg):
|
||||||
|
message = f"FILE|{arg}"
|
||||||
|
event_system.send_ipc_message(message)
|
||||||
|
|
||||||
raise Exception("IPC Server Exists: Will send path(s) to it and close...")
|
if args.new_tab and os.path.isdir(args.new_tab):
|
||||||
|
message = f"FILE|{args.new_tab}"
|
||||||
|
event_system.send_ipc_message(message)
|
||||||
|
|
||||||
|
raise Exception("IPC Server Exists: Will send path(s) to it and close...")
|
||||||
|
|
||||||
|
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
settings.createWindow()
|
settings.create_window()
|
||||||
|
|
||||||
controller = Controller(args, unknownargs, settings)
|
controller = Controller(args, unknownargs, settings)
|
||||||
if not controller:
|
if not controller:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -24,7 +24,9 @@ class WindowController:
|
||||||
self.active_window_id = ""
|
self.active_window_id = ""
|
||||||
self.active_tab_id = ""
|
self.active_tab_id = ""
|
||||||
self.windows = []
|
self.windows = []
|
||||||
self.fm_event_observer()
|
|
||||||
|
if not trace_debug:
|
||||||
|
self.fm_event_observer()
|
||||||
|
|
||||||
@threaded
|
@threaded
|
||||||
def fm_event_observer(self):
|
def fm_event_observer(self):
|
||||||
|
|
|
@ -11,7 +11,7 @@ class Path:
|
||||||
return os.path.expanduser("~") + self.subpath
|
return os.path.expanduser("~") + self.subpath
|
||||||
|
|
||||||
def get_path(self):
|
def get_path(self):
|
||||||
return "/" + "/".join(self.path)
|
return f"/{'/'.join(self.path)}" if self.path else f"/{''.join(self.path)}"
|
||||||
|
|
||||||
def get_path_list(self):
|
def get_path_list(self):
|
||||||
return self.path
|
return self.path
|
||||||
|
@ -21,7 +21,7 @@ class Path:
|
||||||
self.load_directory()
|
self.load_directory()
|
||||||
|
|
||||||
def pop_from_path(self):
|
def pop_from_path(self):
|
||||||
if len(self.path) > 1:
|
try:
|
||||||
self.path.pop()
|
self.path.pop()
|
||||||
|
|
||||||
if not self.go_past_home:
|
if not self.go_past_home:
|
||||||
|
@ -29,6 +29,8 @@ class Path:
|
||||||
self.set_to_home()
|
self.set_to_home()
|
||||||
|
|
||||||
self.load_directory()
|
self.load_directory()
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
def set_path(self, path):
|
def set_path(self, path):
|
||||||
if path == self.get_path():
|
if path == self.get_path():
|
||||||
|
|
|
@ -199,7 +199,7 @@ class View(Settings, FileHandler, Launcher, Icon, Path):
|
||||||
|
|
||||||
def get_current_sub_path(self):
|
def get_current_sub_path(self):
|
||||||
path = self.get_path()
|
path = self.get_path()
|
||||||
home = self.get_home() + "/"
|
home = f"{self.get_home()}/"
|
||||||
return path.replace(home, "")
|
return path.replace(home, "")
|
||||||
|
|
||||||
def get_end_of_path(self):
|
def get_end_of_path(self):
|
||||||
|
|
|
@ -17,7 +17,7 @@ def threaded(fn):
|
||||||
|
|
||||||
class Icon(DesktopIconMixin, VideoIconMixin):
|
class Icon(DesktopIconMixin, VideoIconMixin):
|
||||||
def create_icon(self, dir, file):
|
def create_icon(self, dir, file):
|
||||||
full_path = dir + "/" + file
|
full_path = f"{dir}/{file}"
|
||||||
return self.get_icon_image(dir, file, full_path)
|
return self.get_icon_image(dir, file, full_path)
|
||||||
|
|
||||||
def get_icon_image(self, dir, file, full_path):
|
def get_icon_image(self, dir, file, full_path):
|
||||||
|
@ -36,29 +36,32 @@ class Icon(DesktopIconMixin, VideoIconMixin):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def create_thumbnail(self, dir, file):
|
def create_thumbnail(self, dir, file):
|
||||||
full_path = dir + "/" + file
|
full_path = f"{dir}/{file}"
|
||||||
try:
|
try:
|
||||||
file_hash = hashlib.sha256(str.encode(full_path)).hexdigest()
|
file_hash = hashlib.sha256(str.encode(full_path)).hexdigest()
|
||||||
hash_img_pth = self.ABS_THUMBS_PTH + "/" + file_hash + ".jpg"
|
hash_img_pth = f"{self.ABS_THUMBS_PTH}/{file_hash}.jpg"
|
||||||
if isfile(hash_img_pth) == False:
|
if isfile(hash_img_pth) == False:
|
||||||
self.generate_video_thumbnail(full_path, hash_img_pth)
|
self.generate_video_thumbnail(full_path, hash_img_pth)
|
||||||
|
|
||||||
thumbnl = self.create_scaled_image(hash_img_pth, self.VIDEO_ICON_WH)
|
thumbnl = self.create_scaled_image(hash_img_pth, self.VIDEO_ICON_WH)
|
||||||
if thumbnl == None: # If no icon whatsoever, return internal default
|
if thumbnl == None: # If no icon whatsoever, return internal default
|
||||||
thumbnl = GdkPixbuf.Pixbuf.new_from_file(self.DEFAULT_ICONS + "/video.png")
|
thumbnl = GdkPixbuf.Pixbuf.new_from_file(f"{self.DEFAULT_ICONS}/video.png")
|
||||||
|
|
||||||
return thumbnl
|
return thumbnl
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Thumbnail generation issue:")
|
print("Thumbnail generation issue:")
|
||||||
print( repr(e) )
|
print( repr(e) )
|
||||||
return GdkPixbuf.Pixbuf.new_from_file(self.DEFAULT_ICONS + "/video.png")
|
return GdkPixbuf.Pixbuf.new_from_file(f"{self.DEFAULT_ICONS}/video.png")
|
||||||
|
|
||||||
|
|
||||||
def create_scaled_image(self, path, wxh):
|
def create_scaled_image(self, path, wxh):
|
||||||
try:
|
try:
|
||||||
pixbuf = GdkPixbuf.Pixbuf.new_from_file(path)
|
if path.lower().endswith(".gif"):
|
||||||
scaled_pixbuf = pixbuf.scale_simple(wxh[0], wxh[1], 2) # 2 = BILINEAR and is best by default
|
return GdkPixbuf.PixbufAnimation.new_from_file(path) \
|
||||||
return scaled_pixbuf
|
.get_static_image() \
|
||||||
|
.scale_simple(wxh[0], wxh[1], GdkPixbuf.InterpType.BILINEAR)
|
||||||
|
else:
|
||||||
|
return GdkPixbuf.Pixbuf.new_from_file_at_scale(path, wxh[0], wxh[1], True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Image Scaling Issue:")
|
print("Image Scaling Issue:")
|
||||||
print( repr(e) )
|
print( repr(e) )
|
||||||
|
|
|
@ -59,7 +59,7 @@ class Settings:
|
||||||
subpath = settings["base_of_home"]
|
subpath = settings["base_of_home"]
|
||||||
HIDE_HIDDEN_FILES = True if settings["hide_hidden_files"] == "true" else False
|
HIDE_HIDDEN_FILES = True if settings["hide_hidden_files"] == "true" else False
|
||||||
FFMPG_THUMBNLR = FFMPG_THUMBNLR if settings["thumbnailer_path"] == "" else settings["thumbnailer_path"]
|
FFMPG_THUMBNLR = FFMPG_THUMBNLR if settings["thumbnailer_path"] == "" else settings["thumbnailer_path"]
|
||||||
go_past_home = True if settings["go_past_home"] == "true" else False
|
go_past_home = True if settings["go_past_home"] == "" else settings["go_past_home"]
|
||||||
lock_folder = True if settings["lock_folder"] == "true" else False
|
lock_folder = True if settings["lock_folder"] == "true" else False
|
||||||
locked_folders = settings["locked_folders"].split("::::")
|
locked_folders = settings["locked_folders"].split("::::")
|
||||||
mplayer_options = settings["mplayer_options"].split()
|
mplayer_options = settings["mplayer_options"].split()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# Python imports
|
# Python imports
|
||||||
import sys, traceback, threading, signal, inspect, os, time
|
import sys, traceback, threading, inspect, os, time
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
import gi
|
import gi
|
||||||
|
@ -7,8 +7,9 @@ gi.require_version('Gtk', '3.0')
|
||||||
from gi.repository import Gtk, GLib
|
from gi.repository import Gtk, GLib
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from .mixins import *
|
from .mixins.ui import *
|
||||||
from . import ShowHideMixin, KeyboardSignalsMixin, Controller_Data
|
from .mixins import ShowHideMixin, KeyboardSignalsMixin
|
||||||
|
from . import Controller_Data
|
||||||
|
|
||||||
|
|
||||||
def threaded(fn):
|
def threaded(fn):
|
||||||
|
@ -23,25 +24,23 @@ class Controller(WidgetFileActionMixin, PaneMixin, WindowMixin, ShowHideMixin, \
|
||||||
KeyboardSignalsMixin, Controller_Data):
|
KeyboardSignalsMixin, Controller_Data):
|
||||||
def __init__(self, args, unknownargs, _settings):
|
def __init__(self, args, unknownargs, _settings):
|
||||||
# sys.excepthook = self.custom_except_hook
|
# sys.excepthook = self.custom_except_hook
|
||||||
self.settings = _settings
|
self.setup_controller_data(_settings)
|
||||||
self.setup_controller_data()
|
|
||||||
|
|
||||||
self.window.show()
|
self.window.show()
|
||||||
self.generate_windows(self.state)
|
self.generate_windows(self.state)
|
||||||
|
self.plugins.launch_plugins()
|
||||||
|
|
||||||
self.window.connect("delete-event", self.tear_down)
|
if not trace_debug:
|
||||||
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.tear_down)
|
self.gui_event_observer()
|
||||||
self.gui_event_observer()
|
|
||||||
|
|
||||||
if unknownargs:
|
if unknownargs:
|
||||||
for arg in unknownargs:
|
for arg in unknownargs:
|
||||||
if os.path.isdir(arg):
|
if os.path.isdir(arg):
|
||||||
message = f"FILE|{arg}"
|
message = f"FILE|{arg}"
|
||||||
event_system.send_ipc_message(message)
|
event_system.send_ipc_message(message)
|
||||||
|
|
||||||
if args.new_tab and os.path.isdir(args.new_tab):
|
if args.new_tab and os.path.isdir(args.new_tab):
|
||||||
message = f"FILE|{args.new_tab}"
|
message = f"FILE|{args.new_tab}"
|
||||||
event_system.send_ipc_message(message)
|
event_system.send_ipc_message(message)
|
||||||
|
|
||||||
|
|
||||||
def tear_down(self, widget=None, eve=None):
|
def tear_down(self, widget=None, eve=None):
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
# Python imports
|
# Python imports
|
||||||
|
import signal
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
from gi.repository import GLib
|
from gi.repository import GLib
|
||||||
|
@ -6,6 +7,7 @@ from gi.repository import GLib
|
||||||
# Application imports
|
# Application imports
|
||||||
from shellfm import WindowController
|
from shellfm import WindowController
|
||||||
from trasher.xdgtrash import XDGTrash
|
from trasher.xdgtrash import XDGTrash
|
||||||
|
from . import Plugins
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,16 +16,18 @@ class Controller_Data:
|
||||||
def has_method(self, o, name):
|
def has_method(self, o, name):
|
||||||
return callable(getattr(o, name, None))
|
return callable(getattr(o, name, None))
|
||||||
|
|
||||||
def setup_controller_data(self):
|
def setup_controller_data(self, _settings):
|
||||||
self.window_controller = WindowController()
|
|
||||||
self.trashman = XDGTrash()
|
self.trashman = XDGTrash()
|
||||||
|
self.window_controller = WindowController()
|
||||||
|
self.plugins = Plugins(_settings)
|
||||||
|
self.state = self.window_controller.load_state()
|
||||||
self.trashman.regenerate()
|
self.trashman.regenerate()
|
||||||
|
|
||||||
self.state = self.window_controller.load_state()
|
self.settings = _settings
|
||||||
self.builder = self.settings.builder
|
self.builder = self.settings.get_builder()
|
||||||
self.logger = self.settings.logger
|
self.logger = self.settings.get_logger()
|
||||||
|
|
||||||
self.window = self.settings.getMainWindow()
|
self.window = self.settings.get_main_window()
|
||||||
self.window1 = self.builder.get_object("window_1")
|
self.window1 = self.builder.get_object("window_1")
|
||||||
self.window2 = self.builder.get_object("window_2")
|
self.window2 = self.builder.get_object("window_2")
|
||||||
self.window3 = self.builder.get_object("window_3")
|
self.window3 = self.builder.get_object("window_3")
|
||||||
|
@ -85,10 +89,10 @@ class Controller_Data:
|
||||||
self.is_pane3_hidden = False
|
self.is_pane3_hidden = False
|
||||||
self.is_pane4_hidden = False
|
self.is_pane4_hidden = False
|
||||||
|
|
||||||
self.is_searching = False
|
self.override_drop_dest = None
|
||||||
self.search_iconview = None
|
self.is_searching = False
|
||||||
self.search_view = None
|
self.search_iconview = None
|
||||||
|
self.search_view = None
|
||||||
|
|
||||||
self.skip_edit = False
|
self.skip_edit = False
|
||||||
self.cancel_edit = False
|
self.cancel_edit = False
|
||||||
|
@ -99,3 +103,7 @@ class Controller_Data:
|
||||||
self.success = "#88cc27"
|
self.success = "#88cc27"
|
||||||
self.warning = "#ffa800"
|
self.warning = "#ffa800"
|
||||||
self.error = "#ff0000"
|
self.error = "#ff0000"
|
||||||
|
|
||||||
|
|
||||||
|
self.window.connect("delete-event", self.tear_down)
|
||||||
|
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.tear_down)
|
||||||
|
|
|
@ -15,11 +15,11 @@ def threaded(fn):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class DBusControllerMixin:
|
class IPCServerMixin:
|
||||||
|
|
||||||
@threaded
|
@threaded
|
||||||
def create_ipc_server(self):
|
def create_ipc_server(self):
|
||||||
listener = Listener(('127.0.0.1', 4848), authkey=b'solarfm-ipc')
|
listener = Listener((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey)
|
||||||
self.is_ipc_alive = True
|
self.is_ipc_alive = True
|
||||||
while True:
|
while True:
|
||||||
conn = listener.accept()
|
conn = listener.accept()
|
||||||
|
@ -49,7 +49,7 @@ class DBusControllerMixin:
|
||||||
|
|
||||||
# NOTE: Not perfect but insures we don't lockup the connection for too long.
|
# NOTE: Not perfect but insures we don't lockup the connection for too long.
|
||||||
end_time = time.time()
|
end_time = time.time()
|
||||||
if (end - start) > 15.0:
|
if (end - start) > self.ipc_timeout:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
listener.close()
|
listener.close()
|
||||||
|
@ -57,7 +57,7 @@ class DBusControllerMixin:
|
||||||
|
|
||||||
def send_ipc_message(self, message="Empty Data..."):
|
def send_ipc_message(self, message="Empty Data..."):
|
||||||
try:
|
try:
|
||||||
conn = Client(('127.0.0.1', 4848), authkey=b'solarfm-ipc')
|
conn = Client((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey)
|
||||||
conn.send(message)
|
conn.send(message)
|
||||||
conn.send('close connection')
|
conn.send('close connection')
|
||||||
except Exception as e:
|
except Exception as e:
|
|
@ -0,0 +1,41 @@
|
||||||
|
# Python imports
|
||||||
|
import importlib
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
import gi
|
||||||
|
gi.require_version('Gtk', '3.0')
|
||||||
|
from gi.repository import Gtk, Gio
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Plugins:
|
||||||
|
"""docstring for Plugins"""
|
||||||
|
def __init__(self, settings):
|
||||||
|
self._settings = settings
|
||||||
|
self._plugins_path = self._settings.get_plugins_path()
|
||||||
|
self._plugins_dir_watcher = None
|
||||||
|
self._socket = Gtk.Socket().new()
|
||||||
|
|
||||||
|
def launch_plugins(self):
|
||||||
|
self._set_plugins_watcher()
|
||||||
|
self.load_plugins()
|
||||||
|
|
||||||
|
def _set_plugins_watcher(self):
|
||||||
|
self._plugins_dir_watcher = Gio.File.new_for_path(self._plugins_path) \
|
||||||
|
.monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, Gio.Cancellable())
|
||||||
|
self._plugins_dir_watcher.connect("changed", self._on_plugins_changed, ())
|
||||||
|
|
||||||
|
def _on_plugins_changed(self, file_monitor, file, other_file=None, eve_type=None, data=None):
|
||||||
|
if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED,
|
||||||
|
Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN,
|
||||||
|
Gio.FileMonitorEvent.MOVED_OUT]:
|
||||||
|
self.load_plugins(file)
|
||||||
|
|
||||||
|
def load_plugins(self, file=None):
|
||||||
|
print(f"(Re)loading plugins...")
|
||||||
|
print(locals())
|
||||||
|
|
||||||
|
# importlib.reload(stl_utils)
|
|
@ -2,8 +2,7 @@
|
||||||
Gtk Bound Signal Module
|
Gtk Bound Signal Module
|
||||||
"""
|
"""
|
||||||
from .mixins import *
|
from .mixins import *
|
||||||
from .DBusControllerMixin import DBusControllerMixin
|
from .IPCServerMixin import IPCServerMixin
|
||||||
from .KeyboardSignalsMixin import KeyboardSignalsMixin
|
from .Plugins import Plugins
|
||||||
from .ShowHideMixin import ShowHideMixin
|
|
||||||
from .Controller_Data import Controller_Data
|
from .Controller_Data import Controller_Data
|
||||||
from .Controller import Controller
|
from .Controller import Controller
|
||||||
|
|
|
@ -1,5 +1,2 @@
|
||||||
from .PaneMixin import PaneMixin
|
from .KeyboardSignalsMixin import KeyboardSignalsMixin
|
||||||
from .WidgetMixin import WidgetMixin
|
from .ShowHideMixin import ShowHideMixin
|
||||||
from .TabMixin import TabMixin
|
|
||||||
from .WindowMixin import WindowMixin
|
|
||||||
from .WidgetFileActionMixin import WidgetFileActionMixin
|
|
||||||
|
|
|
@ -107,7 +107,7 @@ class WidgetFileActionMixin:
|
||||||
def open_with_files(self, appchooser_widget):
|
def open_with_files(self, appchooser_widget):
|
||||||
wid, tid, view, iconview, store = self.get_current_state()
|
wid, tid, view, iconview, store = self.get_current_state()
|
||||||
app_info = appchooser_widget.get_app_info()
|
app_info = appchooser_widget.get_app_info()
|
||||||
uris = self.format_to_uris(store, wid, tid, self.selected_files, True)
|
uris = self.format_to_uris(store, wid, tid, self.selected_files)
|
||||||
|
|
||||||
view.app_chooser_exec(app_info, uris)
|
view.app_chooser_exec(app_info, uris)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# Python imports
|
# Python imports
|
||||||
import os, threading, subprocess
|
import os, threading, subprocess, time
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
import gi
|
import gi
|
||||||
|
@ -20,15 +20,13 @@ def threaded(fn):
|
||||||
|
|
||||||
|
|
||||||
class WidgetMixin:
|
class WidgetMixin:
|
||||||
|
|
||||||
def load_store(self, view, store, save_state=False):
|
def load_store(self, view, store, save_state=False):
|
||||||
store.clear()
|
store.clear()
|
||||||
dir = view.get_current_directory()
|
dir = view.get_current_directory()
|
||||||
files = view.get_files()
|
files = view.get_files()
|
||||||
|
|
||||||
icon = GdkPixbuf.Pixbuf.new_from_file(view.DEFAULT_ICON)
|
|
||||||
for i, file in enumerate(files):
|
for i, file in enumerate(files):
|
||||||
store.append([icon, file[0]])
|
store.append([None, file[0]])
|
||||||
self.create_icon(i, view, store, dir, file[0])
|
self.create_icon(i, view, store, dir, file[0])
|
||||||
|
|
||||||
# NOTE: Not likely called often from here but it could be useful
|
# NOTE: Not likely called often from here but it could be useful
|
||||||
|
@ -50,10 +48,14 @@ class WidgetMixin:
|
||||||
try:
|
try:
|
||||||
itr = store.get_iter(i)
|
itr = store.get_iter(i)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(":Invalid Itr detected: (Potential race condition...)")
|
try:
|
||||||
print(f"Index Requested: {i}")
|
time.sleep(0.2)
|
||||||
print(f"Store Size: {len(store)}")
|
itr = store.get_iter(i)
|
||||||
return
|
except Exception as e:
|
||||||
|
print(":Invalid Itr detected: (Potential race condition...)")
|
||||||
|
print(f"Index Requested: {i}")
|
||||||
|
print(f"Store Size: {len(store)}")
|
||||||
|
return
|
||||||
|
|
||||||
if not icon:
|
if not icon:
|
||||||
icon = self.get_system_thumbnail(fpath, view.SYS_ICON_WH[0])
|
icon = self.get_system_thumbnail(fpath, view.SYS_ICON_WH[0])
|
||||||
|
@ -113,7 +115,7 @@ class WidgetMixin:
|
||||||
def create_grid_iconview_widget(self, view, wid):
|
def create_grid_iconview_widget(self, view, wid):
|
||||||
scroll = Gtk.ScrolledWindow()
|
scroll = Gtk.ScrolledWindow()
|
||||||
grid = Gtk.IconView()
|
grid = Gtk.IconView()
|
||||||
store = Gtk.ListStore(GdkPixbuf.Pixbuf, str)
|
store = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str)
|
||||||
|
|
||||||
grid.set_model(store)
|
grid.set_model(store)
|
||||||
grid.set_pixbuf_column(0)
|
grid.set_pixbuf_column(0)
|
||||||
|
@ -131,11 +133,14 @@ class WidgetMixin:
|
||||||
|
|
||||||
grid.connect("button_release_event", self.grid_icon_single_click)
|
grid.connect("button_release_event", self.grid_icon_single_click)
|
||||||
grid.connect("item-activated", self.grid_icon_double_click)
|
grid.connect("item-activated", self.grid_icon_double_click)
|
||||||
|
# grid.connect("toggle-cursor-item", self.grid_cursor_toggled)
|
||||||
|
# grid.connect("notify", self.grid_cursor_toggled)
|
||||||
grid.connect("selection-changed", self.grid_set_selected_items)
|
grid.connect("selection-changed", self.grid_set_selected_items)
|
||||||
grid.connect("drag-data-get", self.grid_on_drag_set)
|
grid.connect("drag-data-get", self.grid_on_drag_set)
|
||||||
grid.connect("drag-data-received", self.grid_on_drag_data_received)
|
grid.connect("drag-data-received", self.grid_on_drag_data_received)
|
||||||
grid.connect("drag-motion", self.grid_on_drag_motion)
|
grid.connect("drag-motion", self.grid_on_drag_motion)
|
||||||
|
|
||||||
|
|
||||||
URI_TARGET_TYPE = 80
|
URI_TARGET_TYPE = 80
|
||||||
uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE)
|
uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE)
|
||||||
targets = [ uri_target ]
|
targets = [ uri_target ]
|
||||||
|
@ -154,8 +159,8 @@ class WidgetMixin:
|
||||||
def create_grid_treeview_widget(self, view, wid):
|
def create_grid_treeview_widget(self, view, wid):
|
||||||
scroll = Gtk.ScrolledWindow()
|
scroll = Gtk.ScrolledWindow()
|
||||||
grid = Gtk.TreeView()
|
grid = Gtk.TreeView()
|
||||||
store = Gtk.ListStore(GdkPixbuf.Pixbuf, str)
|
store = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str)
|
||||||
# store = Gtk.TreeStore(GdkPixbuf.Pixbuf, str)
|
# store = Gtk.TreeStore(GdkPixbuf.Pixbuf or None, str)
|
||||||
column = Gtk.TreeViewColumn("Icons")
|
column = Gtk.TreeViewColumn("Icons")
|
||||||
icon = Gtk.CellRendererPixbuf()
|
icon = Gtk.CellRendererPixbuf()
|
||||||
name = Gtk.CellRendererText()
|
name = Gtk.CellRendererText()
|
|
@ -82,7 +82,7 @@ class WindowMixin(TabMixin):
|
||||||
_wid, _tid, _view, iconview, store = self.get_current_state()
|
_wid, _tid, _view, iconview, store = self.get_current_state()
|
||||||
selected_files = iconview.get_selected_items()
|
selected_files = iconview.get_selected_items()
|
||||||
current_directory = view.get_current_directory()
|
current_directory = view.get_current_directory()
|
||||||
path_file = Gio.File.new_for_path( current_directory)
|
path_file = Gio.File.new_for_path(current_directory)
|
||||||
mount_file = path_file.query_filesystem_info(attributes="filesystem::*", cancellable=None)
|
mount_file = path_file.query_filesystem_info(attributes="filesystem::*", cancellable=None)
|
||||||
formatted_mount_free = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::free")) )
|
formatted_mount_free = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::free")) )
|
||||||
formatted_mount_size = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::size")) )
|
formatted_mount_size = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::size")) )
|
||||||
|
@ -101,11 +101,16 @@ class WindowMixin(TabMixin):
|
||||||
uris = self.format_to_uris(store, _wid, _tid, selected_files, True)
|
uris = self.format_to_uris(store, _wid, _tid, selected_files, True)
|
||||||
combined_size = 0
|
combined_size = 0
|
||||||
for uri in uris:
|
for uri in uris:
|
||||||
file_info = Gio.File.new_for_path(uri).query_info(attributes="standard::size",
|
try:
|
||||||
flags=Gio.FileQueryInfoFlags.NONE,
|
file_info = Gio.File.new_for_path(uri).query_info(attributes="standard::size",
|
||||||
cancellable=None)
|
flags=Gio.FileQueryInfoFlags.NONE,
|
||||||
file_size = file_info.get_size()
|
cancellable=None)
|
||||||
combined_size += file_size
|
file_size = file_info.get_size()
|
||||||
|
combined_size += file_size
|
||||||
|
except Exception as e:
|
||||||
|
if debug:
|
||||||
|
print(repr(e))
|
||||||
|
|
||||||
|
|
||||||
formatted_size = self.sizeof_fmt(combined_size)
|
formatted_size = self.sizeof_fmt(combined_size)
|
||||||
if view.hide_hidden:
|
if view.hide_hidden:
|
||||||
|
@ -152,6 +157,9 @@ class WindowMixin(TabMixin):
|
||||||
def grid_set_selected_items(self, iconview):
|
def grid_set_selected_items(self, iconview):
|
||||||
self.selected_files = iconview.get_selected_items()
|
self.selected_files = iconview.get_selected_items()
|
||||||
|
|
||||||
|
def grid_cursor_toggled(self, iconview):
|
||||||
|
print("wat...")
|
||||||
|
|
||||||
def grid_icon_single_click(self, iconview, eve):
|
def grid_icon_single_click(self, iconview, eve):
|
||||||
try:
|
try:
|
||||||
self.path_menu.popdown()
|
self.path_menu.popdown()
|
||||||
|
@ -213,8 +221,19 @@ class WindowMixin(TabMixin):
|
||||||
data.set_text(uris_text, -1)
|
data.set_text(uris_text, -1)
|
||||||
|
|
||||||
def grid_on_drag_motion(self, iconview, drag_context, x, y, data):
|
def grid_on_drag_motion(self, iconview, drag_context, x, y, data):
|
||||||
wid, tid = iconview.get_name().split("|")
|
current = '|'.join(self.window_controller.get_active_data())
|
||||||
self.window_controller.set_active_data(wid, tid)
|
target = iconview.get_name()
|
||||||
|
wid, tid = target.split("|")
|
||||||
|
store = iconview.get_model()
|
||||||
|
treePath = iconview.get_drag_dest_item().path
|
||||||
|
|
||||||
|
if treePath:
|
||||||
|
uri = self.format_to_uris(store, wid, tid, treePath)[0].replace("file://", "")
|
||||||
|
self.override_drop_dest = uri if isdir(uri) else None
|
||||||
|
|
||||||
|
if target not in current:
|
||||||
|
self.window_controller.set_active_data(wid, tid)
|
||||||
|
|
||||||
|
|
||||||
def grid_on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
|
def grid_on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
|
||||||
if info == 80:
|
if info == 80:
|
||||||
|
@ -224,12 +243,14 @@ class WindowMixin(TabMixin):
|
||||||
view = self.get_fm_window(wid).get_view_by_id(tid)
|
view = self.get_fm_window(wid).get_view_by_id(tid)
|
||||||
|
|
||||||
uris = data.get_uris()
|
uris = data.get_uris()
|
||||||
dest = f"{view.get_current_directory()}"
|
dest = f"{view.get_current_directory()}" if not self.override_drop_dest else self.override_drop_dest
|
||||||
if len(uris) > 0:
|
if len(uris) == 0:
|
||||||
self.move_files(uris, dest)
|
|
||||||
else:
|
|
||||||
uris = data.get_text().split("\n")
|
uris = data.get_text().split("\n")
|
||||||
|
|
||||||
|
from_uri = '/'.join(uris[0].replace("file://", "").split("/")[:-1])
|
||||||
|
if from_uri != dest:
|
||||||
self.move_files(uris, dest)
|
self.move_files(uris, dest)
|
||||||
|
|
||||||
|
|
||||||
def create_new_view_notebook(self, widget=None, wid=None, path=None):
|
def create_new_view_notebook(self, widget=None, wid=None, path=None):
|
||||||
self.create_tab(wid, path)
|
self.create_tab(wid, path)
|
|
@ -0,0 +1,5 @@
|
||||||
|
from .PaneMixin import PaneMixin
|
||||||
|
from .WidgetMixin import WidgetMixin
|
||||||
|
from .TabMixin import TabMixin
|
||||||
|
from .WindowMixin import WindowMixin
|
||||||
|
from .WidgetFileActionMixin import WidgetFileActionMixin
|
|
@ -5,8 +5,8 @@ import os, logging
|
||||||
|
|
||||||
|
|
||||||
class Logger:
|
class Logger:
|
||||||
def __init__(self):
|
def __init__(self, config_path):
|
||||||
pass
|
self._CONFIG_PATH = config_path
|
||||||
|
|
||||||
def get_logger(self, loggerName = "NO_LOGGER_NAME_PASSED", createFile = True):
|
def get_logger(self, loggerName = "NO_LOGGER_NAME_PASSED", createFile = True):
|
||||||
"""
|
"""
|
||||||
|
@ -42,8 +42,8 @@ class Logger:
|
||||||
log.addHandler(ch)
|
log.addHandler(ch)
|
||||||
|
|
||||||
if createFile:
|
if createFile:
|
||||||
folder = "logs"
|
folder = self._CONFIG_PATH
|
||||||
file = folder + "/application.log"
|
file = f"{folder}/application.log"
|
||||||
|
|
||||||
if not os.path.exists(folder):
|
if not os.path.exists(folder):
|
||||||
os.mkdir(folder)
|
os.mkdir(folder)
|
||||||
|
|
|
@ -17,39 +17,45 @@ from . import Logger
|
||||||
|
|
||||||
class Settings:
|
class Settings:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.logger = Logger().get_logger()
|
|
||||||
self.builder = gtk.Builder()
|
self.builder = gtk.Builder()
|
||||||
|
|
||||||
self.SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__))
|
self.SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__))
|
||||||
self.USER_HOME = path.expanduser('~')
|
self.USER_HOME = path.expanduser('~')
|
||||||
self.CONFIG_PATH = f"{self.USER_HOME}/.config/solarfm"
|
self.CONFIG_PATH = f"{self.USER_HOME}/.config/{app_name.lower()}"
|
||||||
self.USR_SOLARFM = "/usr/share/solarfm"
|
self.PLUGINS_PATH = f"{self.CONFIG_PATH}/plugins"
|
||||||
|
self.USR_SOLARFM = f"/usr/share/{app_name.lower()}"
|
||||||
|
|
||||||
self.cssFile = f"{self.CONFIG_PATH}/stylesheet.css"
|
self.cssFile = f"{self.CONFIG_PATH}/stylesheet.css"
|
||||||
self.windows_glade = f"{self.CONFIG_PATH}/Main_Window.glade"
|
self.windows_glade = f"{self.CONFIG_PATH}/Main_Window.glade"
|
||||||
self.DEFAULT_ICONS = f"{self.CONFIG_PATH}/icons"
|
self.DEFAULT_ICONS = f"{self.CONFIG_PATH}/icons"
|
||||||
self.window_icon = f"{self.DEFAULT_ICONS}/solarfm.png"
|
self.window_icon = f"{self.DEFAULT_ICONS}/{app_name.lower()}.png"
|
||||||
self.main_window = None
|
self.main_window = None
|
||||||
|
|
||||||
|
if not os.path.exists(self.CONFIG_PATH):
|
||||||
|
os.mkdir(self.CONFIG_PATH)
|
||||||
|
if not os.path.exists(self.PLUGINS_PATH):
|
||||||
|
os.mkdir(self.PLUGINS_PATH)
|
||||||
|
|
||||||
if not os.path.exists(self.windows_glade):
|
if not os.path.exists(self.windows_glade):
|
||||||
self.windows_glade = f"{self.USR_SOLARFM}/Main_Window.glade"
|
self.windows_glade = f"{self.USR_SOLARFM}/Main_Window.glade"
|
||||||
if not os.path.exists(self.cssFile):
|
if not os.path.exists(self.cssFile):
|
||||||
self.cssFile = f"{self.USR_SOLARFM}/stylesheet.css"
|
self.cssFile = f"{self.USR_SOLARFM}/stylesheet.css"
|
||||||
if not os.path.exists(self.window_icon):
|
if not os.path.exists(self.window_icon):
|
||||||
self.window_icon = f"{self.USR_SOLARFM}/icons/solarfm.png"
|
self.window_icon = f"{self.USR_SOLARFM}/icons/{app_name.lower()}.png"
|
||||||
if not os.path.exists(self.DEFAULT_ICONS):
|
if not os.path.exists(self.DEFAULT_ICONS):
|
||||||
self.DEFAULT_ICONS = f"{self.USR_SOLARFM}/icons"
|
self.DEFAULT_ICONS = f"{self.USR_SOLARFM}/icons"
|
||||||
|
|
||||||
|
self.logger = Logger(self.CONFIG_PATH).get_logger()
|
||||||
self.builder.add_from_file(self.windows_glade)
|
self.builder.add_from_file(self.windows_glade)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def createWindow(self):
|
def create_window(self):
|
||||||
# Get window and connect signals
|
# Get window and connect signals
|
||||||
self.main_window = self.builder.get_object("Main_Window")
|
self.main_window = self.builder.get_object("Main_Window")
|
||||||
self.setWindowData()
|
self._set_window_data()
|
||||||
|
|
||||||
def setWindowData(self):
|
def _set_window_data(self):
|
||||||
self.main_window.set_icon_from_file(self.window_icon)
|
self.main_window.set_icon_from_file(self.window_icon)
|
||||||
screen = self.main_window.get_screen()
|
screen = self.main_window.get_screen()
|
||||||
visual = screen.get_rgba_visual()
|
visual = screen.get_rgba_visual()
|
||||||
|
@ -57,7 +63,7 @@ class Settings:
|
||||||
if visual != None and screen.is_composited():
|
if visual != None and screen.is_composited():
|
||||||
self.main_window.set_visual(visual)
|
self.main_window.set_visual(visual)
|
||||||
self.main_window.set_app_paintable(True)
|
self.main_window.set_app_paintable(True)
|
||||||
self.main_window.connect("draw", self.area_draw)
|
self.main_window.connect("draw", self._area_draw)
|
||||||
|
|
||||||
# bind css file
|
# bind css file
|
||||||
cssProvider = gtk.CssProvider()
|
cssProvider = gtk.CssProvider()
|
||||||
|
@ -66,16 +72,13 @@ class Settings:
|
||||||
styleContext = gtk.StyleContext()
|
styleContext = gtk.StyleContext()
|
||||||
styleContext.add_provider_for_screen(screen, cssProvider, gtk.STYLE_PROVIDER_PRIORITY_USER)
|
styleContext.add_provider_for_screen(screen, cssProvider, gtk.STYLE_PROVIDER_PRIORITY_USER)
|
||||||
|
|
||||||
def area_draw(self, widget, cr):
|
def _area_draw(self, widget, cr):
|
||||||
cr.set_source_rgba(0, 0, 0, 0.54)
|
cr.set_source_rgba(0, 0, 0, 0.54)
|
||||||
cr.set_operator(cairo.OPERATOR_SOURCE)
|
cr.set_operator(cairo.OPERATOR_SOURCE)
|
||||||
cr.paint()
|
cr.paint()
|
||||||
cr.set_operator(cairo.OPERATOR_OVER)
|
cr.set_operator(cairo.OPERATOR_OVER)
|
||||||
|
|
||||||
def getMainWindow(self): return self.main_window
|
def get_monitor_data(self):
|
||||||
|
|
||||||
|
|
||||||
def getMonitorData(self):
|
|
||||||
screen = self.builder.get_object("Main_Window").get_screen()
|
screen = self.builder.get_object("Main_Window").get_screen()
|
||||||
monitors = []
|
monitors = []
|
||||||
for m in range(screen.get_n_monitors()):
|
for m in range(screen.get_n_monitors()):
|
||||||
|
@ -85,3 +88,8 @@ class Settings:
|
||||||
print("{}x{}+{}+{}".format(monitor.width, monitor.height, monitor.x, monitor.y))
|
print("{}x{}+{}+{}".format(monitor.width, monitor.height, monitor.x, monitor.y))
|
||||||
|
|
||||||
return monitors
|
return monitors
|
||||||
|
|
||||||
|
def get_builder(self): return self.builder
|
||||||
|
def get_logger(self): return self.logger
|
||||||
|
def get_main_window(self): return self.main_window
|
||||||
|
def get_plugins_path(self): return self.PLUGINS_PATH
|
||||||
|
|
|
@ -4,12 +4,12 @@ import builtins
|
||||||
# Lib imports
|
# Lib imports
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from signal_classes.DBusControllerMixin import DBusControllerMixin
|
from signal_classes import IPCServerMixin
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Builtins(DBusControllerMixin):
|
class Builtins(IPCServerMixin):
|
||||||
"""Docstring for __builtins__ extender"""
|
"""Docstring for __builtins__ extender"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
|
@ -18,6 +18,11 @@ class Builtins(DBusControllerMixin):
|
||||||
self._gui_events = []
|
self._gui_events = []
|
||||||
self._fm_events = []
|
self._fm_events = []
|
||||||
self.is_ipc_alive = False
|
self.is_ipc_alive = False
|
||||||
|
self.ipc_authkey = b'solarfm-ipc'
|
||||||
|
self.ipc_address = '127.0.0.1'
|
||||||
|
self.ipc_port = 4848
|
||||||
|
self.ipc_timeout = 15.0
|
||||||
|
|
||||||
|
|
||||||
# Makeshift fake "events" type system FIFO
|
# Makeshift fake "events" type system FIFO
|
||||||
def _pop_gui_event(self):
|
def _pop_gui_event(self):
|
||||||
|
@ -61,6 +66,8 @@ class Builtins(DBusControllerMixin):
|
||||||
|
|
||||||
# NOTE: Just reminding myself we can add to builtins two different ways...
|
# NOTE: Just reminding myself we can add to builtins two different ways...
|
||||||
# __builtins__.update({"event_system": Builtins()})
|
# __builtins__.update({"event_system": Builtins()})
|
||||||
|
builtins.app_name = "SolarFM"
|
||||||
builtins.event_system = Builtins()
|
builtins.event_system = Builtins()
|
||||||
builtins.event_sleep_time = 0.5
|
builtins.event_sleep_time = 0.2
|
||||||
builtins.debug = False
|
builtins.debug = False
|
||||||
|
builtins.trace_debug = False
|
||||||
|
|
|
@ -13,24 +13,27 @@ from __builtins__ import Builtins
|
||||||
|
|
||||||
class Main(Builtins):
|
class Main(Builtins):
|
||||||
def __init__(self, args, unknownargs):
|
def __init__(self, args, unknownargs):
|
||||||
event_system.create_ipc_server()
|
if not debug:
|
||||||
time.sleep(0.5)
|
event_system.create_ipc_server()
|
||||||
if not event_system.is_ipc_alive:
|
|
||||||
if unknownargs:
|
|
||||||
for arg in unknownargs:
|
|
||||||
if os.path.isdir(arg):
|
|
||||||
message = f"FILE|{arg}"
|
|
||||||
event_system.send_ipc_message(message)
|
|
||||||
|
|
||||||
if args.new_tab and os.path.isdir(args.new_tab):
|
time.sleep(0.2)
|
||||||
message = f"FILE|{args.new_tab}"
|
if not trace_debug:
|
||||||
event_system.send_ipc_message(message)
|
if not event_system.is_ipc_alive:
|
||||||
|
if unknownargs:
|
||||||
|
for arg in unknownargs:
|
||||||
|
if os.path.isdir(arg):
|
||||||
|
message = f"FILE|{arg}"
|
||||||
|
event_system.send_ipc_message(message)
|
||||||
|
|
||||||
raise Exception("IPC Server Exists: Will send path(s) to it and close...")
|
if args.new_tab and os.path.isdir(args.new_tab):
|
||||||
|
message = f"FILE|{args.new_tab}"
|
||||||
|
event_system.send_ipc_message(message)
|
||||||
|
|
||||||
|
raise Exception("IPC Server Exists: Will send path(s) to it and close...")
|
||||||
|
|
||||||
|
|
||||||
settings = Settings()
|
settings = Settings()
|
||||||
settings.createWindow()
|
settings.create_window()
|
||||||
|
|
||||||
controller = Controller(args, unknownargs, settings)
|
controller = Controller(args, unknownargs, settings)
|
||||||
if not controller:
|
if not controller:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -24,7 +24,9 @@ class WindowController:
|
||||||
self.active_window_id = ""
|
self.active_window_id = ""
|
||||||
self.active_tab_id = ""
|
self.active_tab_id = ""
|
||||||
self.windows = []
|
self.windows = []
|
||||||
self.fm_event_observer()
|
|
||||||
|
if not trace_debug:
|
||||||
|
self.fm_event_observer()
|
||||||
|
|
||||||
@threaded
|
@threaded
|
||||||
def fm_event_observer(self):
|
def fm_event_observer(self):
|
||||||
|
|
|
@ -11,7 +11,7 @@ class Path:
|
||||||
return os.path.expanduser("~") + self.subpath
|
return os.path.expanduser("~") + self.subpath
|
||||||
|
|
||||||
def get_path(self):
|
def get_path(self):
|
||||||
return "/" + "/".join(self.path)
|
return f"/{'/'.join(self.path)}" if self.path else f"/{''.join(self.path)}"
|
||||||
|
|
||||||
def get_path_list(self):
|
def get_path_list(self):
|
||||||
return self.path
|
return self.path
|
||||||
|
@ -21,7 +21,7 @@ class Path:
|
||||||
self.load_directory()
|
self.load_directory()
|
||||||
|
|
||||||
def pop_from_path(self):
|
def pop_from_path(self):
|
||||||
if len(self.path) > 1:
|
try:
|
||||||
self.path.pop()
|
self.path.pop()
|
||||||
|
|
||||||
if not self.go_past_home:
|
if not self.go_past_home:
|
||||||
|
@ -29,6 +29,8 @@ class Path:
|
||||||
self.set_to_home()
|
self.set_to_home()
|
||||||
|
|
||||||
self.load_directory()
|
self.load_directory()
|
||||||
|
except Exception as e:
|
||||||
|
pass
|
||||||
|
|
||||||
def set_path(self, path):
|
def set_path(self, path):
|
||||||
if path == self.get_path():
|
if path == self.get_path():
|
||||||
|
|
|
@ -199,7 +199,7 @@ class View(Settings, FileHandler, Launcher, Icon, Path):
|
||||||
|
|
||||||
def get_current_sub_path(self):
|
def get_current_sub_path(self):
|
||||||
path = self.get_path()
|
path = self.get_path()
|
||||||
home = self.get_home() + "/"
|
home = f"{self.get_home()}/"
|
||||||
return path.replace(home, "")
|
return path.replace(home, "")
|
||||||
|
|
||||||
def get_end_of_path(self):
|
def get_end_of_path(self):
|
||||||
|
|
|
@ -17,7 +17,7 @@ def threaded(fn):
|
||||||
|
|
||||||
class Icon(DesktopIconMixin, VideoIconMixin):
|
class Icon(DesktopIconMixin, VideoIconMixin):
|
||||||
def create_icon(self, dir, file):
|
def create_icon(self, dir, file):
|
||||||
full_path = dir + "/" + file
|
full_path = f"{dir}/{file}"
|
||||||
return self.get_icon_image(dir, file, full_path)
|
return self.get_icon_image(dir, file, full_path)
|
||||||
|
|
||||||
def get_icon_image(self, dir, file, full_path):
|
def get_icon_image(self, dir, file, full_path):
|
||||||
|
@ -36,29 +36,32 @@ class Icon(DesktopIconMixin, VideoIconMixin):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def create_thumbnail(self, dir, file):
|
def create_thumbnail(self, dir, file):
|
||||||
full_path = dir + "/" + file
|
full_path = f"{dir}/{file}"
|
||||||
try:
|
try:
|
||||||
file_hash = hashlib.sha256(str.encode(full_path)).hexdigest()
|
file_hash = hashlib.sha256(str.encode(full_path)).hexdigest()
|
||||||
hash_img_pth = self.ABS_THUMBS_PTH + "/" + file_hash + ".jpg"
|
hash_img_pth = f"{self.ABS_THUMBS_PTH}/{file_hash}.jpg"
|
||||||
if isfile(hash_img_pth) == False:
|
if isfile(hash_img_pth) == False:
|
||||||
self.generate_video_thumbnail(full_path, hash_img_pth)
|
self.generate_video_thumbnail(full_path, hash_img_pth)
|
||||||
|
|
||||||
thumbnl = self.create_scaled_image(hash_img_pth, self.VIDEO_ICON_WH)
|
thumbnl = self.create_scaled_image(hash_img_pth, self.VIDEO_ICON_WH)
|
||||||
if thumbnl == None: # If no icon whatsoever, return internal default
|
if thumbnl == None: # If no icon whatsoever, return internal default
|
||||||
thumbnl = GdkPixbuf.Pixbuf.new_from_file(self.DEFAULT_ICONS + "/video.png")
|
thumbnl = GdkPixbuf.Pixbuf.new_from_file(f"{self.DEFAULT_ICONS}/video.png")
|
||||||
|
|
||||||
return thumbnl
|
return thumbnl
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Thumbnail generation issue:")
|
print("Thumbnail generation issue:")
|
||||||
print( repr(e) )
|
print( repr(e) )
|
||||||
return GdkPixbuf.Pixbuf.new_from_file(self.DEFAULT_ICONS + "/video.png")
|
return GdkPixbuf.Pixbuf.new_from_file(f"{self.DEFAULT_ICONS}/video.png")
|
||||||
|
|
||||||
|
|
||||||
def create_scaled_image(self, path, wxh):
|
def create_scaled_image(self, path, wxh):
|
||||||
try:
|
try:
|
||||||
pixbuf = GdkPixbuf.Pixbuf.new_from_file(path)
|
if path.lower().endswith(".gif"):
|
||||||
scaled_pixbuf = pixbuf.scale_simple(wxh[0], wxh[1], 2) # 2 = BILINEAR and is best by default
|
return GdkPixbuf.PixbufAnimation.new_from_file(path) \
|
||||||
return scaled_pixbuf
|
.get_static_image() \
|
||||||
|
.scale_simple(wxh[0], wxh[1], GdkPixbuf.InterpType.BILINEAR)
|
||||||
|
else:
|
||||||
|
return GdkPixbuf.Pixbuf.new_from_file_at_scale(path, wxh[0], wxh[1], True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Image Scaling Issue:")
|
print("Image Scaling Issue:")
|
||||||
print( repr(e) )
|
print( repr(e) )
|
||||||
|
|
|
@ -59,7 +59,7 @@ class Settings:
|
||||||
subpath = settings["base_of_home"]
|
subpath = settings["base_of_home"]
|
||||||
HIDE_HIDDEN_FILES = True if settings["hide_hidden_files"] == "true" else False
|
HIDE_HIDDEN_FILES = True if settings["hide_hidden_files"] == "true" else False
|
||||||
FFMPG_THUMBNLR = FFMPG_THUMBNLR if settings["thumbnailer_path"] == "" else settings["thumbnailer_path"]
|
FFMPG_THUMBNLR = FFMPG_THUMBNLR if settings["thumbnailer_path"] == "" else settings["thumbnailer_path"]
|
||||||
go_past_home = True if settings["go_past_home"] == "true" else False
|
go_past_home = True if settings["go_past_home"] == "" else settings["go_past_home"]
|
||||||
lock_folder = True if settings["lock_folder"] == "true" else False
|
lock_folder = True if settings["lock_folder"] == "true" else False
|
||||||
locked_folders = settings["locked_folders"].split("::::")
|
locked_folders = settings["locked_folders"].split("::::")
|
||||||
mplayer_options = settings["mplayer_options"].split()
|
mplayer_options = settings["mplayer_options"].split()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# Python imports
|
# Python imports
|
||||||
import sys, traceback, threading, signal, inspect, os, time
|
import sys, traceback, threading, inspect, os, time
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
import gi
|
import gi
|
||||||
|
@ -7,8 +7,9 @@ gi.require_version('Gtk', '3.0')
|
||||||
from gi.repository import Gtk, GLib
|
from gi.repository import Gtk, GLib
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from .mixins import *
|
from .mixins.ui import *
|
||||||
from . import ShowHideMixin, KeyboardSignalsMixin, Controller_Data
|
from .mixins import ShowHideMixin, KeyboardSignalsMixin
|
||||||
|
from . import Controller_Data
|
||||||
|
|
||||||
|
|
||||||
def threaded(fn):
|
def threaded(fn):
|
||||||
|
@ -23,25 +24,23 @@ class Controller(WidgetFileActionMixin, PaneMixin, WindowMixin, ShowHideMixin, \
|
||||||
KeyboardSignalsMixin, Controller_Data):
|
KeyboardSignalsMixin, Controller_Data):
|
||||||
def __init__(self, args, unknownargs, _settings):
|
def __init__(self, args, unknownargs, _settings):
|
||||||
# sys.excepthook = self.custom_except_hook
|
# sys.excepthook = self.custom_except_hook
|
||||||
self.settings = _settings
|
self.setup_controller_data(_settings)
|
||||||
self.setup_controller_data()
|
|
||||||
|
|
||||||
self.window.show()
|
self.window.show()
|
||||||
self.generate_windows(self.state)
|
self.generate_windows(self.state)
|
||||||
|
self.plugins.launch_plugins()
|
||||||
|
|
||||||
self.window.connect("delete-event", self.tear_down)
|
if not trace_debug:
|
||||||
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.tear_down)
|
self.gui_event_observer()
|
||||||
self.gui_event_observer()
|
|
||||||
|
|
||||||
if unknownargs:
|
if unknownargs:
|
||||||
for arg in unknownargs:
|
for arg in unknownargs:
|
||||||
if os.path.isdir(arg):
|
if os.path.isdir(arg):
|
||||||
message = f"FILE|{arg}"
|
message = f"FILE|{arg}"
|
||||||
event_system.send_ipc_message(message)
|
event_system.send_ipc_message(message)
|
||||||
|
|
||||||
if args.new_tab and os.path.isdir(args.new_tab):
|
if args.new_tab and os.path.isdir(args.new_tab):
|
||||||
message = f"FILE|{args.new_tab}"
|
message = f"FILE|{args.new_tab}"
|
||||||
event_system.send_ipc_message(message)
|
event_system.send_ipc_message(message)
|
||||||
|
|
||||||
|
|
||||||
def tear_down(self, widget=None, eve=None):
|
def tear_down(self, widget=None, eve=None):
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
# Python imports
|
# Python imports
|
||||||
|
import signal
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
from gi.repository import GLib
|
from gi.repository import GLib
|
||||||
|
@ -6,6 +7,7 @@ from gi.repository import GLib
|
||||||
# Application imports
|
# Application imports
|
||||||
from shellfm import WindowController
|
from shellfm import WindowController
|
||||||
from trasher.xdgtrash import XDGTrash
|
from trasher.xdgtrash import XDGTrash
|
||||||
|
from . import Plugins
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -14,16 +16,18 @@ class Controller_Data:
|
||||||
def has_method(self, o, name):
|
def has_method(self, o, name):
|
||||||
return callable(getattr(o, name, None))
|
return callable(getattr(o, name, None))
|
||||||
|
|
||||||
def setup_controller_data(self):
|
def setup_controller_data(self, _settings):
|
||||||
self.window_controller = WindowController()
|
|
||||||
self.trashman = XDGTrash()
|
self.trashman = XDGTrash()
|
||||||
|
self.window_controller = WindowController()
|
||||||
|
self.plugins = Plugins(_settings)
|
||||||
|
self.state = self.window_controller.load_state()
|
||||||
self.trashman.regenerate()
|
self.trashman.regenerate()
|
||||||
|
|
||||||
self.state = self.window_controller.load_state()
|
self.settings = _settings
|
||||||
self.builder = self.settings.builder
|
self.builder = self.settings.get_builder()
|
||||||
self.logger = self.settings.logger
|
self.logger = self.settings.get_logger()
|
||||||
|
|
||||||
self.window = self.settings.getMainWindow()
|
self.window = self.settings.get_main_window()
|
||||||
self.window1 = self.builder.get_object("window_1")
|
self.window1 = self.builder.get_object("window_1")
|
||||||
self.window2 = self.builder.get_object("window_2")
|
self.window2 = self.builder.get_object("window_2")
|
||||||
self.window3 = self.builder.get_object("window_3")
|
self.window3 = self.builder.get_object("window_3")
|
||||||
|
@ -85,10 +89,10 @@ class Controller_Data:
|
||||||
self.is_pane3_hidden = False
|
self.is_pane3_hidden = False
|
||||||
self.is_pane4_hidden = False
|
self.is_pane4_hidden = False
|
||||||
|
|
||||||
self.is_searching = False
|
self.override_drop_dest = None
|
||||||
self.search_iconview = None
|
self.is_searching = False
|
||||||
self.search_view = None
|
self.search_iconview = None
|
||||||
|
self.search_view = None
|
||||||
|
|
||||||
self.skip_edit = False
|
self.skip_edit = False
|
||||||
self.cancel_edit = False
|
self.cancel_edit = False
|
||||||
|
@ -99,3 +103,7 @@ class Controller_Data:
|
||||||
self.success = "#88cc27"
|
self.success = "#88cc27"
|
||||||
self.warning = "#ffa800"
|
self.warning = "#ffa800"
|
||||||
self.error = "#ff0000"
|
self.error = "#ff0000"
|
||||||
|
|
||||||
|
|
||||||
|
self.window.connect("delete-event", self.tear_down)
|
||||||
|
GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, self.tear_down)
|
||||||
|
|
|
@ -15,11 +15,11 @@ def threaded(fn):
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class DBusControllerMixin:
|
class IPCServerMixin:
|
||||||
|
|
||||||
@threaded
|
@threaded
|
||||||
def create_ipc_server(self):
|
def create_ipc_server(self):
|
||||||
listener = Listener(('127.0.0.1', 4848), authkey=b'solarfm-ipc')
|
listener = Listener((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey)
|
||||||
self.is_ipc_alive = True
|
self.is_ipc_alive = True
|
||||||
while True:
|
while True:
|
||||||
conn = listener.accept()
|
conn = listener.accept()
|
||||||
|
@ -49,7 +49,7 @@ class DBusControllerMixin:
|
||||||
|
|
||||||
# NOTE: Not perfect but insures we don't lockup the connection for too long.
|
# NOTE: Not perfect but insures we don't lockup the connection for too long.
|
||||||
end_time = time.time()
|
end_time = time.time()
|
||||||
if (end - start) > 15.0:
|
if (end - start) > self.ipc_timeout:
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
listener.close()
|
listener.close()
|
||||||
|
@ -57,7 +57,7 @@ class DBusControllerMixin:
|
||||||
|
|
||||||
def send_ipc_message(self, message="Empty Data..."):
|
def send_ipc_message(self, message="Empty Data..."):
|
||||||
try:
|
try:
|
||||||
conn = Client(('127.0.0.1', 4848), authkey=b'solarfm-ipc')
|
conn = Client((self.ipc_address, self.ipc_port), authkey=self.ipc_authkey)
|
||||||
conn.send(message)
|
conn.send(message)
|
||||||
conn.send('close connection')
|
conn.send('close connection')
|
||||||
except Exception as e:
|
except Exception as e:
|
|
@ -0,0 +1,41 @@
|
||||||
|
# Python imports
|
||||||
|
import importlib
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
import gi
|
||||||
|
gi.require_version('Gtk', '3.0')
|
||||||
|
from gi.repository import Gtk, Gio
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Plugins:
|
||||||
|
"""docstring for Plugins"""
|
||||||
|
def __init__(self, settings):
|
||||||
|
self._settings = settings
|
||||||
|
self._plugins_path = self._settings.get_plugins_path()
|
||||||
|
self._plugins_dir_watcher = None
|
||||||
|
self._socket = Gtk.Socket().new()
|
||||||
|
|
||||||
|
def launch_plugins(self):
|
||||||
|
self._set_plugins_watcher()
|
||||||
|
self.load_plugins()
|
||||||
|
|
||||||
|
def _set_plugins_watcher(self):
|
||||||
|
self._plugins_dir_watcher = Gio.File.new_for_path(self._plugins_path) \
|
||||||
|
.monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, Gio.Cancellable())
|
||||||
|
self._plugins_dir_watcher.connect("changed", self._on_plugins_changed, ())
|
||||||
|
|
||||||
|
def _on_plugins_changed(self, file_monitor, file, other_file=None, eve_type=None, data=None):
|
||||||
|
if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED,
|
||||||
|
Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN,
|
||||||
|
Gio.FileMonitorEvent.MOVED_OUT]:
|
||||||
|
self.load_plugins(file)
|
||||||
|
|
||||||
|
def load_plugins(self, file=None):
|
||||||
|
print(f"(Re)loading plugins...")
|
||||||
|
print(locals())
|
||||||
|
|
||||||
|
# importlib.reload(stl_utils)
|
|
@ -2,8 +2,7 @@
|
||||||
Gtk Bound Signal Module
|
Gtk Bound Signal Module
|
||||||
"""
|
"""
|
||||||
from .mixins import *
|
from .mixins import *
|
||||||
from .DBusControllerMixin import DBusControllerMixin
|
from .IPCServerMixin import IPCServerMixin
|
||||||
from .KeyboardSignalsMixin import KeyboardSignalsMixin
|
from .Plugins import Plugins
|
||||||
from .ShowHideMixin import ShowHideMixin
|
|
||||||
from .Controller_Data import Controller_Data
|
from .Controller_Data import Controller_Data
|
||||||
from .Controller import Controller
|
from .Controller import Controller
|
||||||
|
|
|
@ -1,5 +1,2 @@
|
||||||
from .PaneMixin import PaneMixin
|
from .KeyboardSignalsMixin import KeyboardSignalsMixin
|
||||||
from .WidgetMixin import WidgetMixin
|
from .ShowHideMixin import ShowHideMixin
|
||||||
from .TabMixin import TabMixin
|
|
||||||
from .WindowMixin import WindowMixin
|
|
||||||
from .WidgetFileActionMixin import WidgetFileActionMixin
|
|
||||||
|
|
|
@ -107,7 +107,7 @@ class WidgetFileActionMixin:
|
||||||
def open_with_files(self, appchooser_widget):
|
def open_with_files(self, appchooser_widget):
|
||||||
wid, tid, view, iconview, store = self.get_current_state()
|
wid, tid, view, iconview, store = self.get_current_state()
|
||||||
app_info = appchooser_widget.get_app_info()
|
app_info = appchooser_widget.get_app_info()
|
||||||
uris = self.format_to_uris(store, wid, tid, self.selected_files, True)
|
uris = self.format_to_uris(store, wid, tid, self.selected_files)
|
||||||
|
|
||||||
view.app_chooser_exec(app_info, uris)
|
view.app_chooser_exec(app_info, uris)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# Python imports
|
# Python imports
|
||||||
import os, threading, subprocess
|
import os, threading, subprocess, time
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
import gi
|
import gi
|
||||||
|
@ -20,15 +20,13 @@ def threaded(fn):
|
||||||
|
|
||||||
|
|
||||||
class WidgetMixin:
|
class WidgetMixin:
|
||||||
|
|
||||||
def load_store(self, view, store, save_state=False):
|
def load_store(self, view, store, save_state=False):
|
||||||
store.clear()
|
store.clear()
|
||||||
dir = view.get_current_directory()
|
dir = view.get_current_directory()
|
||||||
files = view.get_files()
|
files = view.get_files()
|
||||||
|
|
||||||
icon = GdkPixbuf.Pixbuf.new_from_file(view.DEFAULT_ICON)
|
|
||||||
for i, file in enumerate(files):
|
for i, file in enumerate(files):
|
||||||
store.append([icon, file[0]])
|
store.append([None, file[0]])
|
||||||
self.create_icon(i, view, store, dir, file[0])
|
self.create_icon(i, view, store, dir, file[0])
|
||||||
|
|
||||||
# NOTE: Not likely called often from here but it could be useful
|
# NOTE: Not likely called often from here but it could be useful
|
||||||
|
@ -50,10 +48,14 @@ class WidgetMixin:
|
||||||
try:
|
try:
|
||||||
itr = store.get_iter(i)
|
itr = store.get_iter(i)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(":Invalid Itr detected: (Potential race condition...)")
|
try:
|
||||||
print(f"Index Requested: {i}")
|
time.sleep(0.2)
|
||||||
print(f"Store Size: {len(store)}")
|
itr = store.get_iter(i)
|
||||||
return
|
except Exception as e:
|
||||||
|
print(":Invalid Itr detected: (Potential race condition...)")
|
||||||
|
print(f"Index Requested: {i}")
|
||||||
|
print(f"Store Size: {len(store)}")
|
||||||
|
return
|
||||||
|
|
||||||
if not icon:
|
if not icon:
|
||||||
icon = self.get_system_thumbnail(fpath, view.SYS_ICON_WH[0])
|
icon = self.get_system_thumbnail(fpath, view.SYS_ICON_WH[0])
|
||||||
|
@ -113,7 +115,7 @@ class WidgetMixin:
|
||||||
def create_grid_iconview_widget(self, view, wid):
|
def create_grid_iconview_widget(self, view, wid):
|
||||||
scroll = Gtk.ScrolledWindow()
|
scroll = Gtk.ScrolledWindow()
|
||||||
grid = Gtk.IconView()
|
grid = Gtk.IconView()
|
||||||
store = Gtk.ListStore(GdkPixbuf.Pixbuf, str)
|
store = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str)
|
||||||
|
|
||||||
grid.set_model(store)
|
grid.set_model(store)
|
||||||
grid.set_pixbuf_column(0)
|
grid.set_pixbuf_column(0)
|
||||||
|
@ -131,11 +133,14 @@ class WidgetMixin:
|
||||||
|
|
||||||
grid.connect("button_release_event", self.grid_icon_single_click)
|
grid.connect("button_release_event", self.grid_icon_single_click)
|
||||||
grid.connect("item-activated", self.grid_icon_double_click)
|
grid.connect("item-activated", self.grid_icon_double_click)
|
||||||
|
# grid.connect("toggle-cursor-item", self.grid_cursor_toggled)
|
||||||
|
# grid.connect("notify", self.grid_cursor_toggled)
|
||||||
grid.connect("selection-changed", self.grid_set_selected_items)
|
grid.connect("selection-changed", self.grid_set_selected_items)
|
||||||
grid.connect("drag-data-get", self.grid_on_drag_set)
|
grid.connect("drag-data-get", self.grid_on_drag_set)
|
||||||
grid.connect("drag-data-received", self.grid_on_drag_data_received)
|
grid.connect("drag-data-received", self.grid_on_drag_data_received)
|
||||||
grid.connect("drag-motion", self.grid_on_drag_motion)
|
grid.connect("drag-motion", self.grid_on_drag_motion)
|
||||||
|
|
||||||
|
|
||||||
URI_TARGET_TYPE = 80
|
URI_TARGET_TYPE = 80
|
||||||
uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE)
|
uri_target = Gtk.TargetEntry.new('text/uri-list', Gtk.TargetFlags(0), URI_TARGET_TYPE)
|
||||||
targets = [ uri_target ]
|
targets = [ uri_target ]
|
||||||
|
@ -154,8 +159,8 @@ class WidgetMixin:
|
||||||
def create_grid_treeview_widget(self, view, wid):
|
def create_grid_treeview_widget(self, view, wid):
|
||||||
scroll = Gtk.ScrolledWindow()
|
scroll = Gtk.ScrolledWindow()
|
||||||
grid = Gtk.TreeView()
|
grid = Gtk.TreeView()
|
||||||
store = Gtk.ListStore(GdkPixbuf.Pixbuf, str)
|
store = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str)
|
||||||
# store = Gtk.TreeStore(GdkPixbuf.Pixbuf, str)
|
# store = Gtk.TreeStore(GdkPixbuf.Pixbuf or None, str)
|
||||||
column = Gtk.TreeViewColumn("Icons")
|
column = Gtk.TreeViewColumn("Icons")
|
||||||
icon = Gtk.CellRendererPixbuf()
|
icon = Gtk.CellRendererPixbuf()
|
||||||
name = Gtk.CellRendererText()
|
name = Gtk.CellRendererText()
|
|
@ -82,7 +82,7 @@ class WindowMixin(TabMixin):
|
||||||
_wid, _tid, _view, iconview, store = self.get_current_state()
|
_wid, _tid, _view, iconview, store = self.get_current_state()
|
||||||
selected_files = iconview.get_selected_items()
|
selected_files = iconview.get_selected_items()
|
||||||
current_directory = view.get_current_directory()
|
current_directory = view.get_current_directory()
|
||||||
path_file = Gio.File.new_for_path( current_directory)
|
path_file = Gio.File.new_for_path(current_directory)
|
||||||
mount_file = path_file.query_filesystem_info(attributes="filesystem::*", cancellable=None)
|
mount_file = path_file.query_filesystem_info(attributes="filesystem::*", cancellable=None)
|
||||||
formatted_mount_free = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::free")) )
|
formatted_mount_free = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::free")) )
|
||||||
formatted_mount_size = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::size")) )
|
formatted_mount_size = self.sizeof_fmt( int(mount_file.get_attribute_as_string("filesystem::size")) )
|
||||||
|
@ -101,11 +101,16 @@ class WindowMixin(TabMixin):
|
||||||
uris = self.format_to_uris(store, _wid, _tid, selected_files, True)
|
uris = self.format_to_uris(store, _wid, _tid, selected_files, True)
|
||||||
combined_size = 0
|
combined_size = 0
|
||||||
for uri in uris:
|
for uri in uris:
|
||||||
file_info = Gio.File.new_for_path(uri).query_info(attributes="standard::size",
|
try:
|
||||||
flags=Gio.FileQueryInfoFlags.NONE,
|
file_info = Gio.File.new_for_path(uri).query_info(attributes="standard::size",
|
||||||
cancellable=None)
|
flags=Gio.FileQueryInfoFlags.NONE,
|
||||||
file_size = file_info.get_size()
|
cancellable=None)
|
||||||
combined_size += file_size
|
file_size = file_info.get_size()
|
||||||
|
combined_size += file_size
|
||||||
|
except Exception as e:
|
||||||
|
if debug:
|
||||||
|
print(repr(e))
|
||||||
|
|
||||||
|
|
||||||
formatted_size = self.sizeof_fmt(combined_size)
|
formatted_size = self.sizeof_fmt(combined_size)
|
||||||
if view.hide_hidden:
|
if view.hide_hidden:
|
||||||
|
@ -152,6 +157,9 @@ class WindowMixin(TabMixin):
|
||||||
def grid_set_selected_items(self, iconview):
|
def grid_set_selected_items(self, iconview):
|
||||||
self.selected_files = iconview.get_selected_items()
|
self.selected_files = iconview.get_selected_items()
|
||||||
|
|
||||||
|
def grid_cursor_toggled(self, iconview):
|
||||||
|
print("wat...")
|
||||||
|
|
||||||
def grid_icon_single_click(self, iconview, eve):
|
def grid_icon_single_click(self, iconview, eve):
|
||||||
try:
|
try:
|
||||||
self.path_menu.popdown()
|
self.path_menu.popdown()
|
||||||
|
@ -213,8 +221,19 @@ class WindowMixin(TabMixin):
|
||||||
data.set_text(uris_text, -1)
|
data.set_text(uris_text, -1)
|
||||||
|
|
||||||
def grid_on_drag_motion(self, iconview, drag_context, x, y, data):
|
def grid_on_drag_motion(self, iconview, drag_context, x, y, data):
|
||||||
wid, tid = iconview.get_name().split("|")
|
current = '|'.join(self.window_controller.get_active_data())
|
||||||
self.window_controller.set_active_data(wid, tid)
|
target = iconview.get_name()
|
||||||
|
wid, tid = target.split("|")
|
||||||
|
store = iconview.get_model()
|
||||||
|
treePath = iconview.get_drag_dest_item().path
|
||||||
|
|
||||||
|
if treePath:
|
||||||
|
uri = self.format_to_uris(store, wid, tid, treePath)[0].replace("file://", "")
|
||||||
|
self.override_drop_dest = uri if isdir(uri) else None
|
||||||
|
|
||||||
|
if target not in current:
|
||||||
|
self.window_controller.set_active_data(wid, tid)
|
||||||
|
|
||||||
|
|
||||||
def grid_on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
|
def grid_on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
|
||||||
if info == 80:
|
if info == 80:
|
||||||
|
@ -224,12 +243,14 @@ class WindowMixin(TabMixin):
|
||||||
view = self.get_fm_window(wid).get_view_by_id(tid)
|
view = self.get_fm_window(wid).get_view_by_id(tid)
|
||||||
|
|
||||||
uris = data.get_uris()
|
uris = data.get_uris()
|
||||||
dest = f"{view.get_current_directory()}"
|
dest = f"{view.get_current_directory()}" if not self.override_drop_dest else self.override_drop_dest
|
||||||
if len(uris) > 0:
|
if len(uris) == 0:
|
||||||
self.move_files(uris, dest)
|
|
||||||
else:
|
|
||||||
uris = data.get_text().split("\n")
|
uris = data.get_text().split("\n")
|
||||||
|
|
||||||
|
from_uri = '/'.join(uris[0].replace("file://", "").split("/")[:-1])
|
||||||
|
if from_uri != dest:
|
||||||
self.move_files(uris, dest)
|
self.move_files(uris, dest)
|
||||||
|
|
||||||
|
|
||||||
def create_new_view_notebook(self, widget=None, wid=None, path=None):
|
def create_new_view_notebook(self, widget=None, wid=None, path=None):
|
||||||
self.create_tab(wid, path)
|
self.create_tab(wid, path)
|
|
@ -0,0 +1,5 @@
|
||||||
|
from .PaneMixin import PaneMixin
|
||||||
|
from .WidgetMixin import WidgetMixin
|
||||||
|
from .TabMixin import TabMixin
|
||||||
|
from .WindowMixin import WindowMixin
|
||||||
|
from .WidgetFileActionMixin import WidgetFileActionMixin
|
|
@ -5,8 +5,8 @@ import os, logging
|
||||||
|
|
||||||
|
|
||||||
class Logger:
|
class Logger:
|
||||||
def __init__(self):
|
def __init__(self, config_path):
|
||||||
pass
|
self._CONFIG_PATH = config_path
|
||||||
|
|
||||||
def get_logger(self, loggerName = "NO_LOGGER_NAME_PASSED", createFile = True):
|
def get_logger(self, loggerName = "NO_LOGGER_NAME_PASSED", createFile = True):
|
||||||
"""
|
"""
|
||||||
|
@ -42,8 +42,8 @@ class Logger:
|
||||||
log.addHandler(ch)
|
log.addHandler(ch)
|
||||||
|
|
||||||
if createFile:
|
if createFile:
|
||||||
folder = "logs"
|
folder = self._CONFIG_PATH
|
||||||
file = folder + "/application.log"
|
file = f"{folder}/application.log"
|
||||||
|
|
||||||
if not os.path.exists(folder):
|
if not os.path.exists(folder):
|
||||||
os.mkdir(folder)
|
os.mkdir(folder)
|
||||||
|
|
|
@ -17,39 +17,45 @@ from . import Logger
|
||||||
|
|
||||||
class Settings:
|
class Settings:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.logger = Logger().get_logger()
|
|
||||||
self.builder = gtk.Builder()
|
self.builder = gtk.Builder()
|
||||||
|
|
||||||
self.SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__))
|
self.SCRIPT_PTH = os.path.dirname(os.path.realpath(__file__))
|
||||||
self.USER_HOME = path.expanduser('~')
|
self.USER_HOME = path.expanduser('~')
|
||||||
self.CONFIG_PATH = f"{self.USER_HOME}/.config/solarfm"
|
self.CONFIG_PATH = f"{self.USER_HOME}/.config/{app_name.lower()}"
|
||||||
self.USR_SOLARFM = "/usr/share/solarfm"
|
self.PLUGINS_PATH = f"{self.CONFIG_PATH}/plugins"
|
||||||
|
self.USR_SOLARFM = f"/usr/share/{app_name.lower()}"
|
||||||
|
|
||||||
self.cssFile = f"{self.CONFIG_PATH}/stylesheet.css"
|
self.cssFile = f"{self.CONFIG_PATH}/stylesheet.css"
|
||||||
self.windows_glade = f"{self.CONFIG_PATH}/Main_Window.glade"
|
self.windows_glade = f"{self.CONFIG_PATH}/Main_Window.glade"
|
||||||
self.DEFAULT_ICONS = f"{self.CONFIG_PATH}/icons"
|
self.DEFAULT_ICONS = f"{self.CONFIG_PATH}/icons"
|
||||||
self.window_icon = f"{self.DEFAULT_ICONS}/solarfm.png"
|
self.window_icon = f"{self.DEFAULT_ICONS}/{app_name.lower()}.png"
|
||||||
self.main_window = None
|
self.main_window = None
|
||||||
|
|
||||||
|
if not os.path.exists(self.CONFIG_PATH):
|
||||||
|
os.mkdir(self.CONFIG_PATH)
|
||||||
|
if not os.path.exists(self.PLUGINS_PATH):
|
||||||
|
os.mkdir(self.PLUGINS_PATH)
|
||||||
|
|
||||||
if not os.path.exists(self.windows_glade):
|
if not os.path.exists(self.windows_glade):
|
||||||
self.windows_glade = f"{self.USR_SOLARFM}/Main_Window.glade"
|
self.windows_glade = f"{self.USR_SOLARFM}/Main_Window.glade"
|
||||||
if not os.path.exists(self.cssFile):
|
if not os.path.exists(self.cssFile):
|
||||||
self.cssFile = f"{self.USR_SOLARFM}/stylesheet.css"
|
self.cssFile = f"{self.USR_SOLARFM}/stylesheet.css"
|
||||||
if not os.path.exists(self.window_icon):
|
if not os.path.exists(self.window_icon):
|
||||||
self.window_icon = f"{self.USR_SOLARFM}/icons/solarfm.png"
|
self.window_icon = f"{self.USR_SOLARFM}/icons/{app_name.lower()}.png"
|
||||||
if not os.path.exists(self.DEFAULT_ICONS):
|
if not os.path.exists(self.DEFAULT_ICONS):
|
||||||
self.DEFAULT_ICONS = f"{self.USR_SOLARFM}/icons"
|
self.DEFAULT_ICONS = f"{self.USR_SOLARFM}/icons"
|
||||||
|
|
||||||
|
self.logger = Logger(self.CONFIG_PATH).get_logger()
|
||||||
self.builder.add_from_file(self.windows_glade)
|
self.builder.add_from_file(self.windows_glade)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def createWindow(self):
|
def create_window(self):
|
||||||
# Get window and connect signals
|
# Get window and connect signals
|
||||||
self.main_window = self.builder.get_object("Main_Window")
|
self.main_window = self.builder.get_object("Main_Window")
|
||||||
self.setWindowData()
|
self._set_window_data()
|
||||||
|
|
||||||
def setWindowData(self):
|
def _set_window_data(self):
|
||||||
self.main_window.set_icon_from_file(self.window_icon)
|
self.main_window.set_icon_from_file(self.window_icon)
|
||||||
screen = self.main_window.get_screen()
|
screen = self.main_window.get_screen()
|
||||||
visual = screen.get_rgba_visual()
|
visual = screen.get_rgba_visual()
|
||||||
|
@ -57,7 +63,7 @@ class Settings:
|
||||||
if visual != None and screen.is_composited():
|
if visual != None and screen.is_composited():
|
||||||
self.main_window.set_visual(visual)
|
self.main_window.set_visual(visual)
|
||||||
self.main_window.set_app_paintable(True)
|
self.main_window.set_app_paintable(True)
|
||||||
self.main_window.connect("draw", self.area_draw)
|
self.main_window.connect("draw", self._area_draw)
|
||||||
|
|
||||||
# bind css file
|
# bind css file
|
||||||
cssProvider = gtk.CssProvider()
|
cssProvider = gtk.CssProvider()
|
||||||
|
@ -66,16 +72,13 @@ class Settings:
|
||||||
styleContext = gtk.StyleContext()
|
styleContext = gtk.StyleContext()
|
||||||
styleContext.add_provider_for_screen(screen, cssProvider, gtk.STYLE_PROVIDER_PRIORITY_USER)
|
styleContext.add_provider_for_screen(screen, cssProvider, gtk.STYLE_PROVIDER_PRIORITY_USER)
|
||||||
|
|
||||||
def area_draw(self, widget, cr):
|
def _area_draw(self, widget, cr):
|
||||||
cr.set_source_rgba(0, 0, 0, 0.54)
|
cr.set_source_rgba(0, 0, 0, 0.54)
|
||||||
cr.set_operator(cairo.OPERATOR_SOURCE)
|
cr.set_operator(cairo.OPERATOR_SOURCE)
|
||||||
cr.paint()
|
cr.paint()
|
||||||
cr.set_operator(cairo.OPERATOR_OVER)
|
cr.set_operator(cairo.OPERATOR_OVER)
|
||||||
|
|
||||||
def getMainWindow(self): return self.main_window
|
def get_monitor_data(self):
|
||||||
|
|
||||||
|
|
||||||
def getMonitorData(self):
|
|
||||||
screen = self.builder.get_object("Main_Window").get_screen()
|
screen = self.builder.get_object("Main_Window").get_screen()
|
||||||
monitors = []
|
monitors = []
|
||||||
for m in range(screen.get_n_monitors()):
|
for m in range(screen.get_n_monitors()):
|
||||||
|
@ -85,3 +88,8 @@ class Settings:
|
||||||
print("{}x{}+{}+{}".format(monitor.width, monitor.height, monitor.x, monitor.y))
|
print("{}x{}+{}+{}".format(monitor.width, monitor.height, monitor.x, monitor.y))
|
||||||
|
|
||||||
return monitors
|
return monitors
|
||||||
|
|
||||||
|
def get_builder(self): return self.builder
|
||||||
|
def get_logger(self): return self.logger
|
||||||
|
def get_main_window(self): return self.main_window
|
||||||
|
def get_plugins_path(self): return self.PLUGINS_PATH
|
||||||
|
|
|
@ -1307,19 +1307,25 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkImageMenuItem">
|
<object class="GtkImageMenuItem">
|
||||||
<property name="label">gtk-new</property>
|
<property name="label">gtk-new</property>
|
||||||
|
<property name="name">create</property>
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
|
<property name="tooltip-text" translatable="yes">New File/Folder...</property>
|
||||||
<property name="use-underline">True</property>
|
<property name="use-underline">True</property>
|
||||||
<property name="use-stock">True</property>
|
<property name="use-stock">True</property>
|
||||||
|
<signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkImageMenuItem">
|
<object class="GtkImageMenuItem">
|
||||||
<property name="label">gtk-open</property>
|
<property name="label">gtk-open</property>
|
||||||
|
<property name="name">open</property>
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
|
<property name="tooltip-text" translatable="yes">Open...</property>
|
||||||
<property name="use-underline">True</property>
|
<property name="use-underline">True</property>
|
||||||
<property name="use-stock">True</property>
|
<property name="use-stock">True</property>
|
||||||
|
<signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
<child>
|
||||||
|
@ -2068,25 +2074,6 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="orientation">vertical</property>
|
<property name="orientation">vertical</property>
|
||||||
<child type="center">
|
|
||||||
<object class="GtkButton">
|
|
||||||
<property name="label">gtk-delete</property>
|
|
||||||
<property name="name">delete</property>
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">True</property>
|
|
||||||
<property name="receives-default">True</property>
|
|
||||||
<property name="tooltip-text" translatable="yes">Delete...</property>
|
|
||||||
<property name="margin-top">20</property>
|
|
||||||
<property name="use-stock">True</property>
|
|
||||||
<property name="always-show-image">True</property>
|
|
||||||
<signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
<property name="position">11</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton">
|
<object class="GtkButton">
|
||||||
<property name="label">gtk-open</property>
|
<property name="label">gtk-open</property>
|
||||||
|
@ -2226,25 +2213,6 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||||
<property name="position">7</property>
|
<property name="position">7</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
<child>
|
|
||||||
<object class="GtkButton">
|
|
||||||
<property name="label" translatable="yes">Go To Trash</property>
|
|
||||||
<property name="name">go_to_trash</property>
|
|
||||||
<property name="visible">True</property>
|
|
||||||
<property name="can-focus">True</property>
|
|
||||||
<property name="receives-default">True</property>
|
|
||||||
<property name="tooltip-text" translatable="yes">Go To Trash...</property>
|
|
||||||
<property name="image">trash_img2</property>
|
|
||||||
<property name="always-show-image">True</property>
|
|
||||||
<signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
|
|
||||||
</object>
|
|
||||||
<packing>
|
|
||||||
<property name="expand">False</property>
|
|
||||||
<property name="fill">True</property>
|
|
||||||
<property name="pack-type">end</property>
|
|
||||||
<property name="position">8</property>
|
|
||||||
</packing>
|
|
||||||
</child>
|
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton">
|
<object class="GtkButton">
|
||||||
<property name="label" translatable="yes">Archive</property>
|
<property name="label" translatable="yes">Archive</property>
|
||||||
|
@ -2257,12 +2225,65 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||||
<property name="always-show-image">True</property>
|
<property name="always-show-image">True</property>
|
||||||
<signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
|
<signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
|
||||||
</object>
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">8</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton">
|
||||||
|
<property name="label">gtk-delete</property>
|
||||||
|
<property name="name">delete</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="receives-default">True</property>
|
||||||
|
<property name="tooltip-text" translatable="yes">Delete...</property>
|
||||||
|
<property name="margin-top">20</property>
|
||||||
|
<property name="use-stock">True</property>
|
||||||
|
<property name="always-show-image">True</property>
|
||||||
|
<signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
|
||||||
|
</object>
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
<property name="fill">True</property>
|
<property name="fill">True</property>
|
||||||
<property name="position">9</property>
|
<property name="position">9</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="restore_from_trash">
|
||||||
|
<property name="label" translatable="yes">Restore From Trash</property>
|
||||||
|
<property name="name">restore_from_trash</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="receives-default">True</property>
|
||||||
|
<property name="tooltip-text" translatable="yes">Restore From Trash...</property>
|
||||||
|
<property name="margin-top">20</property>
|
||||||
|
<signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">10</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton" id="empty_trash">
|
||||||
|
<property name="label" translatable="yes">Empty Trash</property>
|
||||||
|
<property name="name">empty_trash</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="receives-default">True</property>
|
||||||
|
<property name="tooltip-text" translatable="yes">Empty Trash...</property>
|
||||||
|
<property name="margin-bottom">20</property>
|
||||||
|
<signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">11</property>
|
||||||
|
</packing>
|
||||||
|
</child>
|
||||||
<child>
|
<child>
|
||||||
<object class="GtkButton">
|
<object class="GtkButton">
|
||||||
<property name="label" translatable="yes">Trash</property>
|
<property name="label" translatable="yes">Trash</property>
|
||||||
|
@ -2278,8 +2299,25 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
|
||||||
<packing>
|
<packing>
|
||||||
<property name="expand">False</property>
|
<property name="expand">False</property>
|
||||||
<property name="fill">True</property>
|
<property name="fill">True</property>
|
||||||
<property name="pack-type">end</property>
|
<property name="position">12</property>
|
||||||
<property name="position">11</property>
|
</packing>
|
||||||
|
</child>
|
||||||
|
<child>
|
||||||
|
<object class="GtkButton">
|
||||||
|
<property name="label" translatable="yes">Go To Trash</property>
|
||||||
|
<property name="name">go_to_trash</property>
|
||||||
|
<property name="visible">True</property>
|
||||||
|
<property name="can-focus">True</property>
|
||||||
|
<property name="receives-default">True</property>
|
||||||
|
<property name="tooltip-text" translatable="yes">Go To Trash...</property>
|
||||||
|
<property name="image">trash_img2</property>
|
||||||
|
<property name="always-show-image">True</property>
|
||||||
|
<signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
|
||||||
|
</object>
|
||||||
|
<packing>
|
||||||
|
<property name="expand">False</property>
|
||||||
|
<property name="fill">True</property>
|
||||||
|
<property name="position">13</property>
|
||||||
</packing>
|
</packing>
|
||||||
</child>
|
</child>
|
||||||
</object>
|
</object>
|
||||||
|
|
Loading…
Reference in New Issue