Compare commits

..

18 Commits

Author SHA1 Message Date
itdominator 3a2e8eeb08 Attempted further memory leak prevention; fixed bugs from moving to python 12; misc. 2024-09-11 02:11:00 -05:00
itdominator 35456f2bca pyright changes, start.sh changes, misc. 2024-07-26 19:52:00 -05:00
itdominator 9d3a5b9f3b Attempting to prompt for gc; About page updates; small non crit errors fixed 2024-07-04 17:24:31 -05:00
itdominator ce00970171 moved thumbnail generation to plugin; extended plugin loading for pre and post window loading 2024-06-29 21:37:44 -05:00
itdominator 2f954f4c79 updated dir watch; removed keys call where senseable; added additional. debug hook; added threading and async code for testing 2024-06-12 00:32:14 -05:00
itdominator a362039e73 Fixed depricated exception class usage; fixed usertype interupt 2024-03-25 22:49:31 -05:00
itdominator 02c31719d1 Fixing translate plugin; attempted dispose call 2024-03-11 22:28:42 -05:00
itdominator d65ea8dec8 Scaling system icons as some do not match expected scale. 2024-03-11 20:03:48 -05:00
itdominator fec0d26ab7 Improved selection bounds on rename 2024-02-12 19:58:23 -06:00
itdominator a47bd23e78 made main method 2024-02-08 21:24:01 -06:00
itdominator 44ef6ea2bb Reworking some tab logic to omit adding a label widget 2024-01-29 22:53:51 -06:00
itdominator be7be00f78 refactoring pid logic; addedd window state preservation; slight thread rework 2024-01-08 21:11:10 -06:00
itdominator 8e5ae4824c idle_add refactor for event source clearing; Gtk main call moved 2024-01-03 20:36:17 -06:00
itdominator 37e3265be5 GLib idle add return effort 2 2023-12-31 22:35:43 -06:00
itdominator 4cafb7ff9f GLib idle add return effort 2023-12-31 22:20:04 -06:00
itdominator 9336df2afa Changing out threading for some sections; added 2 new tab option 2023-12-24 13:23:24 -06:00
itdominator d936b17429 Improved keybinding clarity; trying to fix thread and async issues 2023-11-25 15:52:43 -06:00
itdominator e6739c3087 Wrapped async in daemon thread for icon loading 2023-11-12 23:25:46 -06:00
77 changed files with 1179 additions and 576 deletions

View File

@ -8,7 +8,7 @@ Additionally, if not building a .deb then just move the contents of user_config
Copy the share/solarfm folder to your user .config/ directory too. Copy the share/solarfm folder to your user .config/ directory too.
`pyrightconfig.json` `pyrightconfig.json`
<p>The pyrightconfig file needs to stay on same level as the .git folders in order to have settings detected when using pyright with lsp functionality.</p> <p>The pyrightconfig file needs to stay on same level as the .git folders in order to have settings detected when using pyright with lsp functionality. "pyrightconfig.json" can prompt IDEs such as Zed on settings to use and where imports are located- look at venvPath and venv. "venvPath" is parent path of "venv" where "venv" is just the name of the folder under the parent path that is the python created venv.
<h6>Install Setup</h6> <h6>Install Setup</h6>
``` ```

View File

@ -14,6 +14,7 @@ class Manifest:
'ui_target': "plugin_control_list", 'ui_target': "plugin_control_list",
'pass_fm_events': "true" 'pass_fm_events': "true"
} }
pre_launch: bool = False
``` ```

View File

@ -122,7 +122,6 @@ class Plugin(PluginBase):
uri = state.uris[0] uri = state.uris[0]
path = state.tab.get_current_directory() path = state.tab.get_current_directory()
properties = self._set_ui_data(uri, path) properties = self._set_ui_data(uri, path)
response = self._properties_dialog.run() response = self._properties_dialog.run()
if response in [Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT]: if response in [Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT]:
@ -168,13 +167,13 @@ class Plugin(PluginBase):
def _set_ui_data(self, uri, path): def _set_ui_data(self, uri, path):
properties = Properties() properties = Properties()
file_info = Gio.File.new_for_path(uri).query_info(attributes="standard::*,owner::*,time::access,time::changed", file_info = Gio.File.new_for_path(uri).query_info(attributes = "standard::*,owner::*,time::access,time::changed",
flags=Gio.FileQueryInfoFlags.NONE, flags = Gio.FileQueryInfoFlags.NONE,
cancellable=None) cancellable = None)
is_symlink = file_info.get_attribute_as_string("standard::is-symlink") is_symlink = file_info.get_attribute_as_string("standard::is-symlink")
properties.file_uri = uri properties.file_uri = uri
properties.file_target = file_info.get_attribute_as_string("standard::symlink-target") if is_symlink else "" properties.file_target = file_info.get_attribute_as_string("standard::symlink-target") if is_symlink in [True, "TRUE"] else ""
properties.file_name = file_info.get_display_name() properties.file_name = file_info.get_display_name()
properties.file_location = path properties.file_location = path
properties.mime_type = file_info.get_content_type() properties.mime_type = file_info.get_content_type()
@ -186,7 +185,7 @@ class Plugin(PluginBase):
# NOTE: Read = 4, Write = 2, Exec = 1 # NOTE: Read = 4, Write = 2, Exec = 1
command = ["stat", "-c", "%a", uri] command = ["stat", "-c", "%a", uri]
with subprocess.Popen(command, stdout=subprocess.PIPE) as proc: with subprocess.Popen(command, stdout = subprocess.PIPE) as proc:
properties.chmod_stat = list(proc.stdout.read().decode("UTF-8").strip()) properties.chmod_stat = list(proc.stdout.read().decode("UTF-8").strip())
owner = self._chmod_map[f"{properties.chmod_stat[0]}"] owner = self._chmod_map[f"{properties.chmod_stat[0]}"]
group = self._chmod_map[f"{properties.chmod_stat[1]}"] group = self._chmod_map[f"{properties.chmod_stat[1]}"]

View File

@ -48,7 +48,7 @@ class GrepPreviewWidget(Gtk.Box):
return bytes(f"\n<span foreground='{color}'>{target}</span>", "utf-8").decode("utf-8") return bytes(f"\n<span foreground='{color}'>{target}</span>", "utf-8").decode("utf-8")
def make_utf8_line_highlight(self, buffer, itr, i, color, target, query): def make_utf8_line_highlight(self, buffer, itr, i, color, target, query):
parts = re.split(r"(" + query + ")(?i)", target.replace("\n", "")) parts = re.split(r"(?i)(" + query + ")", target.replace("\n", ""))
for part in parts: for part in parts:
itr = buffer.get_end_iter() itr = buffer.get_end_iter()

View File

@ -8,6 +8,7 @@
"ui_target": "plugin_control_list", "ui_target": "plugin_control_list",
"pass_fm_events": "true", "pass_fm_events": "true",
"bind_keys": ["Example Plugin||send_message:<Control>f"] "bind_keys": ["Example Plugin||send_message:<Control>f"]
} },
"pre_launch": "false"
} }
} }

View File

@ -0,0 +1,3 @@
"""
Pligin Module
"""

View File

@ -0,0 +1,3 @@
"""
Pligin Package
"""

View File

@ -0,0 +1,73 @@
# Python imports
import json
import os
from os import path
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports
from .icon import Icon
class IconController(Icon):
def __init__(self):
CURRENT_PATH = os.path.dirname(os.path.realpath(__file__))
# NOTE: app_name should be defined using python 'builtins' and so too must be logger used in the various classes
app_name_exists = False
try:
app_name
app_name_exists = True
except Exception as e:
...
APP_CONTEXT = f"{app_name.lower()}" if app_name_exists else "shellfm"
USR_APP_CONTEXT = f"/usr/share/{APP_CONTEXT}"
USER_HOME = path.expanduser('~')
CONFIG_PATH = f"{USER_HOME}/.config/{APP_CONTEXT}"
self.DEFAULT_ICONS = f"{CONFIG_PATH}/icons"
self.DEFAULT_ICON = f"{self.DEFAULT_ICONS}/text.png"
self.FFMPG_THUMBNLR = f"{CONFIG_PATH}/ffmpegthumbnailer" # Thumbnail generator binary
self.BLENDER_THUMBNLR = f"{CONFIG_PATH}/blender-thumbnailer" # Blender thumbnail generator binary
self.ICON_DIRS = ["/usr/share/icons", f"{USER_HOME}/.icons" "/usr/share/pixmaps"]
self.BASE_THUMBS_PTH = f"{USER_HOME}/.thumbnails"
self.ABS_THUMBS_PTH = f"{self.BASE_THUMBS_PTH}/normal"
self.STEAM_ICONS_PTH = f"{self.BASE_THUMBS_PTH}/steam_icons"
if not path.isdir(self.BASE_THUMBS_PTH):
os.mkdir(self.BASE_THUMBS_PTH)
if not path.isdir(self.ABS_THUMBS_PTH):
os.mkdir(self.ABS_THUMBS_PTH)
if not path.isdir(self.STEAM_ICONS_PTH):
os.mkdir(self.STEAM_ICONS_PTH)
if not os.path.exists(self.DEFAULT_ICONS):
self.DEFAULT_ICONS = f"{USR_APP_CONTEXT}/icons"
self.DEFAULT_ICON = f"{self.DEFAULT_ICONS}/text.png"
CONFIG_FILE = f"{CURRENT_PATH}/../settings.json"
with open(CONFIG_FILE) as f:
settings = json.load(f)
config = settings["config"]
self.container_icon_wh = config["container_icon_wh"]
self.video_icon_wh = config["video_icon_wh"]
self.sys_icon_wh = config["sys_icon_wh"]
# Filters
filters = settings["filters"]
self.fmeshs = tuple(filters["meshs"])
self.fcode = tuple(filters["code"])
self.fvideos = tuple(filters["videos"])
self.foffice = tuple(filters["office"])
self.fimages = tuple(filters["images"])
self.ftext = tuple(filters["text"])
self.fmusic = tuple(filters["music"])
self.fpdf = tuple(filters["pdf"])

View File

@ -138,6 +138,7 @@ class Icon(DesktopIconMixin, VideoIconMixin, MeshsIconMixin):
def _call_gtk_thread(event, result): def _call_gtk_thread(event, result):
result.append( self.get_system_thumbnail(full_path, size) ) result.append( self.get_system_thumbnail(full_path, size) )
event.set() event.set()
return False
result = [] result = []
event = threading.Event() event = threading.Event()
@ -151,11 +152,11 @@ class Icon(DesktopIconMixin, VideoIconMixin, MeshsIconMixin):
gio_file = Gio.File.new_for_path(full_path) gio_file = Gio.File.new_for_path(full_path)
info = gio_file.query_info('standard::icon' , 0, None) info = gio_file.query_info('standard::icon' , 0, None)
icon = info.get_icon().get_names()[0] icon = info.get_icon().get_names()[0]
data = settings_manager.get_icon_theme().lookup_icon(icon , size , 0) data = settings_manager.get_icon_theme().lookup_icon(icon , size, 0)
if data: if data:
icon_path = data.get_filename() icon_path = data.get_filename()
return GdkPixbuf.Pixbuf.new_from_file(icon_path) return GdkPixbuf.Pixbuf.new_from_file_at_size(icon_path, width = size, height = size)
raise IconException("No system icon found...") raise IconException("No system icon found...")
except IconException: except IconException:

View File

@ -14,4 +14,4 @@ class MeshsIconMixin:
proc = subprocess.Popen([self.BLENDER_THUMBNLR, full_path, hash_img_path]) proc = subprocess.Popen([self.BLENDER_THUMBNLR, full_path, hash_img_path])
proc.wait() proc.wait()
except Exception as e: except Exception as e:
self.logger.debug(repr(e)) logger.debug(repr(e))

View File

@ -14,7 +14,7 @@ class VideoIconMixin:
proc = subprocess.Popen([self.FFMPG_THUMBNLR, "-t", scrub_percent, "-s", "300", "-c", "jpg", "-i", full_path, "-o", hash_img_path]) proc = subprocess.Popen([self.FFMPG_THUMBNLR, "-t", scrub_percent, "-s", "300", "-c", "jpg", "-i", full_path, "-o", hash_img_path])
proc.wait() proc.wait()
except Exception as e: except Exception as e:
self.logger.debug(repr(e)) logger.info(repr(e))
self.ffprobe_generate_video_thumbnail(full_path, hash_img_path) self.ffprobe_generate_video_thumbnail(full_path, hash_img_path)
@ -51,5 +51,4 @@ class VideoIconMixin:
proc.wait() proc.wait()
except Exception as e: except Exception as e:
print("Video thumbnail generation issue in thread:") print("Video thumbnail generation issue in thread:")
print( repr(e) ) logger.info(repr(e))
self.logger.debug(repr(e))

View File

@ -0,0 +1,12 @@
{
"manifest": {
"name": "Thumbnailer",
"author": "ITDominator",
"version": "0.0.1",
"support": "",
"requests": {
"pass_fm_events": "true"
},
"pre_launch": "true"
}
}

View File

@ -0,0 +1,59 @@
# Python imports
import os
# Lib imports
# Application imports
from plugins.plugin_base import PluginBase
from .icons.controller import IconController
class Plugin(PluginBase):
def __init__(self):
super().__init__()
self.name = "Thumbnailer" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus
# where self.name should not be needed for message comms
# self.path = os.path.dirname(os.path.realpath(__file__))
def run(self):
self.icon_controller = IconController()
self._event_system.subscribe("create-thumbnail", self.create_thumbnail)
def generate_reference_ui_element(self):
...
def create_thumbnail(self, dir, file) -> str:
return self.icon_controller.create_icon(dir, file)
def get_video_icons(self, dir) -> list:
data = []
def get_video_icons(self) -> list:
data = []
fvideos = self.icon_controller.fvideos
vids = [ file for file in os.path.list_dir(dir) if file.lower().endswith(fvideos) ]
for file in vids:
img_hash, hash_img_path = self.create_video_thumbnail(full_path = f"{dir}/{file}", returnHashInstead = True)
data.append([img_hash, hash_img_path])
return data
def get_pixbuf_icon_str_combo(self, dir) -> list:
data = []
for file in os.path.list_dir(dir):
icon = self.icon_controller.create_icon(dir, file).get_pixbuf()
data.append([icon, file])
return data
def get_gtk_icon_str_combo(self, dir) -> list:
data = []
for file in os.path.list_dir(dir):
icon = self.icon_controller.create_icon(dir, file)
data.append([icon, file[0]])
return data

View File

@ -0,0 +1,101 @@
{
"config":{
"thumbnailer_path":"ffmpegthumbnailer",
"blender_thumbnailer_path":"",
"container_icon_wh":[
128,
128
],
"video_icon_wh":[
128,
64
],
"sys_icon_wh":[
56,
56
],
"steam_cdn_url":"https://steamcdn-a.akamaihd.net/steam/apps/",
"remux_folder_max_disk_usage":"8589934592"
},
"filters":{
"meshs":[
".dae",
".fbx",
".gltf",
".obj",
".stl"
],
"code":[
".cpp",
".css",
".c",
".go",
".html",
".htm",
".java",
".js",
".json",
".lua",
".md",
".py",
".rs",
".toml",
".xml",
".pom"
],
"videos":[
".mkv",
".mp4",
".webm",
".avi",
".mov",
".m4v",
".mpg",
".mpeg",
".wmv",
".flv"
],
"office":[
".doc",
".docx",
".xls",
".xlsx",
".xlt",
".xltx",
".xlm",
".ppt",
".pptx",
".pps",
".ppsx",
".odt",
".rtf"
],
"images":[
".png",
".jpg",
".jpeg",
".gif",
".ico",
".tga",
".webp"
],
"text":[
".txt",
".text",
".sh",
".cfg",
".conf",
".log"
],
"music":[
".psf",
".mp3",
".ogg",
".flac",
".m4a"
],
"pdf":[
".pdf"
]
}
}

View File

@ -184,8 +184,8 @@ class Plugin(PluginBase):
response = requests.post(self.vqd_link, headers=self.vqd_headers, data=self.vqd_data, timeout=2) response = requests.post(self.vqd_link, headers=self.vqd_headers, data=self.vqd_data, timeout=2)
if response.status_code == 200: if response.status_code == 200:
data = response.content data = response.content
vqd_start_index = data.index(b"vqd='") + 5 vqd_start_index = data.index(b"vqd=\"") + 5
vqd_end_index = data.index(b"'", vqd_start_index) vqd_end_index = data.index(b"\"", vqd_start_index)
self._vqd_attrib = data[vqd_start_index:vqd_end_index].decode("utf-8") self._vqd_attrib = data[vqd_start_index:vqd_end_index].decode("utf-8")
print(f"Translation VQD: {self._vqd_attrib}") print(f"Translation VQD: {self._vqd_attrib}")

View File

@ -111,6 +111,8 @@ class Plugin(PluginBase):
for uri in state.uris: for uri in state.uris:
self.trashman.trash(uri, verbocity) self.trashman.trash(uri, verbocity)
self.trashman.regenerate()
def restore_trash_files(self, widget = None, eve = None, verbocity = False): def restore_trash_files(self, widget = None, eve = None, verbocity = False):
self._event_system.emit("get_current_state") self._event_system.emit("get_current_state")
state = self._fm_state state = self._fm_state

View File

@ -43,4 +43,4 @@ class Trash(object):
def restore(self, filename, verbose): def restore(self, filename, verbose):
"""Restore a file from trash.""" """Restore a file from trash."""
raise NotImplementedError(_('Backend didnt \ implement this functionality')) raise NotImplementedError(_('Backend didnt implement this functionality'))

View File

@ -127,7 +127,7 @@ DeletionDate={}
f.write(infofile) f.write(infofile)
f.close() f.close()
self.regenerate() # self.regenerate()
if verbose: if verbose:
sys.stderr.write(_('trashed \'{}\'\n').format(filename)) sys.stderr.write(_('trashed \'{}\'\n').format(filename))

View File

@ -7,5 +7,7 @@
{ {
"root": "./src/versions/solarfm-0.0.1/solarfm" "root": "./src/versions/solarfm-0.0.1/solarfm"
} }
] ],
"venvPath": "/home/abaddon/Portable_Apps/py-venvs/pylsp-venv/",
"venv": "venv"
} }

View File

@ -16,13 +16,17 @@ from utils.settings_manager.manager import SettingsManager
# NOTE: Threads WILL NOT die with parent's destruction. # NOTE: Threads WILL NOT die with parent's destruction.
def threaded_wrapper(fn): def threaded_wrapper(fn):
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start() thread = threading.Thread(target = fn, args = args, kwargs = kwargs, daemon = False)
thread.start()
return thread
return wrapper return wrapper
# NOTE: Threads WILL die with parent's destruction. # NOTE: Threads WILL die with parent's destruction.
def daemon_threaded_wrapper(fn): def daemon_threaded_wrapper(fn):
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start() thread = threading.Thread(target = fn, args = args, kwargs = kwargs, daemon = True)
thread.start()
return thread
return wrapper return wrapper
def sizeof_fmt_def(num, suffix="B"): def sizeof_fmt_def(num, suffix="B"):

View File

@ -3,14 +3,13 @@
# Python imports # Python imports
import argparse import argparse
import faulthandler import faulthandler
import locale
import traceback import traceback
from setproctitle import setproctitle from setproctitle import setproctitle
import tracemalloc
tracemalloc.start()
# Lib imports # Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports # Application imports
from __builtins__ import * from __builtins__ import *
@ -18,39 +17,38 @@ from app import Application
def run(): def main(args, unknownargs):
try: setproctitle(f'{app_name}')
locale.setlocale(locale.LC_NUMERIC, 'C')
setproctitle(f"{app_name}") if args.debug == "true":
faulthandler.enable() # For better debug info settings_manager.set_debug(True)
parser = argparse.ArgumentParser() if args.trace_debug == "true":
# Add long and short arguments settings_manager.set_trace_debug(True)
parser.add_argument("--debug", "-d", default="false", help="Do extra console messaging.")
parser.add_argument("--trace-debug", "-td", default="false", help="Disable saves, ignore IPC lock, do extra console messaging.")
parser.add_argument("--no-plugins", "-np", default="false", help="Do not load plugins.")
parser.add_argument("--new-tab", "-t", default="", help="Open a file into new tab.") settings_manager.do_dirty_start_check()
parser.add_argument("--new-window", "-w", default="", help="Open a file into a new window.") Application(args, unknownargs)
# Read arguments (If any...)
args, unknownargs = parser.parse_known_args()
if args.debug == "true":
settings_manager.set_debug(True)
if args.trace_debug == "true":
settings_manager.set_trace_debug(True)
settings_manager.do_dirty_start_check()
Application(args, unknownargs)
Gtk.main()
except Exception as e:
traceback.print_exc()
quit()
if __name__ == "__main__": if __name__ == "__main__":
""" Set process title, get arguments, and create GTK main thread. """ ''' Set process title, get arguments, and create GTK main thread. '''
run()
parser = argparse.ArgumentParser()
# Add long and short arguments
parser.add_argument("--debug", "-d", default="false", help="Do extra console messaging.")
parser.add_argument("--trace-debug", "-td", default="false", help="Disable saves, ignore IPC lock, do extra console messaging.")
parser.add_argument("--no-plugins", "-np", default="false", help="Do not load plugins.")
parser.add_argument("--new-tab", "-nt", default="false", help="Opens a 'New Tab' if a handler is set for it.")
parser.add_argument("--file", "-f", default="default", help="JUST SOME FILE ARG.")
# Read arguments (If any...)
args, unknownargs = parser.parse_known_args()
try:
faulthandler.enable() # For better debug info
main(args, unknownargs)
except Exception as e:
traceback.print_exc()
quit()

View File

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

View File

@ -44,10 +44,13 @@ class Controller(UIMixin, SignalsMixins, Controller_Data):
self._subscribe_to_events() self._subscribe_to_events()
self._load_widgets() self._load_widgets()
if args.no_plugins == "false":
self.plugins_controller.pre_launch_plugins()
self._generate_file_views(self.fm_controller_data) self._generate_file_views(self.fm_controller_data)
if args.no_plugins == "false": if args.no_plugins == "false":
self.plugins.launch_plugins() self.plugins_controller.post_launch_plugins()
for arg in unknownargs + [args.new_tab,]: for arg in unknownargs + [args.new_tab,]:
if os.path.isdir(arg): if os.path.isdir(arg):
@ -78,6 +81,7 @@ class Controller(UIMixin, SignalsMixins, Controller_Data):
event_system.subscribe("do_action_from_menu_controls", self.do_action_from_menu_controls) event_system.subscribe("do_action_from_menu_controls", self.do_action_from_menu_controls)
event_system.subscribe("set_clipboard_data", self.set_clipboard_data) event_system.subscribe("set_clipboard_data", self.set_clipboard_data)
def _load_glade_file(self): def _load_glade_file(self):
self.builder.add_from_file( settings_manager.get_glade_file() ) self.builder.add_from_file( settings_manager.get_glade_file() )
self.builder.expose_object("main_window", self.window) self.builder.expose_object("main_window", self.window)
@ -113,8 +117,9 @@ class Controller(UIMixin, SignalsMixins, Controller_Data):
if not settings_manager.is_trace_debug(): if not settings_manager.is_trace_debug():
self.fm_controller.save_state() self.fm_controller.save_state()
def reload_plugins(self, widget=None, eve=None): def reload_plugins(self, widget=None, eve=None):
self.plugins.reload_plugins() self.plugins_controller.reload_plugins()
def do_action_from_menu_controls(self, _action=None, eve=None): def do_action_from_menu_controls(self, _action=None, eve=None):
@ -130,44 +135,48 @@ class Controller(UIMixin, SignalsMixins, Controller_Data):
event_system.emit("hide_rename_file_menu") event_system.emit("hide_rename_file_menu")
if action == "open": if action == "open":
event_system.emit("open_files") event_system.emit_and_await("open_files")
if action == "open_with": if action == "open_with":
event_system.emit("show_appchooser_menu") event_system.emit_and_await("show_appchooser_menu")
if action == "open_2_new_tab":
event_system.emit_and_await("open_2_new_tab")
if action == "execute": if action == "execute":
event_system.emit("execute_files") event_system.emit_and_await("execute_files")
if action == "execute_in_terminal": if action == "execute_in_terminal":
event_system.emit("execute_files", (True,)) event_system.emit_and_await("execute_files", (True,))
if action == "rename": if action == "rename":
event_system.emit("rename_files") event_system.emit_and_await("rename_files")
if action == "cut": if action == "cut":
event_system.emit("cut_files") event_system.emit_and_await("cut_files")
if action == "copy": if action == "copy":
event_system.emit("copy_files") event_system.emit_and_await("copy_files")
if action == "copy_path": if action == "copy_path":
event_system.emit("copy_path") event_system.emit_and_await("copy_path")
if action == "copy_name": if action == "copy_name":
event_system.emit("copy_name") event_system.emit_and_await("copy_name")
if action == "copy_path_name": if action == "copy_path_name":
event_system.emit("copy_path_name") event_system.emit_and_await("copy_path_name")
if action == "paste": if action == "paste":
event_system.emit("paste_files") event_system.emit_and_await("paste_files")
if action == "create": if action == "create":
event_system.emit("create_files") event_system.emit_and_await("create_files")
if action in ["save_session", "save_session_as", "load_session"]: if action in ["save_session", "save_session_as", "load_session"]:
event_system.emit("save_load_session", (action)) event_system.emit_and_await("save_load_session", (action))
if action == "about_page": if action == "about_page":
event_system.emit("show_about_page") event_system.emit_and_await("show_about_page")
if action == "io_popup": if action == "io_popup":
event_system.emit("show_io_popup") event_system.emit_and_await("show_io_popup")
if action == "plugins_popup": if action == "plugins_popup":
event_system.emit("show_plugins_popup") event_system.emit_and_await("show_plugins_popup")
if action == "messages_popup": if action == "messages_popup":
event_system.emit("show_messages_popup") event_system.emit_and_await("show_messages_popup")
if action == "ui_debug": if action == "ui_debug":
event_system.emit("load_interactive_debug") event_system.emit_and_await("load_interactive_debug")
if action == "tear_down": if action == "tear_down":
event_system.emit("tear_down") event_system.emit_and_await("tear_down")
action = None
def go_home(self, widget=None, eve=None): def go_home(self, widget=None, eve=None):
@ -185,11 +194,14 @@ class Controller(UIMixin, SignalsMixins, Controller_Data):
def tggl_top_main_menubar(self, widget=None, eve=None): def tggl_top_main_menubar(self, widget=None, eve=None):
top_main_menubar = self.builder.get_object("top_main_menubar") top_main_menubar = self.builder.get_object("top_main_menubar")
top_main_menubar.hide() if top_main_menubar.is_visible() else top_main_menubar.show() top_main_menubar.hide() if top_main_menubar.is_visible() else top_main_menubar.show()
top_main_menubar = None
def open_terminal(self, widget=None, eve=None): def open_terminal(self, widget=None, eve=None):
wid, tid = self.fm_controller.get_active_wid_and_tid() wid, tid = self.fm_controller.get_active_wid_and_tid()
tab = self.get_fm_window(wid).get_tab_by_id(tid) tab = self.get_fm_window(wid).get_tab_by_id(tid)
tab.execute([f"{tab.terminal_app}"], start_dir=tab.get_current_directory()) tab.execute([f"{tab.terminal_app}"], start_dir=tab.get_current_directory())
wid, tid, tab = None, None, None
def go_to_path(self, path: str): def go_to_path(self, path: str):
self.builder.get_object("path_entry").set_text(path) self.builder.get_object("path_entry").set_text(path)

View File

@ -29,7 +29,7 @@ class Controller_Data:
self._load_glade_file() self._load_glade_file()
self.fm_controller = WindowController() self.fm_controller = WindowController()
self.plugins = PluginsController() self.plugins_controller = PluginsController()
self.fm_controller_data = self.fm_controller.get_state_from_file() self.fm_controller_data = self.fm_controller.get_state_from_file()
self.window1 = self.builder.get_object("window_1") self.window1 = self.builder.get_object("window_1")
@ -70,23 +70,16 @@ class Controller_Data:
Returns: Returns:
state (obj): State state (obj): State
''' '''
# state = State()
state = self._state state = self._state
state.fm_controller = self.fm_controller state.fm_controller = self.fm_controller
state.notebooks = self.notebooks state.notebooks = self.notebooks
state.wid, state.tid = self.fm_controller.get_active_wid_and_tid() state.wid, state.tid = self.fm_controller.get_active_wid_and_tid()
state.tab = self.get_fm_window(state.wid).get_tab_by_id(state.tid) state.tab = self.get_fm_window(state.wid).get_tab_by_id(state.tid)
state.icon_grid = self.builder.get_object(f"{state.wid}|{state.tid}|icon_grid", use_gtk = False) state.icon_grid = self.builder.get_object(f"{state.wid}|{state.tid}|icon_grid", use_gtk = False)
# state.icon_grid = event_system.emit_and_await("get_files_view_icon_grid", (state.wid, state.tid))
state.store = state.icon_grid.get_model() state.store = state.icon_grid.get_model()
# NOTE: Need to watch this as I thought we had issues with just using single reference upon closing it.
# But, I found that not doing it this way caused objects to generate upon every click... (Because we're getting state info, duh)
# Yet interactive debug view shows them just pilling on and never clearing...
state.message_dialog = self.message_dialog state.message_dialog = self.message_dialog
state.user_pass_dialog = self.user_pass_dialog state.user_pass_dialog = self.user_pass_dialog
# state.message_dialog = MessageWidget()
# state.user_pass_dialog = UserPassWidget()
selected_files = state.icon_grid.get_selected_items() selected_files = state.icon_grid.get_selected_items()
if selected_files: if selected_files:
@ -121,6 +114,9 @@ class Controller_Data:
uris.append(fpath) uris.append(fpath)
tab = None
dir = None
return uris return uris

View File

@ -40,6 +40,7 @@ class FileSystemActions(HandlerMixin, CRUDMixin):
event_system.subscribe("open_files", self.open_files) event_system.subscribe("open_files", self.open_files)
event_system.subscribe("open_with_files", self.open_with_files) event_system.subscribe("open_with_files", self.open_with_files)
event_system.subscribe("open_2_new_tab", self.open_2_new_tab)
event_system.subscribe("execute_files", self.execute_files) event_system.subscribe("execute_files", self.execute_files)
event_system.subscribe("cut_files", self.cut_files) event_system.subscribe("cut_files", self.cut_files)
@ -104,6 +105,12 @@ class FileSystemActions(HandlerMixin, CRUDMixin):
state.tab.app_chooser_exec(app_info, uris) state.tab.app_chooser_exec(app_info, uris)
def open_2_new_tab(self):
state = event_system.emit_and_await("get_current_state")
uri = state.uris[0]
message = f"FILE|{uri}"
logger.info(message)
event_system.emit("post_file_to_ipc", message)
def execute_files(self, in_terminal=False): def execute_files(self, in_terminal=False):
state = event_system.emit_and_await("get_current_state") state = event_system.emit_and_await("get_current_state")

View File

@ -110,26 +110,30 @@ class HandlerMixin:
tab.move_file(fPath, tPath) tab.move_file(fPath, tPath)
else: else:
io_widget = IOWidget(action, file) io_widget = IOWidget(action, file)
io_list = self._builder.get_object("io_list")
io_list.add(io_widget)
io_list.show_all()
if action == "copy": if action == "copy":
file.copy_async(destination=target, file.copy_async(destination=target,
flags=Gio.FileCopyFlags.BACKUP, flags=Gio.FileCopyFlags.BACKUP,
io_priority=98, io_priority=45,
cancellable=io_widget.cancle_eve, cancellable=io_widget.cancle_eve,
progress_callback=io_widget.update_progress, progress_callback=io_widget.update_progress,
callback=io_widget.finish_callback) callback=io_widget.finish_callback)
self._builder.get_object("io_list").add(io_widget)
if action == "move" or action == "rename": if action == "move" or action == "rename":
file.move_async(destination=target, file.move_async(destination=target,
flags=Gio.FileCopyFlags.BACKUP, flags=Gio.FileCopyFlags.BACKUP,
io_priority=98, io_priority=45,
cancellable=io_widget.cancle_eve, cancellable=io_widget.cancle_eve,
progress_callback=None,
# NOTE: progress_callback here causes seg fault when set # NOTE: progress_callback here causes seg fault when set
progress_callback=None,
callback=io_widget.finish_callback) callback=io_widget.finish_callback)
self._builder.get_object("io_list").add(io_widget) io_widget = None
io_list = None
except GObject.GError as e: except GObject.GError as e:
raise OSError(e) raise OSError(e)

View File

@ -30,57 +30,53 @@ class FileActionSignalsMixin:
wid = tab.get_wid() wid = tab.get_wid()
tid = tab.get_id() tid = tab.get_id()
dir_watcher.connect("changed", self.dir_watch_updates, (f"{wid}|{tid}",)) dir_watcher.connect("changed", self.dir_watch_updates, *(f"{wid}|{tid}",))
tab.set_dir_watcher(dir_watcher) tab.set_dir_watcher(dir_watcher)
# NOTE: Too lazy to impliment a proper update handler and so just regen store and update tab. def dir_watch_updates(self, file_monitor, file, other_file = None, eve_type = None, tab_widget_id = None):
# Use a lock system to prevent too many update calls for certain instances but user can manually refresh if they have urgency
def dir_watch_updates(self, file_monitor, file, other_file = None, eve_type = None, data = None):
if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED, if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED,
Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN, Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN,
Gio.FileMonitorEvent.MOVED_OUT]: Gio.FileMonitorEvent.MOVED_OUT]:
logger.debug(eve_type)
if eve_type in [Gio.FileMonitorEvent.MOVED_IN, Gio.FileMonitorEvent.MOVED_OUT]: self.soft_lock_countdown(tab_widget_id)
self.update_on_soft_lock_end(data[0])
elif data[0] in self.soft_update_lock.keys():
self.soft_update_lock[data[0]]["last_update_time"] = time.time()
else:
self.soft_lock_countdown(data[0])
@threaded def soft_lock_countdown(self, tab_widget_id):
def soft_lock_countdown(self, tab_widget): if tab_widget_id in self.soft_update_lock:
self.soft_update_lock[tab_widget] = { "last_update_time": time.time()} timeout_id = self.soft_update_lock[tab_widget_id]["timeout_id"]
GLib.source_remove(timeout_id)
lock = True timeout_id = GLib.timeout_add(0, self.update_on_soft_lock_end, 600, *(tab_widget_id,))
while lock: self.soft_update_lock[tab_widget_id] = { "timeout_id": timeout_id }
time.sleep(0.6)
last_update_time = self.soft_update_lock[tab_widget]["last_update_time"]
current_time = time.time()
if (current_time - last_update_time) > 0.6:
lock = False
self.soft_update_lock.pop(tab_widget, None)
GLib.idle_add(self.update_on_soft_lock_end, *(tab_widget,))
def update_on_soft_lock_end(self, tab_widget): def update_on_soft_lock_end(self, timout_ms, tab_widget_id):
wid, tid = tab_widget.split("|") self.soft_update_lock.pop(tab_widget_id, None)
wid, tid = tab_widget_id.split("|")
notebook = self.builder.get_object(f"window_{wid}") notebook = self.builder.get_object(f"window_{wid}")
tab = self.get_fm_window(wid).get_tab_by_id(tid) tab = self.get_fm_window(wid).get_tab_by_id(tid)
icon_grid = self.builder.get_object(f"{wid}|{tid}|icon_grid", use_gtk = False) icon_grid = self.builder.get_object(f"{wid}|{tid}|icon_grid", use_gtk = False)
store = icon_grid.get_model() store = icon_grid.get_model()
_store, tab_widget_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}") _store, tab_widget_id_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}")
tab.load_directory() tab.load_directory()
icon_grid.clear_and_set_new_store() icon_grid.clear_and_set_new_store()
self.load_store(tab, icon_grid.get_store()) self.load_store(tab, icon_grid.get_store())
tab_widget_label.set_label(tab.get_end_of_path()) tab_widget_id_label.set_label(tab.get_end_of_path())
state = self.get_current_state() state = self.get_current_state()
if [wid, tid] in [state.wid, state.tid]: if [wid, tid] in [state.wid, state.tid]:
self.set_bottom_labels(tab) self.set_bottom_labels(tab)
wid, tid = None, None
notebook = None
tab = None
icon_grid = None
store = None
_store, tab_widget_id_label = None, None
state = None
return False
def do_file_search(self, widget, eve = None): def do_file_search(self, widget, eve = None):
if not self.ctrl_down and not self.shift_down and not self.alt_down: if not self.ctrl_down and not self.shift_down and not self.alt_down:

View File

@ -11,8 +11,8 @@ from gi.repository import Gdk
# Application imports # Application imports
valid_keyvalue_pat = re.compile(r"[a-z0-9A-Z-_\[\]\(\)\| ]")
valid_keyvalue_pat = re.compile(r"[a-z0-9A-Z-_\[\]\(\)\| ]")
@ -20,13 +20,25 @@ class KeyboardSignalsMixin:
""" KeyboardSignalsMixin keyboard hooks controller. """ """ KeyboardSignalsMixin keyboard hooks controller. """
# TODO: Need to set methods that use this to somehow check the keybindings state instead. # TODO: Need to set methods that use this to somehow check the keybindings state instead.
def unset_keys_and_data(self, widget=None, eve=None): def unset_keys_and_data(self, widget = None, eve = None):
self.ctrl_down = False self.ctrl_down = False
self.shift_down = False self.shift_down = False
self.alt_down = False self.alt_down = False
def unmap_special_keys(self, keyname):
if "control" in keyname:
self.ctrl_down = False
if "shift" in keyname:
self.shift_down = False
if "alt" in keyname:
self.alt_down = False
def on_global_key_press_controller(self, eve, user_data): def on_global_key_press_controller(self, eve, user_data):
keyname = Gdk.keyval_name(user_data.keyval).lower() keyname = Gdk.keyval_name(user_data.keyval).lower()
modifiers = Gdk.ModifierType(user_data.get_state() & ~Gdk.ModifierType.LOCK_MASK)
self.was_midified_key = True if modifiers != 0 else False
if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]: if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]:
if "control" in keyname: if "control" in keyname:
self.ctrl_down = True self.ctrl_down = True
@ -36,52 +48,49 @@ class KeyboardSignalsMixin:
self.alt_down = True self.alt_down = True
def on_global_key_release_controller(self, widget, event): def on_global_key_release_controller(self, widget, event):
"""Handler for keyboard events""" """ Handler for keyboard events """
keyname = Gdk.keyval_name(event.keyval).lower() keyname = Gdk.keyval_name(event.keyval).lower()
modifiers = Gdk.ModifierType(event.get_state() & ~Gdk.ModifierType.LOCK_MASK)
if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]: if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]:
if "control" in keyname: should_return = self.was_midified_key and (self.ctrl_down or self.shift_down or self.alt_down)
self.ctrl_down = False self.unmap_special_keys(keyname)
if "shift" in keyname:
self.shift_down = False if should_return:
if "alt" in keyname: self.was_midified_key = False
self.alt_down = False return
mapping = keybindings.lookup(event) mapping = keybindings.lookup(event)
logger.debug(f"on_global_key_release_controller > key > {keyname}")
logger.debug(f"on_global_key_release_controller > keyval > {event.keyval}")
logger.debug(f"on_global_key_release_controller > mapping > {mapping}")
if mapping: if mapping:
# See if in filemanager scope self.handle_mapped_key_event(mapping)
try:
getattr(self, mapping)()
return True
except Exception:
# Must be plugins scope, event call, OR we forgot to add method to file manager scope
if "||" in mapping:
sender, eve_type = mapping.split("||")
else:
sender = ""
eve_type = mapping
self.handle_plugin_key_event(sender, eve_type)
else: else:
logger.debug(f"on_global_key_release_controller > key > {keyname}") self.handle_as_key_event_scope(keyname)
if self.ctrl_down: def handle_mapped_key_event(self, mapping):
if keyname in ["1", "kp_1", "2", "kp_2", "3", "kp_3", "4", "kp_4"]: try:
self.builder.get_object(f"tggl_notebook_{keyname.strip('kp_')}").released() self.handle_as_controller_scope(mapping)
except Exception:
self.handle_as_plugin_scope(mapping)
def handle_plugin_key_event(self, sender, eve_type): def handle_as_controller_scope(self, mapping):
getattr(self, mapping)()
def handle_as_plugin_scope(self, mapping):
if "||" in mapping:
sender, eve_type = mapping.split("||")
else:
sender = ""
eve_type = mapping
self.handle_key_event_system(sender, eve_type)
def handle_as_key_event_scope(self, keyname):
if self.ctrl_down and not keyname in ["1", "kp_1", "2", "kp_2", "3", "kp_3", "4", "kp_4"]:
self.handle_key_event_system(None, keyname)
def handle_key_event_system(self, sender, eve_type):
event_system.emit(eve_type) event_system.emit(eve_type)
def keyboard_close_tab(self):
wid, tid = self.fm_controller.get_active_wid_and_tid()
notebook = self.builder.get_object(f"window_{wid}")
scroll = self.builder.get_object(f"{wid}|{tid}", use_gtk = False)
page = notebook.page_num(scroll)
tab = self.get_fm_window(wid).get_tab_by_id(tid)
watcher = tab.get_dir_watcher()
watcher.cancel()
self.get_fm_window(wid).delete_tab_by_id(tid)
notebook.remove_page(page)
if not trace_debug:
self.fm_controller.save_state()
self.set_window_title()

View File

@ -7,6 +7,7 @@ import gi
gi.require_version("Gtk", "3.0") gi.require_version("Gtk", "3.0")
from gi.repository import Gtk from gi.repository import Gtk
from gi.repository import GLib from gi.repository import GLib
from gi.repository import Gio
# Application imports # Application imports
from ...widgets.tab_header_widget import TabHeaderWidget from ...widgets.tab_header_widget import TabHeaderWidget
@ -19,6 +20,17 @@ class GridMixin:
"""docstring for GridMixin""" """docstring for GridMixin"""
def load_store(self, tab, store, save_state = False, use_generator = False): def load_store(self, tab, store, save_state = False, use_generator = False):
# dir = tab.get_current_directory()
# file = Gio.File.new_for_path(dir)
# dir_list = Gtk.DirectoryList.new("standard::*", file)
# store.set(dir_list)
# file = Gio.File.new_for_path(dir)
# for file in file.enumerate_children("standard::*", Gio.FILE_ATTRIBUTE_STANDARD_NAME, None):
# store.append(file)
# return
dir = tab.get_current_directory() dir = tab.get_current_directory()
files = tab.get_files() files = tab.get_files()
@ -26,61 +38,77 @@ class GridMixin:
store.append([None, file[0]]) store.append([None, file[0]])
Gtk.main_iteration() Gtk.main_iteration()
# for i, file in enumerate(files): self.generate_icons(tab, store, dir, files)
# self.create_icon(i, tab, store, dir, file[0]) # GLib.Thread("", self.generate_icons, tab, store, dir, files)
if use_generator:
# NOTE: tab > icon > _get_system_thumbnail_gtk_thread must not be used
# as the attempted promotion back to gtk threading stalls the generator. (We're already in main gtk thread)
for i, icon in enumerate( self.create_icons_generator(tab, dir, files) ):
self.load_icon(i, store, icon)
else:
try:
loop = asyncio.get_running_loop()
except RuntimeError:
loop = None
if loop and loop.is_running():
loop.create_task( self.create_icons(tab, store, dir, files) )
else:
asyncio.run( self.create_icons(tab, store, dir, files) )
# NOTE: Not likely called often from here but it could be useful # NOTE: Not likely called often from here but it could be useful
if save_state and not trace_debug: if save_state and not trace_debug:
self.fm_controller.save_state() self.fm_controller.save_state()
async def create_icons(self, tab, store, dir, files): dir = None
tasks = [self.update_store(i, store, dir, tab, file[0]) for i, file in enumerate(files)] files = None
await asyncio.gather(*tasks)
async def load_icon(self, i, store, icon): @daemon_threaded
self.update_store(i, store, icon) def generate_icons(self, tab, store, dir, files):
for i, file in enumerate(files):
# GLib.Thread(f"{i}", self.make_and_load_icon, i, store, tab, dir, file[0])
self.make_and_load_icon( i, store, tab, dir, file[0])
async def update_store(self, i, store, dir, tab, file): def update_store(self, i, store, icon):
icon = tab.create_icon(dir, file)
itr = store.get_iter(i) itr = store.get_iter(i)
GLib.idle_add(self.insert_store, store, itr, icon.copy())
itr = None
del icon
@daemon_threaded
def make_and_load_icon(self, i, store, tab, dir, file):
icon = tab.create_icon(dir, file)
self.update_store(i, store, icon)
icon = None
def get_icon(self, tab, dir, file):
tab.create_icon(dir, file)
# @daemon_threaded
# def generate_icons(self, tab, store, dir, files):
# try:
# loop = asyncio.get_running_loop()
# except RuntimeError:
# loop = None
# if loop and loop.is_running():
# loop = asyncio.get_event_loop()
# loop.create_task( self.create_icons(tab, store, dir, files) )
# else:
# asyncio.run( self.create_icons(tab, store, dir, files) )
# async def create_icons(self, tab, store, dir, files):
# icons = [self.get_icon(tab, dir, file[0]) for file in files]
# data = await asyncio.gather(*icons)
# tasks = [self.update_store(i, store, icon) for i, icon in enumerate(data)]
# asyncio.gather(*tasks)
# async def update_store(self, i, store, icon):
# itr = store.get_iter(i)
# GLib.idle_add(self.insert_store, store, itr, icon)
# async def get_icon(self, tab, dir, file):
# return tab.create_icon(dir, file)
def insert_store(self, store, itr, icon):
store.set_value(itr, 0, icon) store.set_value(itr, 0, icon)
def create_icons_generator(self, tab, dir, files): # Note: If the function returns GLib.SOURCE_REMOVE or False it is automatically removed from the list of event sources and will not be called again.
for file in files: return False
icon = tab.create_icon(dir, file[0])
yield icon
# @daemon_threaded def do_ui_update(self):
# def create_icon(self, i, tab, store, dir, file): Gtk.main_iteration()
# icon = tab.create_icon(dir, file) return False
# GLib.idle_add(self.update_store, *(i, store, icon,))
#
# @daemon_threaded
# def load_icon(self, i, store, icon):
# GLib.idle_add(self.update_store, *(i, store, icon,))
# def update_store(self, i, store, icon): def create_tab_widget(self):
# itr = store.get_iter(i) return TabHeaderWidget(self.close_tab)
# store.set_value(itr, 0, icon)
def create_tab_widget(self, tab):
return TabHeaderWidget(tab, self.close_tab)
def create_scroll_and_store(self, tab, wid, use_tree_view = False): def create_scroll_and_store(self, tab, wid, use_tree_view = False):
scroll = Gtk.ScrolledWindow() scroll = Gtk.ScrolledWindow()
@ -137,6 +165,7 @@ class GridMixin:
store = icon_grid.get_model() store = icon_grid.get_model()
tab_label = notebook.get_tab_label(obj).get_children()[0] tab_label = notebook.get_tab_label(obj).get_children()[0]
icon_grid = None
return store, tab_label return store, tab_label
def get_icon_grid_from_notebook(self, notebook, _name): def get_icon_grid_from_notebook(self, notebook, _name):

View File

@ -34,27 +34,46 @@ class TabMixin(GridMixin):
else: else:
tab.set_path(path) tab.set_path(path)
tab_widget = self.create_tab_widget(tab) tab_widget = self.get_tab_widget(tab)
scroll, store = self.create_scroll_and_store(tab, wid) scroll, store = self.create_scroll_and_store(tab, wid)
index = notebook.append_page(scroll, tab_widget) index = notebook.append_page(scroll, tab_widget)
notebook.set_tab_detachable(scroll, True) notebook.set_tab_detachable(scroll, True)
notebook.set_tab_reorderable(scroll, True)
self.fm_controller.set_wid_and_tid(wid, tab.get_id()) self.fm_controller.set_wid_and_tid(wid, tab.get_id())
path_entry.set_text(tab.get_current_directory()) # path_entry.set_text(tab.get_current_directory())
event_system.emit("go_to_path", (tab.get_current_directory(),)) # NOTE: Not efficent if I understand how
notebook.show_all() notebook.show_all()
notebook.set_current_page(index) notebook.set_current_page(index)
ctx = notebook.get_style_context() ctx = notebook.get_style_context()
ctx.add_class("notebook-unselected-focus") ctx.add_class("notebook-unselected-focus")
notebook.set_tab_reorderable(scroll, True)
self.load_store(tab, store) self.load_store(tab, store)
self.set_window_title() event_system.emit("set_window_title", (tab.get_current_directory(),))
self.set_file_watcher(tab) self.set_file_watcher(tab)
tab_widget = None
scroll, store = None, None
index = None
notebook = None
path_entry = None
tab = None
ctx = None
def get_tab_widget(self, tab):
tab_widget = self.create_tab_widget()
tab_widget.tab = tab
tab_widget.label.set_label(f"{tab.get_end_of_path()}")
tab_widget.label.set_width_chars(len(tab.get_end_of_path()))
return tab_widget
def close_tab(self, button, eve = None): def close_tab(self, button, eve = None):
notebook = button.get_parent().get_parent() notebook = button.get_parent().get_parent()
if notebook.get_n_pages() == 1: if notebook.get_n_pages() == 1:
notebook = None
return return
tab_box = button.get_parent() tab_box = button.get_parent()
@ -72,23 +91,35 @@ class TabMixin(GridMixin):
self.builder.dereference_object(f"{wid}|{tid}|icon_grid") self.builder.dereference_object(f"{wid}|{tid}|icon_grid")
self.builder.dereference_object(f"{wid}|{tid}") self.builder.dereference_object(f"{wid}|{tid}")
iter = store.get_iter_first()
while iter:
next_iter = store.iter_next(iter)
store.unref_node(iter)
iter = next_iter
store.clear() store.clear()
icon_grid.destroy() store.run_dispose()
scroll.destroy()
tab_box.destroy()
del store icon_grid.set_model(None)
del icon_grid icon_grid.run_dispose()
del scroll scroll.run_dispose()
del tab_box tab_box.run_dispose()
del watcher
del tab iter = None
wid, tid = None, None
store = None
icon_grid = None
scroll = None
tab_box = None
watcher = None
tab = None
notebook = None
gc.collect()
if not settings_manager.is_trace_debug(): if not settings_manager.is_trace_debug():
self.fm_controller.save_state() self.fm_controller.save_state()
self.set_window_title() self.set_window_title()
gc.collect()
# NOTE: Not actually getting called even tho set in the glade file... # NOTE: Not actually getting called even tho set in the glade file...
def on_tab_dnded(self, notebook, page, x, y): def on_tab_dnded(self, notebook, page, x, y):
@ -111,15 +142,23 @@ class TabMixin(GridMixin):
if not settings_manager.is_trace_debug(): if not settings_manager.is_trace_debug():
self.fm_controller.save_state() self.fm_controller.save_state()
wid, tid = None, None
window = None
tab = None
def on_tab_switch_update(self, notebook, content = None, index = None): def on_tab_switch_update(self, notebook, content = None, index = None):
self.selected_files.clear() self.selected_files.clear()
wid, tid = content.get_children()[0].get_name().split("|") wid, tid = content.get_children()[0].get_name().split("|")
self.fm_controller.set_wid_and_tid(wid, tid) self.fm_controller.set_wid_and_tid(wid, tid)
self.set_path_text(wid, tid) self.set_path_text(wid, tid)
self.set_window_title() self.set_window_title()
wid, tid = None, None
def get_id_from_tab_box(self, tab_box): def get_id_from_tab_box(self, tab_box):
return tab_box.get_children()[2].get_text() return tab_box.tab.get_id()
def get_tab_label(self, notebook, icon_grid): def get_tab_label(self, notebook, icon_grid):
return notebook.get_tab_label(icon_grid.get_parent()).get_children()[0] return notebook.get_tab_label(icon_grid.get_parent()).get_children()[0]
@ -135,6 +174,8 @@ class TabMixin(GridMixin):
state.tab.load_directory() state.tab.load_directory()
self.load_store(state.tab, state.store) self.load_store(state.tab, state.store)
state = None
def update_tab(self, tab_label, tab, store, wid, tid): def update_tab(self, tab_label, tab, store, wid, tid):
self.load_store(tab, store) self.load_store(tab, store)
self.set_path_text(wid, tid) self.set_path_text(wid, tid)
@ -175,16 +216,38 @@ class TabMixin(GridMixin):
if isinstance(focused_obj, Gtk.Entry): if isinstance(focused_obj, Gtk.Entry):
self.process_path_menu(widget, tab, dir) self.process_path_menu(widget, tab, dir)
action = None
store = None
if path.endswith(".") or path == dir: if path.endswith(".") or path == dir:
tab_label = None
notebook = None
wid, tid = None, None
path = None
tab = None
return return
if not tab.set_path(path): if not tab.set_path(path):
tab_label = None
notebook = None
wid, tid = None, None
path = None
tab = None
return return
icon_grid = self.get_icon_grid_from_notebook(notebook, f"{wid}|{tid}") icon_grid = self.get_icon_grid_from_notebook(notebook, f"{wid}|{tid}")
icon_grid.clear_and_set_new_store() icon_grid.clear_and_set_new_store()
self.update_tab(tab_label, tab, icon_grid.get_store(), wid, tid) self.update_tab(tab_label, tab, icon_grid.get_store(), wid, tid)
action = None
wid, tid = None, None
notebook = None
store, tab_label = None, None
path = None
tab = None
icon_grid = None
def process_path_menu(self, gtk_entry, tab, dir): def process_path_menu(self, gtk_entry, tab, dir):
path_menu_buttons = self.builder.get_object("path_menu_buttons") path_menu_buttons = self.builder.get_object("path_menu_buttons")
query = gtk_entry.get_text().replace(dir, "") query = gtk_entry.get_text().replace(dir, "")
@ -201,11 +264,16 @@ class TabMixin(GridMixin):
path_menu_buttons.add(button) path_menu_buttons.add(button)
show_path_menu = True show_path_menu = True
query = None
files = None
if not show_path_menu: if not show_path_menu:
path_menu_buttons = None
event_system.emit("hide_path_menu") event_system.emit("hide_path_menu")
else: else:
event_system.emit("show_path_menu") event_system.emit("show_path_menu")
buttons = path_menu_buttons.get_children() buttons = path_menu_buttons.get_children()
path_menu_buttons = None
if len(buttons) == 1: if len(buttons) == 1:
self.slowed_focus(buttons[0]) self.slowed_focus(buttons[0])
@ -218,6 +286,7 @@ class TabMixin(GridMixin):
def do_focused_click(self, button): def do_focused_click(self, button):
button.grab_focus() button.grab_focus()
button.clicked() button.clicked()
return False
def set_path_entry(self, button = None, eve = None): def set_path_entry(self, button = None, eve = None):
self.path_auto_filled = True self.path_auto_filled = True
@ -230,6 +299,10 @@ class TabMixin(GridMixin):
path_entry.set_position(-1) path_entry.set_position(-1)
event_system.emit("hide_path_menu") event_system.emit("hide_path_menu")
state = None
path = None
path_entry = None
def show_hide_hidden_files(self): def show_hide_hidden_files(self):
wid, tid = self.fm_controller.get_active_wid_and_tid() wid, tid = self.fm_controller.get_active_wid_and_tid()
@ -237,3 +310,6 @@ class TabMixin(GridMixin):
tab.set_hiding_hidden(not tab.is_hiding_hidden()) tab.set_hiding_hidden(not tab.is_hiding_hidden())
tab.load_directory() tab.load_directory()
self.builder.get_object("refresh_tab").released() self.builder.get_object("refresh_tab").released()
wid, tid = None, None
tab = None

View File

@ -46,11 +46,19 @@ class WindowMixin(TabMixin):
self.window.set_title(f"{app_name} ~ {dir}") self.window.set_title(f"{app_name} ~ {dir}")
self.set_bottom_labels(tab) self.set_bottom_labels(tab)
wid, tid = None, None
notebook = None
tab = None
dir = None
def set_path_text(self, wid, tid): def set_path_text(self, wid, tid):
path_entry = self.builder.get_object("path_entry") path_entry = self.builder.get_object("path_entry")
tab = self.get_fm_window(wid).get_tab_by_id(tid) tab = self.get_fm_window(wid).get_tab_by_id(tid)
path_entry.set_text(tab.get_current_directory()) path_entry.set_text(tab.get_current_directory())
path_entry = None
tab = None
def grid_set_selected_items(self, icons_grid): def grid_set_selected_items(self, icons_grid):
new_items = icons_grid.get_selected_items() new_items = icons_grid.get_selected_items()
items_size = len(new_items) items_size = len(new_items)
@ -122,6 +130,10 @@ class WindowMixin(TabMixin):
self.update_tab(tab_label, state.tab, state.icon_grid.get_store(), state.wid, state.tid) self.update_tab(tab_label, state.tab, state.icon_grid.get_store(), state.wid, state.tid)
else: else:
event_system.emit("open_files") event_system.emit("open_files")
state = None
notebook = None
tab_label = None
except WindowException as e: except WindowException as e:
traceback.print_exc() traceback.print_exc()
self.display_message(settings.theming.error_color, f"{repr(e)}") self.display_message(settings.theming.error_color, f"{repr(e)}")
@ -164,6 +176,12 @@ class WindowMixin(TabMixin):
if target not in current: if target not in current:
self.fm_controller.set_wid_and_tid(wid, tid) self.fm_controller.set_wid_and_tid(wid, tid)
current = None
target = None
wid, tid = None, None
store = None
path_at_loc = None
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:
@ -177,6 +195,10 @@ class WindowMixin(TabMixin):
if from_uri != dest: if from_uri != dest:
event_system.emit("move_files", (uris, dest)) event_system.emit("move_files", (uris, dest))
Gtk.drag_finish(drag_context, True, False, time)
return
Gtk.drag_finish(drag_context, False, False, time)
def create_new_tab_notebook(self, widget=None, wid=None, path=None): def create_new_tab_notebook(self, widget=None, wid=None, path=None):
self.create_tab(wid, None, path) self.create_tab(wid, None, path)

View File

@ -6,6 +6,7 @@ gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0') gi.require_version('Gdk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
from gi.repository import Gdk from gi.repository import Gdk
from gi.repository import GLib
# Application imports # Application imports
from .mixins.ui.pane_mixin import PaneMixin from .mixins.ui.pane_mixin import PaneMixin
@ -34,18 +35,20 @@ class UIMixin(PaneMixin, WindowMixin):
nickname = session["window"]["Nickname"] nickname = session["window"]["Nickname"]
tabs = session["window"]["tabs"] tabs = session["window"]["tabs"]
isHidden = True if session["window"]["isHidden"] == "True" else False isHidden = True if session["window"]["isHidden"] == "True" else False
event_system.emit("load_files_view_state", (nickname, tabs)) event_system.emit_and_await("load_files_view_state", (nickname, tabs, isHidden))
@daemon_threaded
def _focus_last_visible_notebook(self, icon_grid): def _focus_last_visible_notebook(self, icon_grid):
import time import time
window = settings_manager.get_main_window() window = settings_manager.get_main_window()
while not window.is_visible() and not window.get_realized(): while not window.is_visible() and not window.get_realized():
time.sleep(0.1) time.sleep(0.2)
icon_grid.event(Gdk.Event().new(type = Gdk.EventType.BUTTON_RELEASE)) icon_grid.event(Gdk.Event().new(type = Gdk.EventType.BUTTON_RELEASE))
window = None
def _current_loading_process(self, session_json = None): def _current_loading_process(self, session_json = None):
if session_json: if session_json:
for j, value in enumerate(session_json): for j, value in enumerate(session_json):
@ -77,7 +80,7 @@ class UIMixin(PaneMixin, WindowMixin):
scroll_win = notebook.get_children()[-1] scroll_win = notebook.get_children()[-1]
icon_grid = scroll_win.get_children()[0] icon_grid = scroll_win.get_children()[0]
self._focus_last_visible_notebook(icon_grid) GLib.Thread("", self._focus_last_visible_notebook, icon_grid)
except UIMixinException as e: except UIMixinException as e:
logger.info("\n: The saved session might be missing window data! :\nLocation: ~/.config/solarfm/session.json\nFix: Back it up and delete it to reset.\n") logger.info("\n: The saved session might be missing window data! :\nLocation: ~/.config/solarfm/session.json\nFix: Back it up and delete it to reset.\n")
logger.debug(repr(e)) logger.debug(repr(e))

View File

@ -16,10 +16,7 @@ class ContextMenuWidget(Gtk.Menu):
def __init__(self): def __init__(self):
super(ContextMenuWidget, self).__init__() super(ContextMenuWidget, self).__init__()
self.builder = settings_manager.get_builder() self._builder = Gtk.Builder()
self._builder = Gtk.Builder()
self._context_menu_data = settings_manager.get_context_menu_data()
self._window = settings_manager.get_main_window()
self._setup_styling() self._setup_styling()
self._setup_signals() self._setup_signals()
@ -32,24 +29,57 @@ class ContextMenuWidget(Gtk.Menu):
def _setup_signals(self): def _setup_signals(self):
event_system.subscribe("show_context_menu", self.show_context_menu) event_system.subscribe("show_context_menu", self.show_context_menu)
event_system.subscribe("hide_context_menu", self.hide_context_menu) event_system.subscribe("hide_context_menu", self.hide_context_menu)
settings_manager.register_signals_to_builder([self,], self._builder) settings_manager.register_signals_to_builder(self, self._builder)
def _load_widgets(self): def _load_widgets(self):
self.builder = settings_manager.get_builder()
self._window = settings_manager.get_main_window()
self._context_menu_data = settings_manager.get_context_menu_data()
self.builder.expose_object("context_menu", self)
self.build_context_menu() self.build_context_menu()
def _emit(self, menu_item, type): def _emit(self, menu_item, type):
event_system.emit("do_action_from_menu_controls", type) event_system.emit("do_action_from_menu_controls", type)
def make_submenu(self, name, data, keys): def build_context_menu(self) -> None:
data = self._context_menu_data
plugins_entry = None
for key, value in data.items():
entry = self.make_menu_item(key, value)
self.append(entry)
if key == "Plugins":
plugins_entry = entry
self.attach_to_widget(self._window, None)
self.show_all()
if plugins_entry:
self.builder.expose_object("context_menu_plugins", plugins_entry.get_submenu())
def make_menu_item(self, label, data) -> Gtk.MenuItem:
if isinstance(data, dict):
return self.make_submenu(label, data)
elif isinstance(data, list):
entry = Gtk.ImageMenuItem(label)
icon = getattr(Gtk, f"{data[0]}")
entry.set_image( Gtk.Image(stock=icon) )
entry.set_always_show_image(True)
entry.connect("activate", self._emit, (data[1]))
return entry
def make_submenu(self, name, data):
menu = Gtk.Menu() menu = Gtk.Menu()
menu_item = Gtk.MenuItem(name) menu_item = Gtk.MenuItem(name)
for key in keys: for key, value in data.items():
if isinstance(data, dict): if isinstance(data, dict):
entry = self.make_menu_item(key, data[key]) entry = self.make_menu_item(key, value)
elif isinstance(data, list): elif isinstance(data, list):
entry = self.make_menu_item(key, data) entry = self.make_menu_item(key, value)
else: else:
continue continue
@ -58,36 +88,9 @@ class ContextMenuWidget(Gtk.Menu):
menu_item.set_submenu(menu) menu_item.set_submenu(menu)
return menu_item return menu_item
def make_menu_item(self, name, data) -> Gtk.MenuItem:
if isinstance(data, dict):
return self.make_submenu(name, data, data.keys())
elif isinstance(data, list):
entry = Gtk.ImageMenuItem(name)
icon = getattr(Gtk, f"{data[0]}")
entry.set_image( Gtk.Image(stock=icon) )
entry.set_always_show_image(True)
entry.connect("activate", self._emit, (data[1]))
return entry
def build_context_menu(self) -> None: def show_context_menu(self, widget = None, eve = None):
data = self._context_menu_data
dkeys = data.keys()
plugins_entry = None
for dkey in dkeys:
entry = self.make_menu_item(dkey, data[dkey])
self.append(entry)
if dkey == "Plugins":
plugins_entry = entry
self.attach_to_widget(self._window, None)
self.show_all()
self.builder.expose_object("context_menu", self)
if plugins_entry:
self.builder.expose_object("context_menu_plugins", plugins_entry.get_submenu())
def show_context_menu(self, widget=None, eve=None):
self.builder.get_object("context_menu").popup_at_pointer(None) self.builder.get_object("context_menu").popup_at_pointer(None)
def hide_context_menu(self, widget=None, eve=None): def hide_context_menu(self, widget = None, eve = None):
self.builder.get_object("context_menu").popdown() self.builder.get_object("context_menu").popdown()

View File

@ -39,11 +39,10 @@ class AboutWidget:
self.about_page = self._builder.get_object("about_page") self.about_page = self._builder.get_object("about_page")
builder.expose_object(f"about_page", self.about_page) builder.expose_object(f"about_page", self.about_page)
def show_about_page(self, widget = None, eve = None):
def show_about_page(self, widget=None, eve=None):
response = self.about_page.run() response = self.about_page.run()
if response in [Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT]: if response in [Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT]:
self.hide_about_page() self.hide_about_page()
def hide_about_page(self, widget=None, eve=None): def hide_about_page(self, widget = None, eve = None):
self.about_page.hide() self.about_page.hide()

View File

@ -50,6 +50,9 @@ class RenameWidget:
def show_rename_file_menu(self, widget=None, eve=None): def show_rename_file_menu(self, widget=None, eve=None):
if widget: if widget:
widget.grab_focus() widget.grab_focus()
end_i = widget.get_text().rfind(".")
if end_i > 0:
widget.select_region(0, end_i)
response = self._rename_file_menu.run() response = self._rename_file_menu.run()
if response == Gtk.ResponseType.CLOSE: if response == Gtk.ResponseType.CLOSE:

View File

@ -1,11 +1,9 @@
# Python imports # Python imports
# Lib imports # Lib imports
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
# Application imports # Application imports
from ...sfm_builder import SFMBuilder
from ...mixins.signals.file_action_signals_mixin import FileActionSignalsMixin from ...mixins.signals.file_action_signals_mixin import FileActionSignalsMixin
from .window_mixin import WindowMixin from .window_mixin import WindowMixin
@ -27,7 +25,8 @@ class FilesWidget(FileActionSignalsMixin, WindowMixin):
self.INDEX = self.ccount self.INDEX = self.ccount
self.NAME = f"window_{self.INDEX}" self.NAME = f"window_{self.INDEX}"
self.builder = Gtk.Builder() self.builder = SFMBuilder()
self.files_view = None self.files_view = None
self.fm_controller = None self.fm_controller = None
@ -41,20 +40,21 @@ class FilesWidget(FileActionSignalsMixin, WindowMixin):
... ...
def _setup_signals(self): def _setup_signals(self):
settings_manager.register_signals_to_builder([self,], self.builder) settings_manager.register_signals_to_builder([self], self.builder)
def _subscribe_to_events(self): def _subscribe_to_events(self):
event_system.subscribe("load_files_view_state", self._load_files_view_state) event_system.subscribe("load_files_view_state", self._load_files_view_state)
event_system.subscribe("get_files_view_icon_grid", self._get_files_view_icon_grid) event_system.subscribe("get_files_view_icon_grid", self._get_files_view_icon_grid)
def _load_widgets(self): def _load_widgets(self):
_builder = settings_manager.get_builder() _builder = settings_manager.get_builder()
self.files_view = _builder.get_object(f"{self.NAME}") self.files_view = _builder.get_object(f"{self.NAME}")
self.files_view.set_group_name("files_widget") self.files_view.set_group_name("files_widget")
self.builder.expose_object(f"{self.NAME}", self.files_view) self.builder.expose_object(f"{self.NAME}", self.files_view)
def _load_files_view_state(self, win_name = None, tabs = None): def _load_files_view_state(self, win_name = None, tabs = None, isHidden = False):
if win_name == self.NAME: if win_name == self.NAME:
if tabs: if tabs:
for tab in tabs: for tab in tabs:
@ -62,6 +62,9 @@ class FilesWidget(FileActionSignalsMixin, WindowMixin):
else: else:
self.create_new_tab_notebook(None, self.INDEX, None) self.create_new_tab_notebook(None, self.INDEX, None)
if isHidden:
self.files_view.hide()
def _get_files_view_icon_grid(self, win_index = None, tid = None): def _get_files_view_icon_grid(self, win_index = None, tid = None):
if win_index == str(self.INDEX): if win_index == str(self.INDEX):
return self.builder.get_object(f"{self.INDEX}|{tid}|icon_grid", use_gtk = False) return self.builder.get_object(f"{self.INDEX}|{tid}|icon_grid", use_gtk = False)

View File

@ -7,6 +7,7 @@ import gi
gi.require_version("Gtk", "3.0") gi.require_version("Gtk", "3.0")
from gi.repository import Gtk from gi.repository import Gtk
from gi.repository import GLib from gi.repository import GLib
from gi.repository import Gio
# Application imports # Application imports
from ...widgets.tab_header_widget import TabHeaderWidget from ...widgets.tab_header_widget import TabHeaderWidget
@ -19,6 +20,17 @@ class GridMixin:
"""docstring for GridMixin""" """docstring for GridMixin"""
def load_store(self, tab, store, save_state = False, use_generator = False): def load_store(self, tab, store, save_state = False, use_generator = False):
# dir = tab.get_current_directory()
# file = Gio.File.new_for_path(dir)
# dir_list = Gtk.DirectoryList.new("standard::*", file)
# store.set(dir_list)
# file = Gio.File.new_for_path(dir)
# for file in file.enumerate_children("standard::*", Gio.FILE_ATTRIBUTE_STANDARD_NAME, None):
# store.append(file)
# return
dir = tab.get_current_directory() dir = tab.get_current_directory()
files = tab.get_files() files = tab.get_files()
@ -26,60 +38,76 @@ class GridMixin:
store.append([None, file[0]]) store.append([None, file[0]])
Gtk.main_iteration() Gtk.main_iteration()
if use_generator: self.generate_icons(tab, store, dir, files)
# NOTE: tab > icon > _get_system_thumbnail_gtk_thread must not be used # GLib.Thread("", self.generate_icons, tab, store, dir, files)
# as the attempted promotion back to gtk threading stalls the generator. (We're already in main gtk thread)
for i, icon in enumerate( self.create_icons_generator(tab, dir, files) ):
self.load_icon(i, store, icon)
else:
# for i, file in enumerate(files):
# self.create_icon(i, tab, store, dir, file[0])
try:
loop = asyncio.get_running_loop()
except RuntimeError:
loop = None
if loop and loop.is_running():
loop.create_task( self.create_icons(tab, store, dir, files) )
else:
asyncio.run( self.create_icons(tab, store, dir, files) )
# NOTE: Not likely called often from here but it could be useful # NOTE: Not likely called often from here but it could be useful
if save_state and not trace_debug: if save_state and not trace_debug:
self.fm_controller.save_state() self.fm_controller.save_state()
async def create_icons(self, tab, store, dir, files): dir = None
tasks = [self.update_store(i, store, dir, tab, file[0]) for i, file in enumerate(files)] files = None
await asyncio.gather(*tasks)
async def load_icon(self, i, store, icon): @daemon_threaded
self.update_store(i, store, icon) def generate_icons(self, tab, store, dir, files):
for i, file in enumerate(files):
# GLib.Thread(f"{i}", self.make_and_load_icon, i, store, tab, dir, file[0])
self.make_and_load_icon( i, store, tab, dir, file[0])
async def update_store(self, i, store, dir, tab, file): def update_store(self, i, store, icon):
icon = tab.create_icon(dir, file)
itr = store.get_iter(i) itr = store.get_iter(i)
GLib.idle_add(self.insert_store, store, itr, icon)
itr = None
@daemon_threaded
def make_and_load_icon(self, i, store, tab, dir, file):
icon = tab.create_icon(dir, file)
self.update_store(i, store, icon)
icon = None
def get_icon(self, tab, dir, file):
tab.create_icon(dir, file)
# @daemon_threaded
# def generate_icons(self, tab, store, dir, files):
# try:
# loop = asyncio.get_running_loop()
# except RuntimeError:
# loop = None
# if loop and loop.is_running():
# loop = asyncio.get_event_loop()
# loop.create_task( self.create_icons(tab, store, dir, files) )
# else:
# asyncio.run( self.create_icons(tab, store, dir, files) )
# async def create_icons(self, tab, store, dir, files):
# icons = [self.get_icon(tab, dir, file[0]) for file in files]
# data = await asyncio.gather(*icons)
# tasks = [self.update_store(i, store, icon) for i, icon in enumerate(data)]
# asyncio.gather(*tasks)
# async def update_store(self, i, store, icon):
# itr = store.get_iter(i)
# GLib.idle_add(self.insert_store, store, itr, icon)
# async def get_icon(self, tab, dir, file):
# return tab.create_icon(dir, file)
def insert_store(self, store, itr, icon):
store.set_value(itr, 0, icon) store.set_value(itr, 0, icon)
def create_icons_generator(self, tab, dir, files): # Note: If the function returns GLib.SOURCE_REMOVE or False it is automatically removed from the list of event sources and will not be called again.
for file in files: return False
icon = tab.create_icon(dir, file[0])
yield icon
# @daemon_threaded def do_ui_update(self):
# def create_icon(self, i, tab, store, dir, file): Gtk.main_iteration()
# icon = tab.create_icon(dir, file) return False
# GLib.idle_add(self.update_store, *(i, store, icon,))
#
# @daemon_threaded
# def load_icon(self, i, store, icon):
# GLib.idle_add(self.update_store, *(i, store, icon,))
#
# def update_store(self, i, store, icon):
# itr = store.get_iter(i)
# store.set_value(itr, 0, icon)
def create_tab_widget(self, tab): def create_tab_widget(self):
return TabHeaderWidget(tab, self.close_tab) return TabHeaderWidget(self.close_tab)
def create_scroll_and_store(self, tab, wid, use_tree_view = False): def create_scroll_and_store(self, tab, wid, use_tree_view = False):
scroll = Gtk.ScrolledWindow() scroll = Gtk.ScrolledWindow()
@ -137,3 +165,10 @@ class GridMixin:
tab_label = notebook.get_tab_label(obj).get_children()[0] tab_label = notebook.get_tab_label(obj).get_children()[0]
return store, tab_label return store, tab_label
def get_icon_grid_from_notebook(self, notebook, _name):
for obj in notebook.get_children():
icon_grid = obj.get_children()[0]
name = icon_grid.get_name()
if name == _name:
return icon_grid

View File

@ -34,29 +34,46 @@ class TabMixin(GridMixin):
else: else:
tab.set_path(path) tab.set_path(path)
tab_widget = self.create_tab_widget(tab) tab_widget = self.get_tab_widget(tab)
scroll, store = self.create_scroll_and_store(tab, wid) scroll, store = self.create_scroll_and_store(tab, wid)
index = notebook.append_page(scroll, tab_widget) index = notebook.append_page(scroll, tab_widget)
notebook.set_tab_detachable(scroll, True) notebook.set_tab_detachable(scroll, True)
notebook.set_tab_reorderable(scroll, True)
self.fm_controller.set_wid_and_tid(wid, tab.get_id()) self.fm_controller.set_wid_and_tid(wid, tab.get_id())
event_system.emit("go_to_path", (tab.get_current_directory(),)) # NOTE: Not efficent if I understand how
# path_entry.set_text(tab.get_current_directory()) # path_entry.set_text(tab.get_current_directory())
event_system.emit("go_to_path", (tab.get_current_directory(),)) # NOTE: Not efficent if I understand how
notebook.show_all() notebook.show_all()
notebook.set_current_page(index) notebook.set_current_page(index)
ctx = notebook.get_style_context() ctx = notebook.get_style_context()
ctx.add_class("notebook-unselected-focus") ctx.add_class("notebook-unselected-focus")
notebook.set_tab_reorderable(scroll, True)
self.load_store(tab, store) self.load_store(tab, store)
# self.set_window_title()
event_system.emit("set_window_title", (tab.get_current_directory(),)) event_system.emit("set_window_title", (tab.get_current_directory(),))
self.set_file_watcher(tab) self.set_file_watcher(tab)
tab_widget = None
scroll, store = None, None
index = None
notebook = None
# path_entry = None
tab = None
ctx = None
def get_tab_widget(self, tab):
tab_widget = self.create_tab_widget()
tab_widget.tab_id = tab.get_id()
tab_widget.label.set_label(f"{tab.get_end_of_path()}")
tab_widget.label.set_width_chars(len(tab.get_end_of_path()))
return tab_widget
def close_tab(self, button, eve = None): def close_tab(self, button, eve = None):
notebook = button.get_parent().get_parent() notebook = button.get_parent().get_parent()
if notebook.get_n_pages() == 1: if notebook.get_n_pages() == 1:
notebook = None
return return
tab_box = button.get_parent() tab_box = button.get_parent()
@ -74,28 +91,37 @@ class TabMixin(GridMixin):
self.builder.dereference_object(f"{wid}|{tid}|icon_grid") self.builder.dereference_object(f"{wid}|{tid}|icon_grid")
self.builder.dereference_object(f"{wid}|{tid}") self.builder.dereference_object(f"{wid}|{tid}")
iter = store.get_iter_first()
while iter:
next_iter = store.iter_next(iter)
store.unref_node(iter)
iter = next_iter
store.clear() store.clear()
# store.run_dispose() store.run_dispose()
icon_grid.destroy()
# icon_grid.run_dispose()
scroll.destroy()
#scroll.run_dispose()
tab_box.destroy()
#tab_box.run_dispose()
del store icon_grid.set_model(None)
del icon_grid icon_grid.run_dispose()
del scroll scroll.run_dispose()
del tab_box tab_box.run_dispose()
del watcher
del tab iter = None
wid, tid = None, None
store = None
icon_grid = None
scroll = None
tab_box = None
watcher = None
tab = None
notebook = None
gc.collect()
if not settings_manager.is_trace_debug(): if not settings_manager.is_trace_debug():
self.fm_controller.save_state() self.fm_controller.save_state()
self.set_window_title() self.set_window_title()
gc.collect()
# NOTE: Not actually getting called even tho set in the glade file... # NOTE: Not actually getting called even tho set in the glade file...
def on_tab_dnded(self, notebook, page, x, y): def on_tab_dnded(self, notebook, page, x, y):
... ...
@ -117,15 +143,22 @@ class TabMixin(GridMixin):
if not settings_manager.is_trace_debug(): if not settings_manager.is_trace_debug():
self.fm_controller.save_state() self.fm_controller.save_state()
wid, tid = None, None
window = None
tab = None
def on_tab_switch_update(self, notebook, content = None, index = None): def on_tab_switch_update(self, notebook, content = None, index = None):
self.selected_files.clear() self.selected_files.clear()
wid, tid = content.get_children()[0].get_name().split("|") wid, tid = content.get_children()[0].tab.get_name().split("|")
self.fm_controller.set_wid_and_tid(wid, tid) self.fm_controller.set_wid_and_tid(wid, tid)
self.set_path_text(wid, tid) self.set_path_text(wid, tid)
self.set_window_title() self.set_window_title()
wid, tid = None, None
def get_id_from_tab_box(self, tab_box): def get_id_from_tab_box(self, tab_box):
return tab_box.get_children()[2].get_text() return tab_box.tab.get_id()
def get_tab_label(self, notebook, icon_grid): def get_tab_label(self, notebook, icon_grid):
return notebook.get_tab_label(icon_grid.get_parent()).get_children()[0] return notebook.get_tab_label(icon_grid.get_parent()).get_children()[0]
@ -141,6 +174,8 @@ class TabMixin(GridMixin):
state.tab.load_directory() state.tab.load_directory()
self.load_store(state.tab, state.store) self.load_store(state.tab, state.store)
state = None
def update_tab(self, tab_label, tab, store, wid, tid): def update_tab(self, tab_label, tab, store, wid, tid):
self.load_store(tab, store) self.load_store(tab, store)
self.set_path_text(wid, tid) self.set_path_text(wid, tid)
@ -181,16 +216,38 @@ class TabMixin(GridMixin):
if isinstance(focused_obj, Gtk.Entry): if isinstance(focused_obj, Gtk.Entry):
self.process_path_menu(widget, tab, dir) self.process_path_menu(widget, tab, dir)
action = None
store = None
if path.endswith(".") or path == dir: if path.endswith(".") or path == dir:
tab_label = None
notebook = None
wid, tid = None, None
path = None
tab = None
return return
if not tab.set_path(path): if not tab.set_path(path):
tab_label = None
notebook = None
wid, tid = None, None
path = None
tab = None
return return
icon_grid = self.get_icon_grid_from_notebook(notebook, f"{wid}|{tid}") icon_grid = self.get_icon_grid_from_notebook(notebook, f"{wid}|{tid}")
icon_grid.clear_and_set_new_store() icon_grid.clear_and_set_new_store()
self.update_tab(tab_label, tab, store, wid, tid) self.update_tab(tab_label, tab, store, wid, tid)
action = None
wid, tid = None, None
notebook = None
store, tab_label = None, None
path = None
tab = None
icon_grid = None
def process_path_menu(self, gtk_entry, tab, dir): def process_path_menu(self, gtk_entry, tab, dir):
path_menu_buttons = self.builder.get_object("path_menu_buttons") path_menu_buttons = self.builder.get_object("path_menu_buttons")
query = gtk_entry.get_text().replace(dir, "") query = gtk_entry.get_text().replace(dir, "")
@ -207,6 +264,10 @@ class TabMixin(GridMixin):
path_menu_buttons.add(button) path_menu_buttons.add(button)
show_path_menu = True show_path_menu = True
path_menu_buttons = None
query = None
files = None
if not show_path_menu: if not show_path_menu:
event_system.emit("hide_path_menu") event_system.emit("hide_path_menu")
else: else:
@ -225,6 +286,8 @@ class TabMixin(GridMixin):
button.grab_focus() button.grab_focus()
button.clicked() button.clicked()
return False
def set_path_entry(self, button = None, eve = None): def set_path_entry(self, button = None, eve = None):
self.path_auto_filled = True self.path_auto_filled = True
state = self.get_current_state() state = self.get_current_state()
@ -236,9 +299,16 @@ class TabMixin(GridMixin):
path_entry.set_position(-1) path_entry.set_position(-1)
event_system.emit("hide_path_menu") event_system.emit("hide_path_menu")
state = None
path = None
path_entry = None
def show_hide_hidden_files(self): def show_hide_hidden_files(self):
wid, tid = self.fm_controller.get_active_wid_and_tid() wid, tid = self.fm_controller.get_active_wid_and_tid()
tab = self.get_fm_window(wid).get_tab_by_id(tid) tab = self.get_fm_window(wid).get_tab_by_id(tid)
tab.set_hiding_hidden(not tab.is_hiding_hidden()) tab.set_hiding_hidden(not tab.is_hiding_hidden())
tab.load_directory() tab.load_directory()
self.builder.get_object("refresh_tab").released() self.builder.get_object("refresh_tab").released()
wid, tid = None, None
tab = None

View File

@ -42,10 +42,17 @@ class WindowMixin(TabMixin):
event_system.emit("set_window_title", (dir,)) event_system.emit("set_window_title", (dir,))
self.set_bottom_labels(tab) self.set_bottom_labels(tab)
wid, tid = None, None
notebook = None
tab = None
dir = None
def set_path_text(self, wid, tid): def set_path_text(self, wid, tid):
tab = self.get_fm_window(wid).get_tab_by_id(tid) tab = self.get_fm_window(wid).get_tab_by_id(tid)
event_system.emit("go_to_path", (tab.get_current_directory(),)) event_system.emit("go_to_path", (tab.get_current_directory(),))
tab = None
def grid_set_selected_items(self, icons_grid): def grid_set_selected_items(self, icons_grid):
new_items = icons_grid.get_selected_items() new_items = icons_grid.get_selected_items()
items_size = len(new_items) items_size = len(new_items)
@ -160,6 +167,12 @@ class WindowMixin(TabMixin):
if target not in current: if target not in current:
self.fm_controller.set_wid_and_tid(wid, tid) self.fm_controller.set_wid_and_tid(wid, tid)
current = None
target = None
wid, tid = None, None
store = None
path_at_loc = None
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:
@ -173,6 +186,10 @@ class WindowMixin(TabMixin):
if from_uri != dest: if from_uri != dest:
event_system.emit("move_files", (uris, dest)) event_system.emit("move_files", (uris, dest))
Gtk.drag_finish(drag_context, True, False, time)
return
Gtk.drag_finish(drag_context, False, False, time)
def create_new_tab_notebook(self, widget=None, wid=None, path=None): def create_new_tab_notebook(self, widget=None, wid=None, path=None):
self.create_tab(wid, None, path) self.create_tab(wid, None, path)

View File

@ -75,6 +75,20 @@ class IconGridWidget(Gtk.IconView):
return self.get_model() return self.get_model()
def clear_and_set_new_store(self): def clear_and_set_new_store(self):
store = self.get_model()
if store:
iter = store.get_iter_first()
while iter:
next_iter = store.iter_next(iter)
store.unref_node(iter)
iter = next_iter
store.clear()
store.run_dispose()
store = None
self.set_model(None) self.set_model(None)
store = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str or None) store = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str or None)
# store = Gtk.ListStore(Gtk.DirectoryList)
self.set_model(store) self.set_model(store)
store = None

View File

@ -59,7 +59,7 @@ class IconTreeWidget(Gtk.TreeView):
name = Gtk.CellRendererText() name = Gtk.CellRendererText()
selec = self.get_selection() selec = self.get_selection()
self.set_model(store) self.set_model(self._store)
selec.set_mode(3) selec.set_mode(3)
column.pack_start(icon, False) column.pack_start(icon, False)

View File

@ -65,19 +65,20 @@ class IOWidget(Gtk.Box):
logger.info(f"Canceling: [{self._action}] of {self._basename} ...") logger.info(f"Canceling: [{self._action}] of {self._basename} ...")
eve.cancel() eve.cancel()
def update_progress(self, current, total, eve=None): def update_progress(self, current, total, eve = None):
self.progress.set_fraction(current/total) self.progress.set_fraction(current/total)
def finish_callback(self, file, task=None, eve=None): def finish_callback(self, file, task = None, eve = None):
if task.had_error():
logger.info(f"{self._action} of {self._basename} cancelled/failed...")
return
if self._action == "move" or self._action == "rename": if self._action == "move" or self._action == "rename":
status = self._file.move_finish(task) status = self._file.move_finish(task)
if self._action == "copy": if self._action == "copy":
status = self._file.copy_finish(task) status = self._file.copy_finish(task)
if status: self.delete_self()
self.delete_self()
else:
logger.info(f"{self._action} of {self._basename} failed...")
def delete_self(self, widget=None, eve=None): def delete_self(self, widget = None, eve = None):
self.get_parent().remove(self) self.get_parent().remove(self)

View File

@ -61,7 +61,6 @@ class MessagePopupWidget(Gtk.Popover):
scroll_window.set_hexpand(True) scroll_window.set_hexpand(True)
vbox.set_orientation(Gtk.Orientation.VERTICAL) vbox.set_orientation(Gtk.Orientation.VERTICAL)
self.builder.expose_object(f"message_popup_widget", self)
self.builder.expose_object(f"message_text_view", message_text_view) self.builder.expose_object(f"message_text_view", message_text_view)
scroll_window.add(message_text_view) scroll_window.add(message_text_view)
@ -103,7 +102,7 @@ class MessagePopupWidget(Gtk.Popover):
self.popup() self.popup()
self.hide_message_timeout(seconds) self.hide_message_timeout(seconds)
@threaded @daemon_threaded
def hide_message_timeout(self, seconds=3): def hide_message_timeout(self, seconds=3):
time.sleep(seconds) time.sleep(seconds)
GLib.idle_add(event_system.emit, ("hide_messages_popup")) GLib.idle_add(event_system.emit, ("hide_messages_popup"))

View File

@ -29,16 +29,16 @@ class PathMenuPopupWidget(Gtk.Popover):
self.set_relative_to(path_entry) self.set_relative_to(path_entry)
self.set_modal(False) self.set_modal(False)
self.set_position(Gtk.PositionType.BOTTOM) self.set_position(Gtk.PositionType.BOTTOM)
self.set_size_request(240, 420) self.set_size_request(480, 420)
def _setup_signals(self): def _setup_signals(self):
event_system.subscribe("show_path_menu", self.show_path_menu) event_system.subscribe("show_path_menu", self.show_path_menu)
event_system.subscribe("hide_path_menu", self.hide_path_menu) event_system.subscribe("hide_path_menu", self.hide_path_menu)
def _load_widgets(self): def _load_widgets(self):
path_menu_buttons = Gtk.ButtonBox() scroll_window = Gtk.ScrolledWindow()
scroll_window = Gtk.ScrolledWindow() view_port = Gtk.Viewport()
view_port = Gtk.Viewport() path_menu_buttons = Gtk.Box()
scroll_window.set_vexpand(True) scroll_window.set_vexpand(True)
scroll_window.set_hexpand(True) scroll_window.set_hexpand(True)
@ -47,12 +47,13 @@ class PathMenuPopupWidget(Gtk.Popover):
self.builder.expose_object(f"path_menu_buttons", path_menu_buttons) self.builder.expose_object(f"path_menu_buttons", path_menu_buttons)
view_port.add(path_menu_buttons) view_port.add(path_menu_buttons)
scroll_window.add(view_port) scroll_window.add(view_port)
scroll_window.show_all()
self.add(scroll_window) self.add(scroll_window)
scroll_window.show_all()
def show_path_menu(self, widget=None, eve=None):
def show_path_menu(self, widget = None, eve = None):
self.popup() self.popup()
def hide_path_menu(self, widget=None, eve=None): def hide_path_menu(self, widget = None, eve = None):
self.popdown() self.popdown()

View File

@ -9,14 +9,12 @@ from gi.repository import Gtk
class TabHeaderWidget(Gtk.Box): class TabHeaderWidget(Gtk.Box):
"""docstring for TabHeaderWidget""" """docstring for TabHeaderWidget"""
def __init__(self, tab, close_tab): def __init__(self, close_tab):
super(TabHeaderWidget, self).__init__() super(TabHeaderWidget, self).__init__()
self._tab = tab
self._close_tab = close_tab # NOTE: Close method in tab_mixin self._close_tab = close_tab # NOTE: Close method in tab_mixin
self._setup_styling() self._setup_styling()
@ -32,25 +30,19 @@ class TabHeaderWidget(Gtk.Box):
... ...
def _load_widgets(self): def _load_widgets(self):
label = Gtk.Label() self.label = Gtk.Label()
tid = Gtk.Label()
close = Gtk.Button() close = Gtk.Button()
icon = Gtk.Image(stock=Gtk.STOCK_CLOSE) icon = Gtk.Image(stock=Gtk.STOCK_CLOSE)
label.set_label(f"{self._tab.get_end_of_path()}") self.label.set_xalign(0.0)
label.set_width_chars(len(self._tab.get_end_of_path())) self.label.set_margin_left(25)
label.set_xalign(0.0) self.label.set_margin_right(25)
label.set_margin_left(25) self.label.set_hexpand(True)
label.set_margin_right(25)
label.set_hexpand(True)
tid.set_label(f"{self._tab.get_id()}")
close.connect("released", self._close_tab) close.connect("released", self._close_tab)
close.add(icon) close.add(icon)
self.add(label) self.add(self.label)
self.add(close) self.add(close)
self.add(tid)
self.show_all() self.show_all()
tid.hide()

View File

@ -1,5 +1,4 @@
# Python imports # Python imports
import time
import signal import signal
# Lib imports # Lib imports
@ -10,6 +9,7 @@ gi.require_version('Gdk', '3.0')
from gi.repository import Gtk from gi.repository import Gtk
from gi.repository import Gdk from gi.repository import Gdk
from gi.repository import GLib from gi.repository import GLib
from gi.repository import GObject
# Application imports # Application imports
from core.controller import Controller from core.controller import Controller
@ -24,18 +24,20 @@ class Window(Gtk.ApplicationWindow):
"""docstring for Window.""" """docstring for Window."""
def __init__(self, args, unknownargs): def __init__(self, args, unknownargs):
super(Window, self).__init__() GObject.threads_init()
self._controller = None super(Window, self).__init__()
settings_manager.set_main_window(self) settings_manager.set_main_window(self)
self._set_window_data() self._controller = None
self._setup_styling() self._setup_styling()
self._setup_signals() self._setup_signals()
self._subscribe_to_events() self._subscribe_to_events()
self._load_widgets(args, unknownargs) self._load_widgets(args, unknownargs)
self._set_window_data()
self._set_size_constraints()
self.show() self.show()
@ -66,6 +68,18 @@ class Window(Gtk.ApplicationWindow):
self.add( self._controller.get_core_widget() ) self.add( self._controller.get_core_widget() )
def _set_size_constraints(self):
_window_x = settings.config.main_window_x
_window_y = settings.config.main_window_y
_min_width = settings.config.main_window_min_width
_min_height = settings.config.main_window_min_height
_width = settings.config.main_window_width
_height = settings.config.main_window_height
self.move(_window_x, _window_y - 28)
self.set_size_request(_min_width, _min_height)
self.set_default_size(_width, _height)
def _set_window_data(self) -> None: def _set_window_data(self) -> None:
screen = self.get_screen() screen = self.get_screen()
visual = screen.get_rgba_visual() visual = screen.get_rgba_visual()
@ -73,7 +87,7 @@ class Window(Gtk.ApplicationWindow):
if visual != None and screen.is_composited(): if visual != None and screen.is_composited():
self.set_visual(visual) self.set_visual(visual)
self.set_app_paintable(True) self.set_app_paintable(True)
self.connect("draw", self._area_draw) # self.connect("draw", self._area_draw)
# bind css file # bind css file
cssProvider = Gtk.CssProvider() cssProvider = Gtk.CssProvider()
@ -94,5 +108,18 @@ class Window(Gtk.ApplicationWindow):
def _tear_down(self, widget = None, eve = None): def _tear_down(self, widget = None, eve = None):
event_system.emit("shutting_down") event_system.emit("shutting_down")
size = self.get_size()
pos = self.get_position()
settings_manager.set_main_window_width(size.width)
settings_manager.set_main_window_height(size.height)
settings_manager.set_main_window_x(pos.root_x)
settings_manager.set_main_window_y(pos.root_y)
settings_manager.save_settings()
settings_manager.clear_pid() settings_manager.clear_pid()
Gtk.main_quit() Gtk.main_quit()
def main(self):
Gtk.main()

View File

@ -15,32 +15,37 @@ class ManifestProcessorException(Exception):
... ...
@dataclass(slots=True) @dataclass(slots = True)
class PluginInfo: class PluginInfo:
path: str = None path: str = None
name: str = None name: str = None
author: str = None author: str = None
version: str = None version: str = None
support: str = None support: str = None
requests:{} = None requests:{} = None
reference: type = None reference: type = None
pre_launch: bool = False
class ManifestProcessor: class ManifestProcessor:
def __init__(self, path, builder): def __init__(self, path, builder):
manifest = join(path, "manifest.json") manifest_pth = join(path, "manifest.json")
if not os.path.exists(manifest): if not os.path.exists(manifest_pth):
raise ManifestProcessorException("Invalid Plugin Structure: Plugin doesn't have 'manifest.json'. Aboarting load...") raise ManifestProcessorException("Invalid Plugin Structure: Plugin doesn't have 'manifest.json'. Aboarting load...")
self._path = path self._path = path
self._builder = builder self._builder = builder
with open(manifest) as f: with open(manifest_pth) as f:
data = json.load(f) data = json.load(f)
self._manifest = data["manifest"] self._manifest = data["manifest"]
self._plugin = self.collect_info() self._plugin = self.collect_info()
def is_pre_launch(self) -> bool:
return self._plugin.pre_launch
def collect_info(self) -> PluginInfo: def collect_info(self) -> PluginInfo:
plugin = PluginInfo() plugin = PluginInfo()
plugin.path = self._path plugin.path = self._path
plugin.name = self._manifest["name"] plugin.name = self._manifest["name"]
plugin.author = self._manifest["author"] plugin.author = self._manifest["author"]
@ -48,14 +53,16 @@ class ManifestProcessor:
plugin.support = self._manifest["support"] plugin.support = self._manifest["support"]
plugin.requests = self._manifest["requests"] plugin.requests = self._manifest["requests"]
if "pre_launch" in self._manifest.keys():
plugin.pre_launch = True if self._manifest["pre_launch"] == "true" else False
return plugin return plugin
def get_loading_data(self): def get_loading_data(self):
loading_data = {} loading_data = {}
requests = self._plugin.requests requests = self._plugin.requests
keys = requests.keys()
if "ui_target" in keys: if "ui_target" in requests:
if requests["ui_target"] in [ if requests["ui_target"] in [
"none", "other", "main_Window", "main_menu_bar", "none", "other", "main_Window", "main_menu_bar",
"main_menu_bttn_box_bar", "path_menu_bar", "plugin_control_list", "main_menu_bttn_box_bar", "path_menu_bar", "plugin_control_list",
@ -63,7 +70,7 @@ class ManifestProcessor:
"window_2", "window_3", "window_4" "window_2", "window_3", "window_4"
]: ]:
if requests["ui_target"] == "other": if requests["ui_target"] == "other":
if "ui_target_id" in keys: if "ui_target_id" in requests:
loading_data["ui_target"] = self._builder.get_object(requests["ui_target_id"]) loading_data["ui_target"] = self._builder.get_object(requests["ui_target_id"])
if loading_data["ui_target"] == None: if loading_data["ui_target"] == None:
raise ManifestProcessorException('Invalid "ui_target_id" given in requests. Must have one if setting "ui_target" to "other"...') raise ManifestProcessorException('Invalid "ui_target_id" given in requests. Must have one if setting "ui_target" to "other"...')
@ -74,11 +81,11 @@ class ManifestProcessor:
else: else:
raise ManifestProcessorException('Unknown "ui_target" given in requests.') raise ManifestProcessorException('Unknown "ui_target" given in requests.')
if "pass_fm_events" in keys: if "pass_fm_events" in requests:
if requests["pass_fm_events"] in ["true"]: if requests["pass_fm_events"] in ["true"]:
loading_data["pass_fm_events"] = True loading_data["pass_fm_events"] = True
if "pass_ui_objects" in keys: if "pass_ui_objects" in requests:
if len(requests["pass_ui_objects"]) > 0: if len(requests["pass_ui_objects"]) > 0:
loading_data["pass_ui_objects"] = [] loading_data["pass_ui_objects"] = []
for ui_id in requests["pass_ui_objects"]: for ui_id in requests["pass_ui_objects"]:
@ -87,7 +94,7 @@ class ManifestProcessor:
except ManifestProcessorException as e: except ManifestProcessorException as e:
logger.error(repr(e)) logger.error(repr(e))
if "bind_keys" in keys: if "bind_keys" in requests:
if isinstance(requests["bind_keys"], list): if isinstance(requests["bind_keys"], list):
loading_data["bind_keys"] = requests["bind_keys"] loading_data["bind_keys"] = requests["bind_keys"]

View File

@ -36,41 +36,76 @@ class PluginsController:
self._plugins_dir_watcher = None self._plugins_dir_watcher = None
self._plugin_collection = [] self._plugin_collection = []
self._plugin_manifests = {}
self._load_manifests()
def launch_plugins(self) -> None: def _load_manifests(self):
logger.info(f"Loading manifests...")
for path, folder in [[join(self._plugins_path, item), item] if os.path.isdir(join(self._plugins_path, item)) else None for item in os.listdir(self._plugins_path)]:
manifest = ManifestProcessor(path, self._builder)
self._plugin_manifests[path] = {
"path": path,
"folder": folder,
"manifest": manifest
}
self._set_plugins_watcher() self._set_plugins_watcher()
self.load_plugins()
def _set_plugins_watcher(self) -> None: def _set_plugins_watcher(self) -> None:
self._plugins_dir_watcher = Gio.File.new_for_path(self._plugins_path) \ self._plugins_dir_watcher = Gio.File.new_for_path(self._plugins_path) \
.monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, Gio.Cancellable()) .monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, Gio.Cancellable())
self._plugins_dir_watcher.connect("changed", self._on_plugins_changed, ()) 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): 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, if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED,
Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN, Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN,
Gio.FileMonitorEvent.MOVED_OUT]: Gio.FileMonitorEvent.MOVED_OUT]:
self.reload_plugins(file) self.reload_plugins(file)
@daemon_threaded def pre_launch_plugins(self) -> None:
def load_plugins(self, file: str = None) -> None: logger.info(f"Loading pre-launch plugins...")
logger.info(f"Loading plugins...") plugin_manifests: {} = {}
for key in self._plugin_manifests:
target_manifest = self._plugin_manifests[key]["manifest"]
if target_manifest.is_pre_launch():
plugin_manifests[key] = self._plugin_manifests[key]
self._load_plugins(plugin_manifests, is_pre_launch = True)
def post_launch_plugins(self) -> None:
logger.info(f"Loading post-launch plugins...")
plugin_manifests: {} = {}
for key in self._plugin_manifests:
target_manifest = self._plugin_manifests[key]["manifest"]
if not target_manifest.is_pre_launch():
plugin_manifests[key] = self._plugin_manifests[key]
self._load_plugins(plugin_manifests)
def _load_plugins(self, plugin_manifests: {} = {}, is_pre_launch: bool = False) -> None:
parent_path = os.getcwd() parent_path = os.getcwd()
for path, folder in [[join(self._plugins_path, item), item] if os.path.isdir(join(self._plugins_path, item)) else None for item in os.listdir(self._plugins_path)]: for key in plugin_manifests:
try: target_manifest = plugin_manifests[key]
target = join(path, "plugin.py") path, folder, manifest = target_manifest["path"], target_manifest["folder"], target_manifest["manifest"]
manifest = ManifestProcessor(path, self._builder)
try:
target = join(path, "plugin.py")
if not os.path.exists(target): if not os.path.exists(target):
raise FileNotFoundError("Invalid Plugin Structure: Plugin doesn't have 'plugin.py'. Aboarting load...") raise FileNotFoundError("Invalid Plugin Structure: Plugin doesn't have 'plugin.py'. Aboarting load...")
plugin, loading_data = manifest.get_loading_data() plugin, loading_data = manifest.get_loading_data()
module = self.load_plugin_module(path, folder, target) module = self.load_plugin_module(path, folder, target)
GLib.idle_add(self.execute_plugin, *(module, plugin, loading_data)) if is_pre_launch:
# self.execute_plugin(module, plugin, loading_data) self.execute_plugin(module, plugin, loading_data)
else:
GLib.idle_add(self.execute_plugin, *(module, plugin, loading_data))
except InvalidPluginException as e: except InvalidPluginException as e:
logger.info(f"Malformed Plugin: Not loading -->: '{folder}' !") logger.info(f"Malformed Plugin: Not loading -->: '{folder}' !")
logger.debug("Trace: ", traceback.print_exc()) logger.debug("Trace: ", traceback.print_exc())
@ -100,25 +135,26 @@ class PluginsController:
def execute_plugin(self, module: type, plugin: PluginInfo, loading_data: []): def execute_plugin(self, module: type, plugin: PluginInfo, loading_data: []):
plugin.reference = module.Plugin() plugin.reference = module.Plugin()
keys = loading_data.keys()
if "ui_target" in keys: if "ui_target" in loading_data:
loading_data["ui_target"].add( plugin.reference.generate_reference_ui_element() ) loading_data["ui_target"].add( plugin.reference.generate_reference_ui_element() )
loading_data["ui_target"].show_all() loading_data["ui_target"].show_all()
if "pass_ui_objects" in keys: if "pass_ui_objects" in loading_data:
plugin.reference.set_ui_object_collection( loading_data["pass_ui_objects"] ) plugin.reference.set_ui_object_collection( loading_data["pass_ui_objects"] )
if "pass_fm_events" in keys: if "pass_fm_events" in loading_data:
plugin.reference.set_fm_event_system(event_system) plugin.reference.set_fm_event_system(event_system)
plugin.reference.subscribe_to_events() plugin.reference.subscribe_to_events()
if "bind_keys" in keys: if "bind_keys" in loading_data:
keybindings.append_bindings( loading_data["bind_keys"] ) keybindings.append_bindings( loading_data["bind_keys"] )
plugin.reference.run() plugin.reference.run()
self._plugin_collection.append(plugin) self._plugin_collection.append(plugin)
return False
def reload_plugins(self, file: str = None) -> None: def reload_plugins(self, file: str = None) -> None:
logger.info(f"Reloading plugins...") logger.info(f"Reloading plugins...")
parent_path = os.getcwd() parent_path = os.getcwd()

View File

@ -14,7 +14,6 @@ from random import randint
from .utils.settings import Settings from .utils.settings import Settings
from .utils.launcher import Launcher from .utils.launcher import Launcher
from .utils.filehandler import FileHandler from .utils.filehandler import FileHandler
from .icons.icon import Icon
from .path import Path from .path import Path
@ -40,9 +39,8 @@ except Exception as e:
class Tab(Settings, FileHandler, Launcher, Icon, Path): class Tab(Settings, FileHandler, Launcher, Path):
def __init__(self): def __init__(self):
self.logger = None
self._id_length: int = 10 self._id_length: int = 10
self._id: str = "" self._id: str = ""
@ -168,33 +166,6 @@ class Tab(Settings, FileHandler, Launcher, Icon, Path):
} }
} }
def get_video_icons(self) -> list:
data = []
dir = self.get_current_directory()
for file in self._vids:
img_hash, hash_img_path = self.create_video_thumbnail(full_path=f"{dir}/{file}", returnHashInstead=True)
data.append([img_hash, hash_img_path])
return data
def get_pixbuf_icon_str_combo(self):
data = []
dir = self.get_current_directory()
for file in self._files:
icon = self.create_icon(dir, file).get_pixbuf()
data.append([icon, file])
return data
def get_gtk_icon_str_combo(self) -> list:
data = []
dir = self.get_current_directory()
for file in self._files:
icon = self.create_icon(dir, file)
data.append([icon, file[0]])
return data
def get_current_directory(self) -> str: def get_current_directory(self) -> str:
return self.get_path() return self.get_path()
@ -264,7 +235,7 @@ class Tab(Settings, FileHandler, Launcher, Icon, Path):
return int(text) if text.isdigit() else text return int(text) if text.isdigit() else text
def _natural_keys(self, text): def _natural_keys(self, text):
return [ self._atoi(c) for c in re.split('(\d+)',text) ] return [ self._atoi(c) for c in re.split(r'(\d+)', text) ]
def _hash_text(self, text) -> str: def _hash_text(self, text) -> str:
return hashlib.sha256(str.encode(text)).hexdigest()[:18] return hashlib.sha256(str.encode(text)).hexdigest()[:18]
@ -289,3 +260,7 @@ class Tab(Settings, FileHandler, Launcher, Icon, Path):
def _set_error_message(self, text: str): def _set_error_message(self, text: str):
self.error_message = text self.error_message = text
def create_icon(self, dir, file):
return event_system.emit_and_await("create-thumbnail", (dir, file,))

View File

@ -41,11 +41,11 @@ class Launcher:
def execute(self, command, start_dir=os.getenv("HOME"), use_shell=False): def execute(self, command, start_dir=os.getenv("HOME"), use_shell=False):
try: try:
self.logger.debug(command) logger.debug(command)
subprocess.Popen(command, cwd=start_dir, shell=use_shell, start_new_session=True, stdout=None, stderr=None, close_fds=True) subprocess.Popen(command, cwd=start_dir, shell=use_shell, start_new_session=True, stdout=None, stderr=None, close_fds=True)
except ShellFMLauncherException as e: except ShellFMLauncherException as e:
self.logger.error(f"Couldn't execute: {command}") logger.error(f"Couldn't execute: {command}")
self.logger.error(e) logger.error(e)
# TODO: Return std(out/in/err) handlers along with subprocess instead of sinking to null # TODO: Return std(out/in/err) handlers along with subprocess instead of sinking to null
def execute_and_return_thread_handler(self, command, start_dir=os.getenv("HOME"), use_shell=False): def execute_and_return_thread_handler(self, command, start_dir=os.getenv("HOME"), use_shell=False):
@ -53,8 +53,8 @@ class Launcher:
DEVNULL = open(os.devnull, 'w') DEVNULL = open(os.devnull, 'w')
return subprocess.Popen(command, cwd=start_dir, shell=use_shell, start_new_session=False, stdout=DEVNULL, stderr=DEVNULL, close_fds=False) return subprocess.Popen(command, cwd=start_dir, shell=use_shell, start_new_session=False, stdout=DEVNULL, stderr=DEVNULL, close_fds=False)
except ShellFMLauncherException as e: except ShellFMLauncherException as e:
self.logger.error(f"Couldn't execute and return thread: {command}") logger.error(f"Couldn't execute and return thread: {command}")
self.logger.error(e) logger.error(e)
return None return None
@threaded @threaded
@ -63,7 +63,7 @@ class Launcher:
def remux_video(self, hash, file): def remux_video(self, hash, file):
remux_vid_pth = "{self.REMUX_FOLDER}/{hash}.mp4" remux_vid_pth = "{self.REMUX_FOLDER}/{hash}.mp4"
self.logger.debug(remux_vid_pth) logger.debug(remux_vid_pth)
if not os.path.isfile(remux_vid_pth): if not os.path.isfile(remux_vid_pth):
self.check_remux_space() self.check_remux_space()
@ -83,8 +83,8 @@ class Launcher:
proc = subprocess.Popen(command) proc = subprocess.Popen(command)
proc.wait() proc.wait()
except ShellFMLauncherException as e: except ShellFMLauncherException as e:
self.logger.error(message) logger.error(message)
self.logger.error(e) logger.error(e)
return False return False
return True return True
@ -94,7 +94,7 @@ class Launcher:
try: try:
limit = int(limit) limit = int(limit)
except ShellFMLauncherException as e: except ShellFMLauncherException as e:
self.logger.debug(e) logger.debug(e)
return return
usage = self.get_remux_folder_usage(self.REMUX_FOLDER) usage = self.get_remux_folder_usage(self.REMUX_FOLDER)

View File

@ -14,8 +14,6 @@ class ShellFMSettingsException(Exception):
class Settings: class Settings:
logger = None
# NOTE: app_name should be defined using python 'builtins' # NOTE: app_name should be defined using python 'builtins'
app_name_exists = False app_name_exists = False
try: try:
@ -31,45 +29,13 @@ class Settings:
CONFIG_FILE = f"{CONFIG_PATH}/settings.json" CONFIG_FILE = f"{CONFIG_PATH}/settings.json"
HIDE_HIDDEN_FILES = True HIDE_HIDDEN_FILES = True
DEFAULT_ICONS = f"{CONFIG_PATH}/icons"
DEFAULT_ICON = f"{DEFAULT_ICONS}/text.png"
FFMPG_THUMBNLR = f"{CONFIG_PATH}/ffmpegthumbnailer" # Thumbnail generator binary
BLENDER_THUMBNLR = f"{CONFIG_PATH}/blender-thumbnailer" # Blender thumbnail generator binary
REMUX_FOLDER = f"{USER_HOME}/.remuxs" # Remuxed files folder REMUX_FOLDER = f"{USER_HOME}/.remuxs" # Remuxed files folder
ICON_DIRS = ["/usr/share/icons", f"{USER_HOME}/.icons" "/usr/share/pixmaps"]
BASE_THUMBS_PTH = f"{USER_HOME}/.thumbnails"
ABS_THUMBS_PTH = f"{BASE_THUMBS_PTH}/normal"
STEAM_ICONS_PTH = f"{BASE_THUMBS_PTH}/steam_icons"
if not os.path.exists(CONFIG_PATH) or not os.path.exists(CONFIG_FILE):
msg = f"No config file located! Aborting loading ShellFM library...\nExpected: {CONFIG_FILE}"
raise ShellFMSettingsException(msg)
if not path.isdir(REMUX_FOLDER):
os.mkdir(REMUX_FOLDER)
if not path.isdir(BASE_THUMBS_PTH):
os.mkdir(BASE_THUMBS_PTH)
if not path.isdir(ABS_THUMBS_PTH):
os.mkdir(ABS_THUMBS_PTH)
if not path.isdir(STEAM_ICONS_PTH):
os.mkdir(STEAM_ICONS_PTH)
if not os.path.exists(DEFAULT_ICONS):
DEFAULT_ICONS = f"{USR_APP_CONTEXT}/icons"
DEFAULT_ICON = f"{DEFAULT_ICONS}/text.png"
with open(CONFIG_FILE) as f: with open(CONFIG_FILE) as f:
settings = json.load(f) settings = json.load(f)
config = settings["config"] config = settings["config"]
subpath = config["base_of_home"] subpath = config["base_of_home"]
STEAM_CDN_URL = config["steam_cdn_url"]
FFMPG_THUMBNLR = FFMPG_THUMBNLR if config["thumbnailer_path"] == "" else config["thumbnailer_path"]
BLENDER_THUMBNLR = BLENDER_THUMBNLR if config["blender_thumbnailer_path"] == "" else config["blender_thumbnailer_path"]
HIDE_HIDDEN_FILES = True if config["hide_hidden_files"] in ["true", ""] else False HIDE_HIDDEN_FILES = True if config["hide_hidden_files"] in ["true", ""] else False
go_past_home = True if config["go_past_home"] in ["true", ""] else False go_past_home = True if config["go_past_home"] in ["true", ""] else False
lock_folder = False if config["lock_folder"] in ["false", ""] else True lock_folder = False if config["lock_folder"] in ["false", ""] else True
@ -83,9 +49,6 @@ class Settings:
code_app = config["code_app"] code_app = config["code_app"]
text_app = config["text_app"] text_app = config["text_app"]
terminal_app = config["terminal_app"] terminal_app = config["terminal_app"]
container_icon_wh = config["container_icon_wh"]
video_icon_wh = config["video_icon_wh"]
sys_icon_wh = config["sys_icon_wh"]
file_manager_app = config["file_manager_app"] file_manager_app = config["file_manager_app"]
remux_folder_max_disk_usage = config["remux_folder_max_disk_usage"] remux_folder_max_disk_usage = config["remux_folder_max_disk_usage"]

View File

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

View File

@ -51,15 +51,20 @@ class IPCServer(Singleton):
listener = Listener((self._ipc_address, self._ipc_port)) listener = Listener((self._ipc_address, self._ipc_port))
self.is_ipc_alive = True self.is_ipc_alive = True
self._run_ipc_loop(listener) # self._run_ipc_loop(listener)
GLib.Thread("", self._run_ipc_loop, listener)
@daemon_threaded # @daemon_threaded
def _run_ipc_loop(self, listener) -> None: def _run_ipc_loop(self, listener) -> None:
while True: while True:
try: try:
conn = listener.accept() conn = listener.accept()
start_time = time.perf_counter() start_time = time.perf_counter()
GLib.idle_add(self._handle_ipc_message, *(conn, start_time,)) GLib.idle_add(self._handle_ipc_message, *(conn, start_time,))
conn = None
start_time = None
except Exception as e: except Exception as e:
logger.debug( repr(e) ) logger.debug( repr(e) )
@ -73,22 +78,29 @@ class IPCServer(Singleton):
if "FILE|" in msg: if "FILE|" in msg:
file = msg.split("FILE|")[1].strip() file = msg.split("FILE|")[1].strip()
if file: if file:
event_system.emit("handle_file_from_ipc", file) event_system.emit_and_await("handle_file_from_ipc", file)
msg = None
file = None
conn.close() conn.close()
break break
if msg in ['close connection', 'close server']: if msg in ['close connection', 'close server']:
msg = None
conn.close() conn.close()
break break
# NOTE: Not perfect but insures we don't lock up the connection for too long. # NOTE: Not perfect but insures we don't lock up the connection for too long.
end_time = time.perf_counter() end_time = time.perf_counter()
if (end_time - start_time) > self._ipc_timeout: if (end_time - start_time) > self._ipc_timeout:
msg = None
end_time = None
conn.close() conn.close()
break break
return False
def send_ipc_message(self, message: str = "Empty Data...") -> None: def send_ipc_message(self, message: str = "Empty Data...") -> None:
try: try:

View File

@ -146,6 +146,13 @@ class SettingsManager(StartCheckMixin, Singleton):
def is_trace_debug(self) -> bool: return self._trace_debug def is_trace_debug(self) -> bool: return self._trace_debug
def is_debug(self) -> bool: return self._debug def is_debug(self) -> bool: return self._debug
def set_main_window_x(self, x = 0): self.settings.config.main_window_x = x
def set_main_window_y(self, y = 0): self.settings.config.main_window_y = y
def set_main_window_width(self, width = 800): self.settings.config.main_window_width = width
def set_main_window_height(self, height = 600): self.settings.config.main_window_height = height
def set_main_window_min_width(self, width = 720): self.settings.config.main_window_min_width = width
def set_main_window_min_height(self, height = 480): self.settings.config.main_window_min_height = height
def set_trace_debug(self, trace_debug: bool): def set_trace_debug(self, trace_debug: bool):
self._trace_debug = trace_debug self._trace_debug = trace_debug

View File

@ -30,6 +30,13 @@ class Config:
sys_icon_wh: list = field(default_factory=lambda: [56, 56]) sys_icon_wh: list = field(default_factory=lambda: [56, 56])
steam_cdn_url: str = "https://steamcdn-a.akamaihd.net/steam/apps/" steam_cdn_url: str = "https://steamcdn-a.akamaihd.net/steam/apps/"
remux_folder_max_disk_usage: str = "8589934592" remux_folder_max_disk_usage: str = "8589934592"
make_transparent: int = 0
main_window_x: int = 721
main_window_y: int = 465
main_window_min_width: int = 720
main_window_min_height: int = 480
main_window_width: int = 800
main_window_height: int = 600
application_dirs: list = field(default_factory=lambda: [ application_dirs: list = field(default_factory=lambda: [
"/usr/share/applications", "/usr/share/applications",
f"{settings_manager.get_home_path()}/.local/share/applications" f"{settings_manager.get_home_path()}/.local/share/applications"

View File

@ -11,36 +11,48 @@ import inspect
class StartCheckMixin: class StartCheckMixin:
def is_dirty_start(self) -> bool: return self._dirty_start def is_dirty_start(self) -> bool:
def clear_pid(self): self._clean_pid() return self._dirty_start
def clear_pid(self):
if not self.is_trace_debug():
self._clean_pid()
def do_dirty_start_check(self): def do_dirty_start_check(self):
if not os.path.exists(self._PID_FILE): if self.is_trace_debug():
self._write_new_pid() pid = os.getpid()
else: self._print_pid(pid)
with open(self._PID_FILE, "r") as _pid: return
pid = _pid.readline().strip()
if os.path.exists(self._PID_FILE):
with open(self._PID_FILE, "r") as f:
pid = f.readline().strip()
if pid not in ("", None): if pid not in ("", None):
self._check_alive_status(int(pid)) if self.is_pid_alive( int(pid) ):
else: print("PID file exists and PID is alive... Letting downstream errors (sans debug args) handle app closure propigation.")
self._write_new_pid() return
self._write_new_pid()
""" Check For the existence of a unix pid. """ """ Check For the existence of a unix pid. """
def _check_alive_status(self, pid): def is_pid_alive(self, pid):
print(f"PID Found: {pid}") print(f"PID Found: {pid}")
try: try:
os.kill(pid, 0) os.kill(pid, 0)
except OSError: except OSError:
print(f"{app_name} is starting dirty...") print(f"{app_name} PID file exists but PID is irrelevant; starting dirty...")
self._dirty_start = True self._dirty_start = True
self._write_new_pid() return False
return
print("PID is alive... Let downstream errors (sans debug args) handle app closure propigation.") return True
def _write_new_pid(self): def _write_new_pid(self):
pid = os.getpid() pid = os.getpid()
self._write_pid(pid) self._write_pid(pid)
self._print_pid(pid)
def _print_pid(self, pid):
print(f"{app_name} PID: {pid}") print(f"{app_name} PID: {pid}")
def _clean_pid(self): def _clean_pid(self):

View File

@ -20,6 +20,10 @@ function main() {
files[$size]="${target}" files[$size]="${target}"
done done
python /opt/solarfm.zip "${files[@]}" export G_SLICE=always-malloc
export G_DEBUG=gc-friendly
export GOBJECT_DEBUG=instance-count
export GSK_RENDERER=cairo
python /opt/solarfm.zip "$@"
} }
main "$@"; main "$@";

View File

@ -1,17 +0,0 @@
#!/bin/bash
# . CONFIG.sh
# set -o xtrace ## To debug scripts
# set -o errexit ## To exit on error
# set -o errunset ## To exit if a variable is referenced but not set
function main() {
call_path=`pwd`
cd "${call_path}"
echo "Working Dir: " $(pwd)
python /opt/solarfm.zip "$@"
}
main "$@";

View File

@ -1,8 +1,9 @@
{ {
"Open Actions": { "Open Actions": {
"Open": ["STOCK_OPEN", "open"], "Open": ["STOCK_OPEN", "open"],
"Open With": ["STOCK_OPEN", "open_with"], "Open With": ["STOCK_OPEN", "open_with"],
"Execute": ["STOCK_EXECUTE", "execute"], "Open 2 Tab": ["STOCK_OPEN", "open_2_new_tab"],
"Execute": ["STOCK_EXECUTE", "execute"],
"Execute in Terminal": ["STOCK_EXECUTE", "execute_in_terminal"] "Execute in Terminal": ["STOCK_EXECUTE", "execute_in_terminal"]
}, },
"File Actions": { "File Actions": {

View File

@ -2,8 +2,6 @@
"config": { "config": {
"base_of_home": "", "base_of_home": "",
"hide_hidden_files": "true", "hide_hidden_files": "true",
"thumbnailer_path": "ffmpegthumbnailer",
"blender_thumbnailer_path": "",
"go_past_home": "true", "go_past_home": "true",
"lock_folder": "false", "lock_folder": "false",
"locked_folders": "venv::::flasks", "locked_folders": "venv::::flasks",
@ -16,12 +14,18 @@
"code_app": "newton", "code_app": "newton",
"text_app": "mousepad", "text_app": "mousepad",
"terminal_app": "terminator", "terminal_app": "terminator",
"container_icon_wh": [128, 128],
"video_icon_wh": [128, 64],
"sys_icon_wh": [56, 56],
"file_manager_app": "solarfm", "file_manager_app": "solarfm",
"steam_cdn_url": "https://steamcdn-a.akamaihd.net/steam/apps/", "remux_folder_max_disk_usage": "8589934592",
"remux_folder_max_disk_usage": "8589934592" "make_transparent":0,
"main_window_x":721,
"main_window_y":465,
"main_window_min_width":720,
"main_window_min_height":480,
"main_window_width":800,
"main_window_height":600,
"application_dirs":[
"/usr/share/applications"
]
}, },
"filters": { "filters": {
"meshs": [".dae", ".fbx", ".gltf", ".obj", ".stl"], "meshs": [".dae", ".fbx", ".gltf", ".obj", ".stl"],

View File

@ -8,7 +8,7 @@
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="border-width">5</property> <property name="border-width">5</property>
<property name="window-position">center-on-parent</property> <property name="window-position">center-on-parent</property>
<property name="icon">../icons/solarfm.png</property> <property name="icon">../icons/solarfm-64x64.png</property>
<property name="type-hint">dialog</property> <property name="type-hint">dialog</property>
<property name="skip-taskbar-hint">True</property> <property name="skip-taskbar-hint">True</property>
<property name="skip-pager-hint">True</property> <property name="skip-pager-hint">True</property>
@ -19,6 +19,7 @@
<property name="copyright" translatable="yes">Copyright (C) 2021 GPL2</property> <property name="copyright" translatable="yes">Copyright (C) 2021 GPL2</property>
<property name="comments" translatable="yes">by ITDominator</property> <property name="comments" translatable="yes">by ITDominator</property>
<property name="website">https://code.itdominator.com/itdominator/SolarFM</property> <property name="website">https://code.itdominator.com/itdominator/SolarFM</property>
<property name="website-label" translatable="yes">ITDominator</property>
<property name="license" translatable="yes">SolarFM - Copyright (C) 2021 ITDominator GPL2 <property name="license" translatable="yes">SolarFM - Copyright (C) 2021 ITDominator GPL2
@ -367,7 +368,9 @@ Public License instead of this License.
SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspection.</property> SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspection.</property>
<property name="translator-credits" translatable="yes" comments="Please replace this line with your own names, one name per line. ">translator-credits</property> <property name="documenters">...</property>
<property name="translator-credits" translatable="yes" comments="Please replace this line with your own names, one name per line. ">...</property>
<property name="artists">...</property>
<property name="logo">../icons/solarfm-64x64.png</property> <property name="logo">../icons/solarfm-64x64.png</property>
<property name="wrap-license">True</property> <property name="wrap-license">True</property>
<property name="license-type">custom</property> <property name="license-type">custom</property>