Compare commits
18 Commits
Author | SHA1 | Date |
---|---|---|
itdominator | 3a2e8eeb08 | |
itdominator | 35456f2bca | |
itdominator | 9d3a5b9f3b | |
itdominator | ce00970171 | |
itdominator | 2f954f4c79 | |
itdominator | a362039e73 | |
itdominator | 02c31719d1 | |
itdominator | d65ea8dec8 | |
itdominator | fec0d26ab7 | |
itdominator | a47bd23e78 | |
itdominator | 44ef6ea2bb | |
itdominator | be7be00f78 | |
itdominator | 8e5ae4824c | |
itdominator | 37e3265be5 | |
itdominator | 4cafb7ff9f | |
itdominator | 9336df2afa | |
itdominator | d936b17429 | |
itdominator | e6739c3087 |
|
@ -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.
|
||||
|
||||
`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>
|
||||
```
|
||||
|
@ -32,4 +32,4 @@ A selected file in the active quad-pane will move to trash since it is the defau
|
|||
![1 SolarFM single pane. ](images/pic1.png)
|
||||
![2 SolarFM double pane. ](images/pic2.png)
|
||||
![3 SolarFM triple pane. ](images/pic3.png)
|
||||
![4 SolarFM quad pane. ](images/pic4.png)
|
||||
![4 SolarFM quad pane. ](images/pic4.png)
|
||||
|
|
|
@ -14,6 +14,7 @@ class Manifest:
|
|||
'ui_target': "plugin_control_list",
|
||||
'pass_fm_events': "true"
|
||||
}
|
||||
pre_launch: bool = False
|
||||
```
|
||||
|
||||
|
||||
|
|
|
@ -122,7 +122,6 @@ class Plugin(PluginBase):
|
|||
uri = state.uris[0]
|
||||
path = state.tab.get_current_directory()
|
||||
|
||||
|
||||
properties = self._set_ui_data(uri, path)
|
||||
response = self._properties_dialog.run()
|
||||
if response in [Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT]:
|
||||
|
@ -168,13 +167,13 @@ class Plugin(PluginBase):
|
|||
|
||||
def _set_ui_data(self, uri, path):
|
||||
properties = Properties()
|
||||
file_info = Gio.File.new_for_path(uri).query_info(attributes="standard::*,owner::*,time::access,time::changed",
|
||||
flags=Gio.FileQueryInfoFlags.NONE,
|
||||
cancellable=None)
|
||||
file_info = Gio.File.new_for_path(uri).query_info(attributes = "standard::*,owner::*,time::access,time::changed",
|
||||
flags = Gio.FileQueryInfoFlags.NONE,
|
||||
cancellable = None)
|
||||
|
||||
is_symlink = file_info.get_attribute_as_string("standard::is-symlink")
|
||||
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_location = path
|
||||
properties.mime_type = file_info.get_content_type()
|
||||
|
@ -186,7 +185,7 @@ class Plugin(PluginBase):
|
|||
|
||||
# NOTE: Read = 4, Write = 2, Exec = 1
|
||||
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())
|
||||
owner = self._chmod_map[f"{properties.chmod_stat[0]}"]
|
||||
group = self._chmod_map[f"{properties.chmod_stat[1]}"]
|
||||
|
|
|
@ -48,7 +48,7 @@ class GrepPreviewWidget(Gtk.Box):
|
|||
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):
|
||||
parts = re.split(r"(" + query + ")(?i)", target.replace("\n", ""))
|
||||
parts = re.split(r"(?i)(" + query + ")", target.replace("\n", ""))
|
||||
for part in parts:
|
||||
itr = buffer.get_end_iter()
|
||||
|
||||
|
@ -57,4 +57,4 @@ class GrepPreviewWidget(Gtk.Box):
|
|||
else:
|
||||
new_s = f"<span foreground='#000000' background='{color}'>{part}</span>"
|
||||
_part = bytes(new_s, "utf-8").decode("utf-8")
|
||||
buffer.insert_markup(itr, _part, len(_part))
|
||||
buffer.insert_markup(itr, _part, len(_part))
|
|
@ -8,6 +8,7 @@
|
|||
"ui_target": "plugin_control_list",
|
||||
"pass_fm_events": "true",
|
||||
"bind_keys": ["Example Plugin||send_message:<Control>f"]
|
||||
}
|
||||
},
|
||||
"pre_launch": "false"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
"""
|
||||
Pligin Module
|
||||
"""
|
|
@ -0,0 +1,3 @@
|
|||
"""
|
||||
Pligin Package
|
||||
"""
|
|
@ -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"])
|
|
@ -138,6 +138,7 @@ class Icon(DesktopIconMixin, VideoIconMixin, MeshsIconMixin):
|
|||
def _call_gtk_thread(event, result):
|
||||
result.append( self.get_system_thumbnail(full_path, size) )
|
||||
event.set()
|
||||
return False
|
||||
|
||||
result = []
|
||||
event = threading.Event()
|
||||
|
@ -151,11 +152,11 @@ class Icon(DesktopIconMixin, VideoIconMixin, MeshsIconMixin):
|
|||
gio_file = Gio.File.new_for_path(full_path)
|
||||
info = gio_file.query_info('standard::icon' , 0, None)
|
||||
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:
|
||||
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...")
|
||||
except IconException:
|
||||
|
@ -201,4 +202,4 @@ class Icon(DesktopIconMixin, VideoIconMixin, MeshsIconMixin):
|
|||
pixbuf = GdkPixbuf.Pixbuf.new_from_bytes(data, GdkPixbuf.Colorspace.RGB,
|
||||
False, 8, w, h, w * 3)
|
||||
|
||||
return pixbuf.scale_simple(wxh[0], wxh[1], 2) # BILINEAR = 2
|
||||
return pixbuf.scale_simple(wxh[0], wxh[1], 2) # BILINEAR = 2
|
|
@ -14,4 +14,4 @@ class MeshsIconMixin:
|
|||
proc = subprocess.Popen([self.BLENDER_THUMBNLR, full_path, hash_img_path])
|
||||
proc.wait()
|
||||
except Exception as e:
|
||||
self.logger.debug(repr(e))
|
||||
logger.debug(repr(e))
|
|
@ -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.wait()
|
||||
except Exception as e:
|
||||
self.logger.debug(repr(e))
|
||||
logger.info(repr(e))
|
||||
self.ffprobe_generate_video_thumbnail(full_path, hash_img_path)
|
||||
|
||||
|
||||
|
@ -51,5 +51,4 @@ class VideoIconMixin:
|
|||
proc.wait()
|
||||
except Exception as e:
|
||||
print("Video thumbnail generation issue in thread:")
|
||||
print( repr(e) )
|
||||
self.logger.debug(repr(e))
|
||||
logger.info(repr(e))
|
|
@ -0,0 +1,12 @@
|
|||
{
|
||||
"manifest": {
|
||||
"name": "Thumbnailer",
|
||||
"author": "ITDominator",
|
||||
"version": "0.0.1",
|
||||
"support": "",
|
||||
"requests": {
|
||||
"pass_fm_events": "true"
|
||||
},
|
||||
"pre_launch": "true"
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
|
@ -184,11 +184,11 @@ class Plugin(PluginBase):
|
|||
response = requests.post(self.vqd_link, headers=self.vqd_headers, data=self.vqd_data, timeout=2)
|
||||
if response.status_code == 200:
|
||||
data = response.content
|
||||
vqd_start_index = data.index(b"vqd='") + 5
|
||||
vqd_end_index = data.index(b"'", vqd_start_index)
|
||||
vqd_start_index = data.index(b"vqd=\"") + 5
|
||||
vqd_end_index = data.index(b"\"", vqd_start_index)
|
||||
self._vqd_attrib = data[vqd_start_index:vqd_end_index].decode("utf-8")
|
||||
|
||||
print(f"Translation VQD: {self._vqd_attrib}")
|
||||
else:
|
||||
msg = f"Could not get VQS attribute... Response Code: {response.status_code}"
|
||||
self._translate_to_buffer.set_text(msg)
|
||||
self._translate_to_buffer.set_text(msg)
|
|
@ -111,6 +111,8 @@ class Plugin(PluginBase):
|
|||
for uri in state.uris:
|
||||
self.trashman.trash(uri, verbocity)
|
||||
|
||||
self.trashman.regenerate()
|
||||
|
||||
def restore_trash_files(self, widget = None, eve = None, verbocity = False):
|
||||
self._event_system.emit("get_current_state")
|
||||
state = self._fm_state
|
||||
|
|
|
@ -43,4 +43,4 @@ class Trash(object):
|
|||
|
||||
def restore(self, filename, verbose):
|
||||
"""Restore a file from trash."""
|
||||
raise NotImplementedError(_('Backend didn’t \ implement this functionality'))
|
||||
raise NotImplementedError(_('Backend didn’t implement this functionality'))
|
||||
|
|
|
@ -127,7 +127,7 @@ DeletionDate={}
|
|||
f.write(infofile)
|
||||
f.close()
|
||||
|
||||
self.regenerate()
|
||||
# self.regenerate()
|
||||
|
||||
if verbose:
|
||||
sys.stderr.write(_('trashed \'{}\'\n').format(filename))
|
||||
|
|
|
@ -7,5 +7,7 @@
|
|||
{
|
||||
"root": "./src/versions/solarfm-0.0.1/solarfm"
|
||||
}
|
||||
]
|
||||
],
|
||||
"venvPath": "/home/abaddon/Portable_Apps/py-venvs/pylsp-venv/",
|
||||
"venv": "venv"
|
||||
}
|
||||
|
|
|
@ -16,13 +16,17 @@ from utils.settings_manager.manager import SettingsManager
|
|||
# NOTE: Threads WILL NOT die with parent's destruction.
|
||||
def threaded_wrapper(fn):
|
||||
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
|
||||
|
||||
# NOTE: Threads WILL die with parent's destruction.
|
||||
def daemon_threaded_wrapper(fn):
|
||||
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
|
||||
|
||||
def sizeof_fmt_def(num, suffix="B"):
|
||||
|
@ -61,4 +65,4 @@ def custom_except_hook(exc_type, exc_value, exc_traceback):
|
|||
|
||||
logger.error("Uncaught exception", exc_info=(exc_type, exc_value, exc_traceback))
|
||||
|
||||
sys.excepthook = custom_except_hook
|
||||
sys.excepthook = custom_except_hook
|
|
@ -3,14 +3,13 @@
|
|||
# Python imports
|
||||
import argparse
|
||||
import faulthandler
|
||||
import locale
|
||||
import traceback
|
||||
from setproctitle import setproctitle
|
||||
|
||||
import tracemalloc
|
||||
tracemalloc.start()
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from __builtins__ import *
|
||||
|
@ -18,39 +17,38 @@ from app import Application
|
|||
|
||||
|
||||
|
||||
def run():
|
||||
try:
|
||||
locale.setlocale(locale.LC_NUMERIC, 'C')
|
||||
def main(args, unknownargs):
|
||||
setproctitle(f'{app_name}')
|
||||
|
||||
setproctitle(f"{app_name}")
|
||||
faulthandler.enable() # For better debug info
|
||||
if args.debug == "true":
|
||||
settings_manager.set_debug(True)
|
||||
|
||||
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.")
|
||||
if args.trace_debug == "true":
|
||||
settings_manager.set_trace_debug(True)
|
||||
|
||||
parser.add_argument("--new-tab", "-t", default="", help="Open a file into new tab.")
|
||||
parser.add_argument("--new-window", "-w", default="", help="Open a file into a new window.")
|
||||
settings_manager.do_dirty_start_check()
|
||||
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__":
|
||||
""" Set process title, get arguments, and create GTK main thread. """
|
||||
run()
|
||||
''' Set process title, get arguments, and create GTK main thread. '''
|
||||
|
||||
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()
|
||||
|
|
|
@ -15,35 +15,40 @@ class AppLaunchException(Exception):
|
|||
...
|
||||
|
||||
|
||||
class Application(IPCServer):
|
||||
|
||||
class Application:
|
||||
""" docstring for Application. """
|
||||
|
||||
def __init__(self, args, unknownargs):
|
||||
super(Application, self).__init__()
|
||||
|
||||
if not settings_manager.is_trace_debug():
|
||||
self.socket_realization_check()
|
||||
|
||||
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.load_ipc(args, unknownargs)
|
||||
|
||||
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:
|
||||
self.create_ipc_listener()
|
||||
ipc_server.create_ipc_listener()
|
||||
except Exception:
|
||||
self.send_test_ipc_message()
|
||||
ipc_server.send_test_ipc_message()
|
||||
|
||||
try:
|
||||
self.create_ipc_listener()
|
||||
ipc_server.create_ipc_listener()
|
||||
except Exception as e:
|
||||
...
|
||||
|
||||
|
@ -51,9 +56,9 @@ class Application(IPCServer):
|
|||
try:
|
||||
# kill -SIGUSR2 <pid> from Linux/Unix or SIGBREAK signal from Windows
|
||||
signal.signal(
|
||||
vars(signal).get("SIGBREAK") or vars(signal).get("SIGUSR1"),
|
||||
vars(signal).get("SIGBREAK") or vars(signal).get("SIGUSR2"),
|
||||
debug_signal_handler
|
||||
)
|
||||
except ValueError:
|
||||
# Typically: ValueError: signal only works in main thread
|
||||
...
|
||||
...
|
|
@ -44,10 +44,13 @@ class Controller(UIMixin, SignalsMixins, Controller_Data):
|
|||
self._subscribe_to_events()
|
||||
self._load_widgets()
|
||||
|
||||
if args.no_plugins == "false":
|
||||
self.plugins_controller.pre_launch_plugins()
|
||||
|
||||
self._generate_file_views(self.fm_controller_data)
|
||||
|
||||
if args.no_plugins == "false":
|
||||
self.plugins.launch_plugins()
|
||||
self.plugins_controller.post_launch_plugins()
|
||||
|
||||
for arg in unknownargs + [args.new_tab,]:
|
||||
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("set_clipboard_data", self.set_clipboard_data)
|
||||
|
||||
|
||||
def _load_glade_file(self):
|
||||
self.builder.add_from_file( settings_manager.get_glade_file() )
|
||||
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():
|
||||
self.fm_controller.save_state()
|
||||
|
||||
|
||||
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):
|
||||
|
@ -130,44 +135,48 @@ class Controller(UIMixin, SignalsMixins, Controller_Data):
|
|||
event_system.emit("hide_rename_file_menu")
|
||||
|
||||
if action == "open":
|
||||
event_system.emit("open_files")
|
||||
event_system.emit_and_await("open_files")
|
||||
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":
|
||||
event_system.emit("execute_files")
|
||||
event_system.emit_and_await("execute_files")
|
||||
if action == "execute_in_terminal":
|
||||
event_system.emit("execute_files", (True,))
|
||||
event_system.emit_and_await("execute_files", (True,))
|
||||
if action == "rename":
|
||||
event_system.emit("rename_files")
|
||||
event_system.emit_and_await("rename_files")
|
||||
if action == "cut":
|
||||
event_system.emit("cut_files")
|
||||
event_system.emit_and_await("cut_files")
|
||||
if action == "copy":
|
||||
event_system.emit("copy_files")
|
||||
event_system.emit_and_await("copy_files")
|
||||
if action == "copy_path":
|
||||
event_system.emit("copy_path")
|
||||
event_system.emit_and_await("copy_path")
|
||||
if action == "copy_name":
|
||||
event_system.emit("copy_name")
|
||||
event_system.emit_and_await("copy_name")
|
||||
if action == "copy_path_name":
|
||||
event_system.emit("copy_path_name")
|
||||
event_system.emit_and_await("copy_path_name")
|
||||
if action == "paste":
|
||||
event_system.emit("paste_files")
|
||||
event_system.emit_and_await("paste_files")
|
||||
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"]:
|
||||
event_system.emit("save_load_session", (action))
|
||||
event_system.emit_and_await("save_load_session", (action))
|
||||
|
||||
if action == "about_page":
|
||||
event_system.emit("show_about_page")
|
||||
event_system.emit_and_await("show_about_page")
|
||||
if action == "io_popup":
|
||||
event_system.emit("show_io_popup")
|
||||
event_system.emit_and_await("show_io_popup")
|
||||
if action == "plugins_popup":
|
||||
event_system.emit("show_plugins_popup")
|
||||
event_system.emit_and_await("show_plugins_popup")
|
||||
if action == "messages_popup":
|
||||
event_system.emit("show_messages_popup")
|
||||
event_system.emit_and_await("show_messages_popup")
|
||||
if action == "ui_debug":
|
||||
event_system.emit("load_interactive_debug")
|
||||
event_system.emit_and_await("load_interactive_debug")
|
||||
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):
|
||||
|
@ -185,11 +194,14 @@ class Controller(UIMixin, SignalsMixins, Controller_Data):
|
|||
def tggl_top_main_menubar(self, widget=None, eve=None):
|
||||
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 = None
|
||||
|
||||
def open_terminal(self, widget=None, eve=None):
|
||||
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
||||
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
||||
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):
|
||||
self.builder.get_object("path_entry").set_text(path)
|
||||
self.builder.get_object("path_entry").set_text(path)
|
||||
|
|
|
@ -29,7 +29,7 @@ class Controller_Data:
|
|||
|
||||
self._load_glade_file()
|
||||
self.fm_controller = WindowController()
|
||||
self.plugins = PluginsController()
|
||||
self.plugins_controller = PluginsController()
|
||||
self.fm_controller_data = self.fm_controller.get_state_from_file()
|
||||
|
||||
self.window1 = self.builder.get_object("window_1")
|
||||
|
@ -70,23 +70,16 @@ class Controller_Data:
|
|||
Returns:
|
||||
state (obj): State
|
||||
'''
|
||||
# state = State()
|
||||
|
||||
state = self._state
|
||||
state.fm_controller = self.fm_controller
|
||||
state.notebooks = self.notebooks
|
||||
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.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()
|
||||
|
||||
# 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.user_pass_dialog = self.user_pass_dialog
|
||||
# state.message_dialog = MessageWidget()
|
||||
# state.user_pass_dialog = UserPassWidget()
|
||||
|
||||
selected_files = state.icon_grid.get_selected_items()
|
||||
if selected_files:
|
||||
|
@ -121,6 +114,9 @@ class Controller_Data:
|
|||
|
||||
uris.append(fpath)
|
||||
|
||||
tab = None
|
||||
dir = None
|
||||
|
||||
return uris
|
||||
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ class FileSystemActions(HandlerMixin, CRUDMixin):
|
|||
|
||||
event_system.subscribe("open_files", self.open_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("cut_files", self.cut_files)
|
||||
|
@ -104,6 +105,12 @@ class FileSystemActions(HandlerMixin, CRUDMixin):
|
|||
|
||||
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):
|
||||
state = event_system.emit_and_await("get_current_state")
|
||||
|
@ -111,4 +118,4 @@ class FileSystemActions(HandlerMixin, CRUDMixin):
|
|||
command = None
|
||||
for path in state.uris:
|
||||
command = f"{shlex.quote(path)}" if not in_terminal else f"{state.tab.terminal_app} -e {shlex.quote(path)}"
|
||||
state.tab.execute(shlex.split(command), start_dir=state.tab.get_current_directory())
|
||||
state.tab.execute(shlex.split(command), start_dir=state.tab.get_current_directory())
|
|
@ -110,26 +110,30 @@ class HandlerMixin:
|
|||
tab.move_file(fPath, tPath)
|
||||
else:
|
||||
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":
|
||||
file.copy_async(destination=target,
|
||||
flags=Gio.FileCopyFlags.BACKUP,
|
||||
io_priority=98,
|
||||
io_priority=45,
|
||||
cancellable=io_widget.cancle_eve,
|
||||
progress_callback=io_widget.update_progress,
|
||||
callback=io_widget.finish_callback)
|
||||
|
||||
self._builder.get_object("io_list").add(io_widget)
|
||||
if action == "move" or action == "rename":
|
||||
file.move_async(destination=target,
|
||||
flags=Gio.FileCopyFlags.BACKUP,
|
||||
io_priority=98,
|
||||
io_priority=45,
|
||||
cancellable=io_widget.cancle_eve,
|
||||
progress_callback=None,
|
||||
# NOTE: progress_callback here causes seg fault when set
|
||||
progress_callback=None,
|
||||
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:
|
||||
raise OSError(e)
|
||||
|
@ -162,4 +166,4 @@ class HandlerMixin:
|
|||
target = Gio.File.new_for_path(f"{base_path}/{file_name}-copy{i}{extension}")
|
||||
i += 1
|
||||
|
||||
return target
|
||||
return target
|
|
@ -30,57 +30,53 @@ class FileActionSignalsMixin:
|
|||
|
||||
wid = tab.get_wid()
|
||||
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)
|
||||
|
||||
# NOTE: Too lazy to impliment a proper update handler and so just regen store and update tab.
|
||||
# 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):
|
||||
def dir_watch_updates(self, file_monitor, file, other_file = None, eve_type = None, tab_widget_id = None):
|
||||
if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED,
|
||||
Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN,
|
||||
Gio.FileMonitorEvent.MOVED_OUT]:
|
||||
logger.debug(eve_type)
|
||||
|
||||
if eve_type in [Gio.FileMonitorEvent.MOVED_IN, Gio.FileMonitorEvent.MOVED_OUT]:
|
||||
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])
|
||||
self.soft_lock_countdown(tab_widget_id)
|
||||
|
||||
@threaded
|
||||
def soft_lock_countdown(self, tab_widget):
|
||||
self.soft_update_lock[tab_widget] = { "last_update_time": time.time()}
|
||||
def soft_lock_countdown(self, tab_widget_id):
|
||||
if tab_widget_id in self.soft_update_lock:
|
||||
timeout_id = self.soft_update_lock[tab_widget_id]["timeout_id"]
|
||||
GLib.source_remove(timeout_id)
|
||||
|
||||
lock = True
|
||||
while lock:
|
||||
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,))
|
||||
timeout_id = GLib.timeout_add(0, self.update_on_soft_lock_end, 600, *(tab_widget_id,))
|
||||
self.soft_update_lock[tab_widget_id] = { "timeout_id": timeout_id }
|
||||
|
||||
|
||||
def update_on_soft_lock_end(self, tab_widget):
|
||||
wid, tid = tab_widget.split("|")
|
||||
def update_on_soft_lock_end(self, timout_ms, tab_widget_id):
|
||||
self.soft_update_lock.pop(tab_widget_id, None)
|
||||
|
||||
wid, tid = tab_widget_id.split("|")
|
||||
notebook = self.builder.get_object(f"window_{wid}")
|
||||
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)
|
||||
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()
|
||||
icon_grid.clear_and_set_new_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()
|
||||
if [wid, tid] in [state.wid, state.tid]:
|
||||
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):
|
||||
if not self.ctrl_down and not self.shift_down and not self.alt_down:
|
||||
|
|
|
@ -11,8 +11,8 @@ from gi.repository import Gdk
|
|||
# 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. """
|
||||
|
||||
# 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.shift_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):
|
||||
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 "control" in keyname:
|
||||
self.ctrl_down = True
|
||||
|
@ -36,52 +48,49 @@ class KeyboardSignalsMixin:
|
|||
self.alt_down = True
|
||||
|
||||
def on_global_key_release_controller(self, widget, event):
|
||||
"""Handler for keyboard events"""
|
||||
keyname = Gdk.keyval_name(event.keyval).lower()
|
||||
""" Handler for keyboard events """
|
||||
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 "control" in keyname:
|
||||
self.ctrl_down = False
|
||||
if "shift" in keyname:
|
||||
self.shift_down = False
|
||||
if "alt" in keyname:
|
||||
self.alt_down = False
|
||||
should_return = self.was_midified_key and (self.ctrl_down or self.shift_down or self.alt_down)
|
||||
self.unmap_special_keys(keyname)
|
||||
|
||||
if should_return:
|
||||
self.was_midified_key = False
|
||||
return
|
||||
|
||||
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:
|
||||
# See if in filemanager scope
|
||||
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)
|
||||
self.handle_mapped_key_event(mapping)
|
||||
else:
|
||||
logger.debug(f"on_global_key_release_controller > key > {keyname}")
|
||||
self.handle_as_key_event_scope(keyname)
|
||||
|
||||
if self.ctrl_down:
|
||||
if keyname in ["1", "kp_1", "2", "kp_2", "3", "kp_3", "4", "kp_4"]:
|
||||
self.builder.get_object(f"tggl_notebook_{keyname.strip('kp_')}").released()
|
||||
def handle_mapped_key_event(self, mapping):
|
||||
try:
|
||||
self.handle_as_controller_scope(mapping)
|
||||
except Exception:
|
||||
self.handle_as_plugin_scope(mapping)
|
||||
|
||||
def handle_plugin_key_event(self, sender, eve_type):
|
||||
event_system.emit(eve_type)
|
||||
def handle_as_controller_scope(self, mapping):
|
||||
getattr(self, mapping)()
|
||||
|
||||
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()
|
||||
def handle_as_plugin_scope(self, mapping):
|
||||
if "||" in mapping:
|
||||
sender, eve_type = mapping.split("||")
|
||||
else:
|
||||
sender = ""
|
||||
eve_type = mapping
|
||||
|
||||
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()
|
||||
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)
|
|
@ -7,6 +7,7 @@ import gi
|
|||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import GLib
|
||||
from gi.repository import Gio
|
||||
|
||||
# Application imports
|
||||
from ...widgets.tab_header_widget import TabHeaderWidget
|
||||
|
@ -19,6 +20,17 @@ class GridMixin:
|
|||
"""docstring for GridMixin"""
|
||||
|
||||
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()
|
||||
files = tab.get_files()
|
||||
|
||||
|
@ -26,61 +38,77 @@ class GridMixin:
|
|||
store.append([None, file[0]])
|
||||
|
||||
Gtk.main_iteration()
|
||||
# for i, file in enumerate(files):
|
||||
# self.create_icon(i, tab, store, dir, file[0])
|
||||
|
||||
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) )
|
||||
self.generate_icons(tab, store, dir, files)
|
||||
# GLib.Thread("", self.generate_icons, tab, store, dir, files)
|
||||
|
||||
# NOTE: Not likely called often from here but it could be useful
|
||||
if save_state and not trace_debug:
|
||||
self.fm_controller.save_state()
|
||||
|
||||
async def create_icons(self, tab, store, dir, files):
|
||||
tasks = [self.update_store(i, store, dir, tab, file[0]) for i, file in enumerate(files)]
|
||||
await asyncio.gather(*tasks)
|
||||
dir = None
|
||||
files = None
|
||||
|
||||
async def load_icon(self, i, store, icon):
|
||||
self.update_store(i, store, icon)
|
||||
@daemon_threaded
|
||||
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):
|
||||
icon = tab.create_icon(dir, file)
|
||||
def update_store(self, i, store, icon):
|
||||
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)
|
||||
|
||||
def create_icons_generator(self, tab, dir, files):
|
||||
for file in files:
|
||||
icon = tab.create_icon(dir, file[0])
|
||||
yield icon
|
||||
# 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.
|
||||
return False
|
||||
|
||||
# @daemon_threaded
|
||||
# def create_icon(self, i, tab, store, dir, file):
|
||||
# icon = tab.create_icon(dir, file)
|
||||
# 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 do_ui_update(self):
|
||||
Gtk.main_iteration()
|
||||
return False
|
||||
|
||||
# def update_store(self, i, store, icon):
|
||||
# itr = store.get_iter(i)
|
||||
# store.set_value(itr, 0, icon)
|
||||
|
||||
def create_tab_widget(self, tab):
|
||||
return TabHeaderWidget(tab, self.close_tab)
|
||||
def create_tab_widget(self):
|
||||
return TabHeaderWidget(self.close_tab)
|
||||
|
||||
def create_scroll_and_store(self, tab, wid, use_tree_view = False):
|
||||
scroll = Gtk.ScrolledWindow()
|
||||
|
@ -137,6 +165,7 @@ class GridMixin:
|
|||
store = icon_grid.get_model()
|
||||
tab_label = notebook.get_tab_label(obj).get_children()[0]
|
||||
|
||||
icon_grid = None
|
||||
return store, tab_label
|
||||
|
||||
def get_icon_grid_from_notebook(self, notebook, _name):
|
||||
|
|
|
@ -34,27 +34,46 @@ class TabMixin(GridMixin):
|
|||
else:
|
||||
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)
|
||||
index = notebook.append_page(scroll, tab_widget)
|
||||
notebook.set_tab_detachable(scroll, True)
|
||||
notebook.set_tab_reorderable(scroll, True)
|
||||
|
||||
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.set_current_page(index)
|
||||
|
||||
ctx = notebook.get_style_context()
|
||||
ctx.add_class("notebook-unselected-focus")
|
||||
notebook.set_tab_reorderable(scroll, True)
|
||||
self.load_store(tab, store)
|
||||
self.set_window_title()
|
||||
event_system.emit("set_window_title", (tab.get_current_directory(),))
|
||||
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):
|
||||
notebook = button.get_parent().get_parent()
|
||||
if notebook.get_n_pages() == 1:
|
||||
notebook = None
|
||||
return
|
||||
|
||||
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}")
|
||||
|
||||
iter = store.get_iter_first()
|
||||
while iter:
|
||||
next_iter = store.iter_next(iter)
|
||||
store.unref_node(iter)
|
||||
iter = next_iter
|
||||
|
||||
store.clear()
|
||||
icon_grid.destroy()
|
||||
scroll.destroy()
|
||||
tab_box.destroy()
|
||||
store.run_dispose()
|
||||
|
||||
del store
|
||||
del icon_grid
|
||||
del scroll
|
||||
del tab_box
|
||||
del watcher
|
||||
del tab
|
||||
icon_grid.set_model(None)
|
||||
icon_grid.run_dispose()
|
||||
scroll.run_dispose()
|
||||
tab_box.run_dispose()
|
||||
|
||||
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():
|
||||
self.fm_controller.save_state()
|
||||
|
||||
self.set_window_title()
|
||||
gc.collect()
|
||||
|
||||
# NOTE: Not actually getting called even tho set in the glade file...
|
||||
def on_tab_dnded(self, notebook, page, x, y):
|
||||
|
@ -111,15 +142,23 @@ class TabMixin(GridMixin):
|
|||
if not settings_manager.is_trace_debug():
|
||||
self.fm_controller.save_state()
|
||||
|
||||
wid, tid = None, None
|
||||
window = None
|
||||
tab = None
|
||||
|
||||
|
||||
def on_tab_switch_update(self, notebook, content = None, index = None):
|
||||
self.selected_files.clear()
|
||||
wid, tid = content.get_children()[0].get_name().split("|")
|
||||
|
||||
self.fm_controller.set_wid_and_tid(wid, tid)
|
||||
self.set_path_text(wid, tid)
|
||||
self.set_window_title()
|
||||
|
||||
wid, tid = None, None
|
||||
|
||||
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):
|
||||
return notebook.get_tab_label(icon_grid.get_parent()).get_children()[0]
|
||||
|
@ -135,6 +174,8 @@ class TabMixin(GridMixin):
|
|||
state.tab.load_directory()
|
||||
self.load_store(state.tab, state.store)
|
||||
|
||||
state = None
|
||||
|
||||
def update_tab(self, tab_label, tab, store, wid, tid):
|
||||
self.load_store(tab, store)
|
||||
self.set_path_text(wid, tid)
|
||||
|
@ -175,16 +216,38 @@ class TabMixin(GridMixin):
|
|||
if isinstance(focused_obj, Gtk.Entry):
|
||||
self.process_path_menu(widget, tab, dir)
|
||||
|
||||
action = None
|
||||
store = None
|
||||
|
||||
if path.endswith(".") or path == dir:
|
||||
tab_label = None
|
||||
notebook = None
|
||||
wid, tid = None, None
|
||||
path = None
|
||||
tab = None
|
||||
return
|
||||
|
||||
if not tab.set_path(path):
|
||||
tab_label = None
|
||||
notebook = None
|
||||
wid, tid = None, None
|
||||
path = None
|
||||
tab = None
|
||||
return
|
||||
|
||||
icon_grid = self.get_icon_grid_from_notebook(notebook, f"{wid}|{tid}")
|
||||
icon_grid.clear_and_set_new_store()
|
||||
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):
|
||||
path_menu_buttons = self.builder.get_object("path_menu_buttons")
|
||||
query = gtk_entry.get_text().replace(dir, "")
|
||||
|
@ -201,11 +264,16 @@ class TabMixin(GridMixin):
|
|||
path_menu_buttons.add(button)
|
||||
show_path_menu = True
|
||||
|
||||
query = None
|
||||
files = None
|
||||
|
||||
if not show_path_menu:
|
||||
path_menu_buttons = None
|
||||
event_system.emit("hide_path_menu")
|
||||
else:
|
||||
event_system.emit("show_path_menu")
|
||||
buttons = path_menu_buttons.get_children()
|
||||
path_menu_buttons = None
|
||||
|
||||
if len(buttons) == 1:
|
||||
self.slowed_focus(buttons[0])
|
||||
|
@ -218,6 +286,7 @@ class TabMixin(GridMixin):
|
|||
def do_focused_click(self, button):
|
||||
button.grab_focus()
|
||||
button.clicked()
|
||||
return False
|
||||
|
||||
def set_path_entry(self, button = None, eve = None):
|
||||
self.path_auto_filled = True
|
||||
|
@ -230,6 +299,10 @@ class TabMixin(GridMixin):
|
|||
path_entry.set_position(-1)
|
||||
event_system.emit("hide_path_menu")
|
||||
|
||||
state = None
|
||||
path = None
|
||||
path_entry = None
|
||||
|
||||
|
||||
def show_hide_hidden_files(self):
|
||||
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.load_directory()
|
||||
self.builder.get_object("refresh_tab").released()
|
||||
|
||||
wid, tid = None, None
|
||||
tab = None
|
|
@ -46,11 +46,19 @@ class WindowMixin(TabMixin):
|
|||
self.window.set_title(f"{app_name} ~ {dir}")
|
||||
self.set_bottom_labels(tab)
|
||||
|
||||
wid, tid = None, None
|
||||
notebook = None
|
||||
tab = None
|
||||
dir = None
|
||||
|
||||
def set_path_text(self, wid, tid):
|
||||
path_entry = self.builder.get_object("path_entry")
|
||||
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
||||
path_entry.set_text(tab.get_current_directory())
|
||||
|
||||
path_entry = None
|
||||
tab = None
|
||||
|
||||
def grid_set_selected_items(self, icons_grid):
|
||||
new_items = icons_grid.get_selected_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)
|
||||
else:
|
||||
event_system.emit("open_files")
|
||||
|
||||
state = None
|
||||
notebook = None
|
||||
tab_label = None
|
||||
except WindowException as e:
|
||||
traceback.print_exc()
|
||||
self.display_message(settings.theming.error_color, f"{repr(e)}")
|
||||
|
@ -164,6 +176,12 @@ class WindowMixin(TabMixin):
|
|||
if target not in current:
|
||||
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):
|
||||
if info == 80:
|
||||
|
@ -177,6 +195,10 @@ class WindowMixin(TabMixin):
|
|||
if from_uri != 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):
|
||||
self.create_tab(wid, None, path)
|
||||
self.create_tab(wid, None, path)
|
|
@ -6,6 +6,7 @@ gi.require_version('Gtk', '3.0')
|
|||
gi.require_version('Gdk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import Gdk
|
||||
from gi.repository import GLib
|
||||
|
||||
# Application imports
|
||||
from .mixins.ui.pane_mixin import PaneMixin
|
||||
|
@ -34,18 +35,20 @@ class UIMixin(PaneMixin, WindowMixin):
|
|||
nickname = session["window"]["Nickname"]
|
||||
tabs = session["window"]["tabs"]
|
||||
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):
|
||||
import time
|
||||
|
||||
window = settings_manager.get_main_window()
|
||||
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))
|
||||
|
||||
window = None
|
||||
|
||||
def _current_loading_process(self, session_json = None):
|
||||
if session_json:
|
||||
for j, value in enumerate(session_json):
|
||||
|
@ -77,7 +80,7 @@ class UIMixin(PaneMixin, WindowMixin):
|
|||
|
||||
scroll_win = notebook.get_children()[-1]
|
||||
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:
|
||||
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))
|
||||
|
@ -85,4 +88,4 @@ class UIMixin(PaneMixin, WindowMixin):
|
|||
for j in range(0, 4):
|
||||
i = j + 1
|
||||
self.fm_controller.create_window()
|
||||
self.create_new_tab_notebook(None, i, None)
|
||||
self.create_new_tab_notebook(None, i, None)
|
||||
|
|
|
@ -16,10 +16,7 @@ class ContextMenuWidget(Gtk.Menu):
|
|||
def __init__(self):
|
||||
super(ContextMenuWidget, self).__init__()
|
||||
|
||||
self.builder = settings_manager.get_builder()
|
||||
self._builder = Gtk.Builder()
|
||||
self._context_menu_data = settings_manager.get_context_menu_data()
|
||||
self._window = settings_manager.get_main_window()
|
||||
self._builder = Gtk.Builder()
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
|
@ -32,24 +29,57 @@ class ContextMenuWidget(Gtk.Menu):
|
|||
def _setup_signals(self):
|
||||
event_system.subscribe("show_context_menu", self.show_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):
|
||||
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()
|
||||
|
||||
def _emit(self, menu_item, 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_item = Gtk.MenuItem(name)
|
||||
|
||||
for key in keys:
|
||||
for key, value in data.items():
|
||||
if isinstance(data, dict):
|
||||
entry = self.make_menu_item(key, data[key])
|
||||
entry = self.make_menu_item(key, value)
|
||||
elif isinstance(data, list):
|
||||
entry = self.make_menu_item(key, data)
|
||||
entry = self.make_menu_item(key, value)
|
||||
else:
|
||||
continue
|
||||
|
||||
|
@ -58,36 +88,9 @@ class ContextMenuWidget(Gtk.Menu):
|
|||
menu_item.set_submenu(menu)
|
||||
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:
|
||||
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):
|
||||
def show_context_menu(self, widget = None, eve = 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()
|
||||
|
|
|
@ -39,11 +39,10 @@ class AboutWidget:
|
|||
self.about_page = self._builder.get_object("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()
|
||||
if response in [Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT]:
|
||||
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()
|
||||
|
|
|
@ -50,6 +50,9 @@ class RenameWidget:
|
|||
def show_rename_file_menu(self, widget=None, eve=None):
|
||||
if widget:
|
||||
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()
|
||||
if response == Gtk.ResponseType.CLOSE:
|
||||
|
@ -78,4 +81,4 @@ class RenameWidget:
|
|||
def hide_rename_file_menu_enter_key(self, widget=None, eve=None):
|
||||
keyname = Gdk.keyval_name(eve.keyval).lower()
|
||||
if keyname in ["return", "enter"]:
|
||||
self._rename_file_menu.hide()
|
||||
self._rename_file_menu.hide()
|
|
@ -1,11 +1,9 @@
|
|||
# Python imports
|
||||
|
||||
# Lib imports
|
||||
import gi
|
||||
gi.require_version('Gtk', '3.0')
|
||||
from gi.repository import Gtk
|
||||
|
||||
# Application imports
|
||||
from ...sfm_builder import SFMBuilder
|
||||
from ...mixins.signals.file_action_signals_mixin import FileActionSignalsMixin
|
||||
from .window_mixin import WindowMixin
|
||||
|
||||
|
@ -27,7 +25,8 @@ class FilesWidget(FileActionSignalsMixin, WindowMixin):
|
|||
|
||||
self.INDEX = self.ccount
|
||||
self.NAME = f"window_{self.INDEX}"
|
||||
self.builder = Gtk.Builder()
|
||||
self.builder = SFMBuilder()
|
||||
|
||||
self.files_view = None
|
||||
self.fm_controller = None
|
||||
|
||||
|
@ -41,20 +40,21 @@ class FilesWidget(FileActionSignalsMixin, WindowMixin):
|
|||
...
|
||||
|
||||
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):
|
||||
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)
|
||||
|
||||
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.set_group_name("files_widget")
|
||||
|
||||
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 tabs:
|
||||
for tab in tabs:
|
||||
|
@ -62,10 +62,13 @@ class FilesWidget(FileActionSignalsMixin, WindowMixin):
|
|||
else:
|
||||
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):
|
||||
if win_index == str(self.INDEX):
|
||||
return self.builder.get_object(f"{self.INDEX}|{tid}|icon_grid", use_gtk = False)
|
||||
|
||||
|
||||
def set_fm_controller(self, _fm_controller):
|
||||
self.fm_controller = _fm_controller
|
||||
self.fm_controller = _fm_controller
|
|
@ -7,6 +7,7 @@ import gi
|
|||
gi.require_version("Gtk", "3.0")
|
||||
from gi.repository import Gtk
|
||||
from gi.repository import GLib
|
||||
from gi.repository import Gio
|
||||
|
||||
# Application imports
|
||||
from ...widgets.tab_header_widget import TabHeaderWidget
|
||||
|
@ -19,6 +20,17 @@ class GridMixin:
|
|||
"""docstring for GridMixin"""
|
||||
|
||||
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()
|
||||
files = tab.get_files()
|
||||
|
||||
|
@ -26,60 +38,76 @@ class GridMixin:
|
|||
store.append([None, file[0]])
|
||||
|
||||
Gtk.main_iteration()
|
||||
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:
|
||||
# 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) )
|
||||
self.generate_icons(tab, store, dir, files)
|
||||
# GLib.Thread("", self.generate_icons, tab, store, dir, files)
|
||||
|
||||
# NOTE: Not likely called often from here but it could be useful
|
||||
if save_state and not trace_debug:
|
||||
self.fm_controller.save_state()
|
||||
|
||||
async def create_icons(self, tab, store, dir, files):
|
||||
tasks = [self.update_store(i, store, dir, tab, file[0]) for i, file in enumerate(files)]
|
||||
await asyncio.gather(*tasks)
|
||||
dir = None
|
||||
files = None
|
||||
|
||||
async def load_icon(self, i, store, icon):
|
||||
self.update_store(i, store, icon)
|
||||
@daemon_threaded
|
||||
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):
|
||||
icon = tab.create_icon(dir, file)
|
||||
def update_store(self, i, store, icon):
|
||||
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)
|
||||
|
||||
def create_icons_generator(self, tab, dir, files):
|
||||
for file in files:
|
||||
icon = tab.create_icon(dir, file[0])
|
||||
yield icon
|
||||
# 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.
|
||||
return False
|
||||
|
||||
# @daemon_threaded
|
||||
# def create_icon(self, i, tab, store, dir, file):
|
||||
# icon = tab.create_icon(dir, file)
|
||||
# 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 do_ui_update(self):
|
||||
Gtk.main_iteration()
|
||||
return False
|
||||
|
||||
def create_tab_widget(self, tab):
|
||||
return TabHeaderWidget(tab, self.close_tab)
|
||||
def create_tab_widget(self):
|
||||
return TabHeaderWidget(self.close_tab)
|
||||
|
||||
def create_scroll_and_store(self, tab, wid, use_tree_view = False):
|
||||
scroll = Gtk.ScrolledWindow()
|
||||
|
@ -137,3 +165,10 @@ class GridMixin:
|
|||
tab_label = notebook.get_tab_label(obj).get_children()[0]
|
||||
|
||||
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
|
||||
|
|
|
@ -34,29 +34,46 @@ class TabMixin(GridMixin):
|
|||
else:
|
||||
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)
|
||||
index = notebook.append_page(scroll, tab_widget)
|
||||
notebook.set_tab_detachable(scroll, True)
|
||||
notebook.set_tab_reorderable(scroll, True)
|
||||
|
||||
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())
|
||||
event_system.emit("go_to_path", (tab.get_current_directory(),)) # NOTE: Not efficent if I understand how
|
||||
notebook.show_all()
|
||||
notebook.set_current_page(index)
|
||||
|
||||
ctx = notebook.get_style_context()
|
||||
ctx.add_class("notebook-unselected-focus")
|
||||
notebook.set_tab_reorderable(scroll, True)
|
||||
self.load_store(tab, store)
|
||||
# self.set_window_title()
|
||||
event_system.emit("set_window_title", (tab.get_current_directory(),))
|
||||
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):
|
||||
notebook = button.get_parent().get_parent()
|
||||
if notebook.get_n_pages() == 1:
|
||||
notebook = None
|
||||
return
|
||||
|
||||
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}")
|
||||
|
||||
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()
|
||||
icon_grid.destroy()
|
||||
# icon_grid.run_dispose()
|
||||
scroll.destroy()
|
||||
#scroll.run_dispose()
|
||||
tab_box.destroy()
|
||||
#tab_box.run_dispose()
|
||||
store.run_dispose()
|
||||
|
||||
del store
|
||||
del icon_grid
|
||||
del scroll
|
||||
del tab_box
|
||||
del watcher
|
||||
del tab
|
||||
icon_grid.set_model(None)
|
||||
icon_grid.run_dispose()
|
||||
scroll.run_dispose()
|
||||
tab_box.run_dispose()
|
||||
|
||||
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():
|
||||
self.fm_controller.save_state()
|
||||
|
||||
self.set_window_title()
|
||||
|
||||
gc.collect()
|
||||
|
||||
# NOTE: Not actually getting called even tho set in the glade file...
|
||||
def on_tab_dnded(self, notebook, page, x, y):
|
||||
...
|
||||
|
@ -117,15 +143,22 @@ class TabMixin(GridMixin):
|
|||
if not settings_manager.is_trace_debug():
|
||||
self.fm_controller.save_state()
|
||||
|
||||
wid, tid = None, None
|
||||
window = None
|
||||
tab = None
|
||||
|
||||
|
||||
def on_tab_switch_update(self, notebook, content = None, index = None):
|
||||
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.set_path_text(wid, tid)
|
||||
self.set_window_title()
|
||||
|
||||
wid, tid = None, None
|
||||
|
||||
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):
|
||||
return notebook.get_tab_label(icon_grid.get_parent()).get_children()[0]
|
||||
|
@ -141,6 +174,8 @@ class TabMixin(GridMixin):
|
|||
state.tab.load_directory()
|
||||
self.load_store(state.tab, state.store)
|
||||
|
||||
state = None
|
||||
|
||||
def update_tab(self, tab_label, tab, store, wid, tid):
|
||||
self.load_store(tab, store)
|
||||
self.set_path_text(wid, tid)
|
||||
|
@ -181,16 +216,38 @@ class TabMixin(GridMixin):
|
|||
if isinstance(focused_obj, Gtk.Entry):
|
||||
self.process_path_menu(widget, tab, dir)
|
||||
|
||||
action = None
|
||||
store = None
|
||||
|
||||
if path.endswith(".") or path == dir:
|
||||
tab_label = None
|
||||
notebook = None
|
||||
wid, tid = None, None
|
||||
path = None
|
||||
tab = None
|
||||
return
|
||||
|
||||
if not tab.set_path(path):
|
||||
tab_label = None
|
||||
notebook = None
|
||||
wid, tid = None, None
|
||||
path = None
|
||||
tab = None
|
||||
return
|
||||
|
||||
icon_grid = self.get_icon_grid_from_notebook(notebook, f"{wid}|{tid}")
|
||||
icon_grid.clear_and_set_new_store()
|
||||
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):
|
||||
path_menu_buttons = self.builder.get_object("path_menu_buttons")
|
||||
query = gtk_entry.get_text().replace(dir, "")
|
||||
|
@ -207,6 +264,10 @@ class TabMixin(GridMixin):
|
|||
path_menu_buttons.add(button)
|
||||
show_path_menu = True
|
||||
|
||||
path_menu_buttons = None
|
||||
query = None
|
||||
files = None
|
||||
|
||||
if not show_path_menu:
|
||||
event_system.emit("hide_path_menu")
|
||||
else:
|
||||
|
@ -225,6 +286,8 @@ class TabMixin(GridMixin):
|
|||
button.grab_focus()
|
||||
button.clicked()
|
||||
|
||||
return False
|
||||
|
||||
def set_path_entry(self, button = None, eve = None):
|
||||
self.path_auto_filled = True
|
||||
state = self.get_current_state()
|
||||
|
@ -236,9 +299,16 @@ class TabMixin(GridMixin):
|
|||
path_entry.set_position(-1)
|
||||
event_system.emit("hide_path_menu")
|
||||
|
||||
state = None
|
||||
path = None
|
||||
path_entry = None
|
||||
|
||||
def show_hide_hidden_files(self):
|
||||
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
||||
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
||||
tab.set_hiding_hidden(not tab.is_hiding_hidden())
|
||||
tab.load_directory()
|
||||
self.builder.get_object("refresh_tab").released()
|
||||
|
||||
wid, tid = None, None
|
||||
tab = None
|
||||
|
|
|
@ -42,10 +42,17 @@ class WindowMixin(TabMixin):
|
|||
event_system.emit("set_window_title", (dir,))
|
||||
self.set_bottom_labels(tab)
|
||||
|
||||
wid, tid = None, None
|
||||
notebook = None
|
||||
tab = None
|
||||
dir = None
|
||||
|
||||
def set_path_text(self, wid, tid):
|
||||
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
||||
event_system.emit("go_to_path", (tab.get_current_directory(),))
|
||||
|
||||
tab = None
|
||||
|
||||
def grid_set_selected_items(self, icons_grid):
|
||||
new_items = icons_grid.get_selected_items()
|
||||
items_size = len(new_items)
|
||||
|
@ -160,6 +167,12 @@ class WindowMixin(TabMixin):
|
|||
if target not in current:
|
||||
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):
|
||||
if info == 80:
|
||||
|
@ -173,6 +186,10 @@ class WindowMixin(TabMixin):
|
|||
if from_uri != 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):
|
||||
self.create_tab(wid, None, path)
|
||||
|
|
|
@ -75,6 +75,20 @@ class IconGridWidget(Gtk.IconView):
|
|||
return self.get_model()
|
||||
|
||||
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)
|
||||
store = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str or None)
|
||||
# store = Gtk.ListStore(Gtk.DirectoryList)
|
||||
self.set_model(store)
|
||||
store = None
|
||||
|
|
|
@ -59,7 +59,7 @@ class IconTreeWidget(Gtk.TreeView):
|
|||
name = Gtk.CellRendererText()
|
||||
selec = self.get_selection()
|
||||
|
||||
self.set_model(store)
|
||||
self.set_model(self._store)
|
||||
selec.set_mode(3)
|
||||
|
||||
column.pack_start(icon, False)
|
||||
|
|
|
@ -65,19 +65,20 @@ class IOWidget(Gtk.Box):
|
|||
logger.info(f"Canceling: [{self._action}] of {self._basename} ...")
|
||||
eve.cancel()
|
||||
|
||||
def update_progress(self, current, total, eve=None):
|
||||
def update_progress(self, current, total, eve = None):
|
||||
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":
|
||||
status = self._file.move_finish(task)
|
||||
if self._action == "copy":
|
||||
status = self._file.copy_finish(task)
|
||||
|
||||
if status:
|
||||
self.delete_self()
|
||||
else:
|
||||
logger.info(f"{self._action} of {self._basename} failed...")
|
||||
self.delete_self()
|
||||
|
||||
def delete_self(self, widget=None, eve=None):
|
||||
self.get_parent().remove(self)
|
||||
def delete_self(self, widget = None, eve = None):
|
||||
self.get_parent().remove(self)
|
|
@ -61,7 +61,6 @@ class MessagePopupWidget(Gtk.Popover):
|
|||
scroll_window.set_hexpand(True)
|
||||
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)
|
||||
|
||||
scroll_window.add(message_text_view)
|
||||
|
@ -103,7 +102,7 @@ class MessagePopupWidget(Gtk.Popover):
|
|||
self.popup()
|
||||
self.hide_message_timeout(seconds)
|
||||
|
||||
@threaded
|
||||
@daemon_threaded
|
||||
def hide_message_timeout(self, seconds=3):
|
||||
time.sleep(seconds)
|
||||
GLib.idle_add(event_system.emit, ("hide_messages_popup"))
|
||||
|
|
|
@ -29,16 +29,16 @@ class PathMenuPopupWidget(Gtk.Popover):
|
|||
self.set_relative_to(path_entry)
|
||||
self.set_modal(False)
|
||||
self.set_position(Gtk.PositionType.BOTTOM)
|
||||
self.set_size_request(240, 420)
|
||||
self.set_size_request(480, 420)
|
||||
|
||||
def _setup_signals(self):
|
||||
event_system.subscribe("show_path_menu", self.show_path_menu)
|
||||
event_system.subscribe("hide_path_menu", self.hide_path_menu)
|
||||
|
||||
def _load_widgets(self):
|
||||
path_menu_buttons = Gtk.ButtonBox()
|
||||
scroll_window = Gtk.ScrolledWindow()
|
||||
view_port = Gtk.Viewport()
|
||||
scroll_window = Gtk.ScrolledWindow()
|
||||
view_port = Gtk.Viewport()
|
||||
path_menu_buttons = Gtk.Box()
|
||||
|
||||
scroll_window.set_vexpand(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)
|
||||
view_port.add(path_menu_buttons)
|
||||
scroll_window.add(view_port)
|
||||
scroll_window.show_all()
|
||||
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()
|
||||
|
||||
def hide_path_menu(self, widget=None, eve=None):
|
||||
self.popdown()
|
||||
def hide_path_menu(self, widget = None, eve = None):
|
||||
self.popdown()
|
|
@ -9,14 +9,12 @@ from gi.repository import Gtk
|
|||
|
||||
|
||||
|
||||
|
||||
class TabHeaderWidget(Gtk.Box):
|
||||
"""docstring for TabHeaderWidget"""
|
||||
|
||||
def __init__(self, tab, close_tab):
|
||||
def __init__(self, close_tab):
|
||||
super(TabHeaderWidget, self).__init__()
|
||||
|
||||
self._tab = tab
|
||||
self._close_tab = close_tab # NOTE: Close method in tab_mixin
|
||||
|
||||
self._setup_styling()
|
||||
|
@ -32,25 +30,19 @@ class TabHeaderWidget(Gtk.Box):
|
|||
...
|
||||
|
||||
def _load_widgets(self):
|
||||
label = Gtk.Label()
|
||||
tid = Gtk.Label()
|
||||
self.label = Gtk.Label()
|
||||
close = Gtk.Button()
|
||||
icon = Gtk.Image(stock=Gtk.STOCK_CLOSE)
|
||||
|
||||
label.set_label(f"{self._tab.get_end_of_path()}")
|
||||
label.set_width_chars(len(self._tab.get_end_of_path()))
|
||||
label.set_xalign(0.0)
|
||||
label.set_margin_left(25)
|
||||
label.set_margin_right(25)
|
||||
label.set_hexpand(True)
|
||||
tid.set_label(f"{self._tab.get_id()}")
|
||||
self.label.set_xalign(0.0)
|
||||
self.label.set_margin_left(25)
|
||||
self.label.set_margin_right(25)
|
||||
self.label.set_hexpand(True)
|
||||
|
||||
close.connect("released", self._close_tab)
|
||||
|
||||
close.add(icon)
|
||||
self.add(label)
|
||||
self.add(self.label)
|
||||
self.add(close)
|
||||
self.add(tid)
|
||||
|
||||
self.show_all()
|
||||
tid.hide()
|
||||
self.show_all()
|
|
@ -1,5 +1,4 @@
|
|||
# Python imports
|
||||
import time
|
||||
import signal
|
||||
|
||||
# Lib imports
|
||||
|
@ -10,6 +9,7 @@ gi.require_version('Gdk', '3.0')
|
|||
from gi.repository import Gtk
|
||||
from gi.repository import Gdk
|
||||
from gi.repository import GLib
|
||||
from gi.repository import GObject
|
||||
|
||||
# Application imports
|
||||
from core.controller import Controller
|
||||
|
@ -24,18 +24,20 @@ class Window(Gtk.ApplicationWindow):
|
|||
"""docstring for Window."""
|
||||
|
||||
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)
|
||||
|
||||
self._set_window_data()
|
||||
self._controller = None
|
||||
|
||||
self._setup_styling()
|
||||
self._setup_signals()
|
||||
self._subscribe_to_events()
|
||||
|
||||
self._load_widgets(args, unknownargs)
|
||||
|
||||
self._set_window_data()
|
||||
self._set_size_constraints()
|
||||
self.show()
|
||||
|
||||
|
||||
|
@ -66,6 +68,18 @@ class Window(Gtk.ApplicationWindow):
|
|||
|
||||
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:
|
||||
screen = self.get_screen()
|
||||
visual = screen.get_rgba_visual()
|
||||
|
@ -73,7 +87,7 @@ class Window(Gtk.ApplicationWindow):
|
|||
if visual != None and screen.is_composited():
|
||||
self.set_visual(visual)
|
||||
self.set_app_paintable(True)
|
||||
self.connect("draw", self._area_draw)
|
||||
# self.connect("draw", self._area_draw)
|
||||
|
||||
# bind css file
|
||||
cssProvider = Gtk.CssProvider()
|
||||
|
@ -87,12 +101,25 @@ class Window(Gtk.ApplicationWindow):
|
|||
cr.set_operator(cairo.OPERATOR_SOURCE)
|
||||
cr.paint()
|
||||
cr.set_operator(cairo.OPERATOR_OVER)
|
||||
|
||||
|
||||
def _load_interactive_debug(self):
|
||||
self.set_interactive_debugging(True)
|
||||
|
||||
|
||||
def _tear_down(self, widget = None, eve = None):
|
||||
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()
|
||||
Gtk.main_quit()
|
||||
Gtk.main_quit()
|
||||
|
||||
def main(self):
|
||||
Gtk.main()
|
||||
|
|
|
@ -15,32 +15,37 @@ class ManifestProcessorException(Exception):
|
|||
...
|
||||
|
||||
|
||||
@dataclass(slots=True)
|
||||
@dataclass(slots = True)
|
||||
class PluginInfo:
|
||||
path: str = None
|
||||
name: str = None
|
||||
author: str = None
|
||||
version: str = None
|
||||
support: str = None
|
||||
requests:{} = None
|
||||
reference: type = None
|
||||
path: str = None
|
||||
name: str = None
|
||||
author: str = None
|
||||
version: str = None
|
||||
support: str = None
|
||||
requests:{} = None
|
||||
reference: type = None
|
||||
pre_launch: bool = False
|
||||
|
||||
|
||||
class ManifestProcessor:
|
||||
def __init__(self, path, builder):
|
||||
manifest = join(path, "manifest.json")
|
||||
if not os.path.exists(manifest):
|
||||
manifest_pth = join(path, "manifest.json")
|
||||
if not os.path.exists(manifest_pth):
|
||||
raise ManifestProcessorException("Invalid Plugin Structure: Plugin doesn't have 'manifest.json'. Aboarting load...")
|
||||
|
||||
self._path = path
|
||||
self._builder = builder
|
||||
with open(manifest) as f:
|
||||
with open(manifest_pth) as f:
|
||||
data = json.load(f)
|
||||
self._manifest = data["manifest"]
|
||||
self._plugin = self.collect_info()
|
||||
|
||||
def is_pre_launch(self) -> bool:
|
||||
return self._plugin.pre_launch
|
||||
|
||||
def collect_info(self) -> PluginInfo:
|
||||
plugin = PluginInfo()
|
||||
|
||||
plugin.path = self._path
|
||||
plugin.name = self._manifest["name"]
|
||||
plugin.author = self._manifest["author"]
|
||||
|
@ -48,14 +53,16 @@ class ManifestProcessor:
|
|||
plugin.support = self._manifest["support"]
|
||||
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
|
||||
|
||||
def get_loading_data(self):
|
||||
loading_data = {}
|
||||
requests = self._plugin.requests
|
||||
keys = requests.keys()
|
||||
|
||||
if "ui_target" in keys:
|
||||
if "ui_target" in requests:
|
||||
if requests["ui_target"] in [
|
||||
"none", "other", "main_Window", "main_menu_bar",
|
||||
"main_menu_bttn_box_bar", "path_menu_bar", "plugin_control_list",
|
||||
|
@ -63,7 +70,7 @@ class ManifestProcessor:
|
|||
"window_2", "window_3", "window_4"
|
||||
]:
|
||||
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"])
|
||||
if loading_data["ui_target"] == None:
|
||||
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:
|
||||
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"]:
|
||||
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:
|
||||
loading_data["pass_ui_objects"] = []
|
||||
for ui_id in requests["pass_ui_objects"]:
|
||||
|
@ -87,8 +94,8 @@ class ManifestProcessor:
|
|||
except ManifestProcessorException as e:
|
||||
logger.error(repr(e))
|
||||
|
||||
if "bind_keys" in keys:
|
||||
if "bind_keys" in requests:
|
||||
if isinstance(requests["bind_keys"], list):
|
||||
loading_data["bind_keys"] = requests["bind_keys"]
|
||||
|
||||
return self._plugin, loading_data
|
||||
return self._plugin, loading_data
|
||||
|
|
|
@ -36,41 +36,76 @@ class PluginsController:
|
|||
|
||||
self._plugins_dir_watcher = None
|
||||
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.load_plugins()
|
||||
|
||||
def _set_plugins_watcher(self) -> None:
|
||||
self._plugins_dir_watcher = Gio.File.new_for_path(self._plugins_path) \
|
||||
.monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, Gio.Cancellable())
|
||||
self._plugins_dir_watcher.connect("changed", self._on_plugins_changed, ())
|
||||
|
||||
def _on_plugins_changed(self, file_monitor, file, other_file=None, eve_type=None, data=None):
|
||||
def _on_plugins_changed(self, file_monitor, file, other_file = None, eve_type = None, data = None):
|
||||
if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED,
|
||||
Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN,
|
||||
Gio.FileMonitorEvent.MOVED_OUT]:
|
||||
self.reload_plugins(file)
|
||||
|
||||
@daemon_threaded
|
||||
def load_plugins(self, file: str = None) -> None:
|
||||
logger.info(f"Loading plugins...")
|
||||
def pre_launch_plugins(self) -> None:
|
||||
logger.info(f"Loading pre-launch 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()
|
||||
|
||||
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)]:
|
||||
try:
|
||||
target = join(path, "plugin.py")
|
||||
manifest = ManifestProcessor(path, self._builder)
|
||||
for key in plugin_manifests:
|
||||
target_manifest = plugin_manifests[key]
|
||||
path, folder, manifest = target_manifest["path"], target_manifest["folder"], target_manifest["manifest"]
|
||||
|
||||
try:
|
||||
target = join(path, "plugin.py")
|
||||
if not os.path.exists(target):
|
||||
raise FileNotFoundError("Invalid Plugin Structure: Plugin doesn't have 'plugin.py'. Aboarting load...")
|
||||
|
||||
plugin, loading_data = manifest.get_loading_data()
|
||||
module = self.load_plugin_module(path, folder, target)
|
||||
|
||||
GLib.idle_add(self.execute_plugin, *(module, plugin, loading_data))
|
||||
# self.execute_plugin(module, plugin, loading_data)
|
||||
if is_pre_launch:
|
||||
self.execute_plugin(module, plugin, loading_data)
|
||||
else:
|
||||
GLib.idle_add(self.execute_plugin, *(module, plugin, loading_data))
|
||||
except InvalidPluginException as e:
|
||||
logger.info(f"Malformed Plugin: Not loading -->: '{folder}' !")
|
||||
logger.debug("Trace: ", traceback.print_exc())
|
||||
|
@ -100,25 +135,26 @@ class PluginsController:
|
|||
|
||||
def execute_plugin(self, module: type, plugin: PluginInfo, loading_data: []):
|
||||
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"].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"] )
|
||||
|
||||
if "pass_fm_events" in keys:
|
||||
if "pass_fm_events" in loading_data:
|
||||
plugin.reference.set_fm_event_system(event_system)
|
||||
plugin.reference.subscribe_to_events()
|
||||
|
||||
if "bind_keys" in keys:
|
||||
if "bind_keys" in loading_data:
|
||||
keybindings.append_bindings( loading_data["bind_keys"] )
|
||||
|
||||
plugin.reference.run()
|
||||
self._plugin_collection.append(plugin)
|
||||
|
||||
return False
|
||||
|
||||
def reload_plugins(self, file: str = None) -> None:
|
||||
logger.info(f"Reloading plugins...")
|
||||
parent_path = os.getcwd()
|
||||
|
|
|
@ -14,7 +14,6 @@ from random import randint
|
|||
from .utils.settings import Settings
|
||||
from .utils.launcher import Launcher
|
||||
from .utils.filehandler import FileHandler
|
||||
from .icons.icon import Icon
|
||||
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):
|
||||
self.logger = None
|
||||
self._id_length: int = 10
|
||||
|
||||
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:
|
||||
return self.get_path()
|
||||
|
||||
|
@ -264,7 +235,7 @@ class Tab(Settings, FileHandler, Launcher, Icon, Path):
|
|||
return int(text) if text.isdigit() else 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:
|
||||
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):
|
||||
self.error_message = text
|
||||
|
||||
|
||||
def create_icon(self, dir, file):
|
||||
return event_system.emit_and_await("create-thumbnail", (dir, file,))
|
||||
|
|
|
@ -41,11 +41,11 @@ class Launcher:
|
|||
|
||||
def execute(self, command, start_dir=os.getenv("HOME"), use_shell=False):
|
||||
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)
|
||||
except ShellFMLauncherException as e:
|
||||
self.logger.error(f"Couldn't execute: {command}")
|
||||
self.logger.error(e)
|
||||
logger.error(f"Couldn't execute: {command}")
|
||||
logger.error(e)
|
||||
|
||||
# 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):
|
||||
|
@ -53,8 +53,8 @@ class Launcher:
|
|||
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)
|
||||
except ShellFMLauncherException as e:
|
||||
self.logger.error(f"Couldn't execute and return thread: {command}")
|
||||
self.logger.error(e)
|
||||
logger.error(f"Couldn't execute and return thread: {command}")
|
||||
logger.error(e)
|
||||
return None
|
||||
|
||||
@threaded
|
||||
|
@ -63,7 +63,7 @@ class Launcher:
|
|||
|
||||
def remux_video(self, hash, file):
|
||||
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):
|
||||
self.check_remux_space()
|
||||
|
@ -83,8 +83,8 @@ class Launcher:
|
|||
proc = subprocess.Popen(command)
|
||||
proc.wait()
|
||||
except ShellFMLauncherException as e:
|
||||
self.logger.error(message)
|
||||
self.logger.error(e)
|
||||
logger.error(message)
|
||||
logger.error(e)
|
||||
return False
|
||||
|
||||
return True
|
||||
|
@ -94,7 +94,7 @@ class Launcher:
|
|||
try:
|
||||
limit = int(limit)
|
||||
except ShellFMLauncherException as e:
|
||||
self.logger.debug(e)
|
||||
logger.debug(e)
|
||||
return
|
||||
|
||||
usage = self.get_remux_folder_usage(self.REMUX_FOLDER)
|
||||
|
@ -113,4 +113,4 @@ class Launcher:
|
|||
if not os.path.islink(fp): # Skip if it is symbolic link
|
||||
total_size += os.path.getsize(fp)
|
||||
|
||||
return total_size
|
||||
return total_size
|
|
@ -14,8 +14,6 @@ class ShellFMSettingsException(Exception):
|
|||
|
||||
|
||||
class Settings:
|
||||
logger = None
|
||||
|
||||
# NOTE: app_name should be defined using python 'builtins'
|
||||
app_name_exists = False
|
||||
try:
|
||||
|
@ -31,45 +29,13 @@ class Settings:
|
|||
CONFIG_FILE = f"{CONFIG_PATH}/settings.json"
|
||||
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
|
||||
|
||||
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:
|
||||
settings = json.load(f)
|
||||
config = settings["config"]
|
||||
|
||||
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
|
||||
go_past_home = True if config["go_past_home"] in ["true", ""] else False
|
||||
lock_folder = False if config["lock_folder"] in ["false", ""] else True
|
||||
|
@ -83,9 +49,6 @@ class Settings:
|
|||
code_app = config["code_app"]
|
||||
text_app = config["text_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"]
|
||||
remux_folder_max_disk_usage = config["remux_folder_max_disk_usage"]
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ def debug_signal_handler(signal, frame):
|
|||
rpdb2.start_embedded_debugger("foobar", True, True)
|
||||
rpdb2.setbreak(depth=1)
|
||||
return
|
||||
except StandardError:
|
||||
except Exception:
|
||||
...
|
||||
|
||||
try:
|
||||
|
@ -26,7 +26,7 @@ def debug_signal_handler(signal, frame):
|
|||
logger.debug("\n\nStarting embedded rconsole debugger...\n\n")
|
||||
rconsole.spawn_server()
|
||||
return
|
||||
except StandardError as ex:
|
||||
except Exception as ex:
|
||||
...
|
||||
|
||||
try:
|
||||
|
@ -34,7 +34,15 @@ def debug_signal_handler(signal, frame):
|
|||
logger.debug("\n\nStarting PuDB debugger...\n\n")
|
||||
set_trace(paused = True)
|
||||
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:
|
||||
|
@ -42,11 +50,11 @@ def debug_signal_handler(signal, frame):
|
|||
logger.debug("\n\nStarting embedded PDB debugger...\n\n")
|
||||
pdb.Pdb(skip=['gi.*']).set_trace()
|
||||
return
|
||||
except StandardError as ex:
|
||||
except Exception as ex:
|
||||
...
|
||||
|
||||
try:
|
||||
import code
|
||||
code.interact()
|
||||
except StandardError as ex:
|
||||
logger.debug(f"{ex}, returning to normal program flow...")
|
||||
except Exception as ex:
|
||||
logger.debug(f"{ex}, returning to normal program flow...")
|
|
@ -51,15 +51,20 @@ class IPCServer(Singleton):
|
|||
listener = Listener((self._ipc_address, self._ipc_port))
|
||||
|
||||
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:
|
||||
while True:
|
||||
try:
|
||||
conn = listener.accept()
|
||||
start_time = time.perf_counter()
|
||||
|
||||
GLib.idle_add(self._handle_ipc_message, *(conn, start_time,))
|
||||
|
||||
conn = None
|
||||
start_time = None
|
||||
except Exception as e:
|
||||
logger.debug( repr(e) )
|
||||
|
||||
|
@ -73,22 +78,29 @@ class IPCServer(Singleton):
|
|||
if "FILE|" in msg:
|
||||
file = msg.split("FILE|")[1].strip()
|
||||
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()
|
||||
break
|
||||
|
||||
|
||||
if msg in ['close connection', 'close server']:
|
||||
msg = None
|
||||
conn.close()
|
||||
break
|
||||
|
||||
# NOTE: Not perfect but insures we don't lock up the connection for too long.
|
||||
end_time = time.perf_counter()
|
||||
if (end_time - start_time) > self._ipc_timeout:
|
||||
msg = None
|
||||
end_time = None
|
||||
conn.close()
|
||||
break
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def send_ipc_message(self, message: str = "Empty Data...") -> None:
|
||||
try:
|
||||
|
|
|
@ -146,6 +146,13 @@ class SettingsManager(StartCheckMixin, Singleton):
|
|||
def is_trace_debug(self) -> bool: return self._trace_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):
|
||||
self._trace_debug = trace_debug
|
||||
|
||||
|
@ -164,4 +171,4 @@ class SettingsManager(StartCheckMixin, Singleton):
|
|||
|
||||
def save_settings(self):
|
||||
with open(self._CONFIG_FILE, 'w') as outfile:
|
||||
json.dump(self.settings.as_dict(), outfile, separators=(',', ':'), indent=4)
|
||||
json.dump(self.settings.as_dict(), outfile, separators=(',', ':'), indent=4)
|
||||
|
|
|
@ -30,7 +30,14 @@ class Config:
|
|||
sys_icon_wh: list = field(default_factory=lambda: [56, 56])
|
||||
steam_cdn_url: str = "https://steamcdn-a.akamaihd.net/steam/apps/"
|
||||
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: [
|
||||
"/usr/share/applications",
|
||||
f"{settings_manager.get_home_path()}/.local/share/applications"
|
||||
])
|
||||
])
|
|
@ -11,36 +11,48 @@ import inspect
|
|||
|
||||
|
||||
class StartCheckMixin:
|
||||
def is_dirty_start(self) -> bool: return self._dirty_start
|
||||
def clear_pid(self): self._clean_pid()
|
||||
def is_dirty_start(self) -> bool:
|
||||
return self._dirty_start
|
||||
|
||||
def clear_pid(self):
|
||||
if not self.is_trace_debug():
|
||||
self._clean_pid()
|
||||
|
||||
def do_dirty_start_check(self):
|
||||
if not os.path.exists(self._PID_FILE):
|
||||
self._write_new_pid()
|
||||
else:
|
||||
with open(self._PID_FILE, "r") as _pid:
|
||||
pid = _pid.readline().strip()
|
||||
if self.is_trace_debug():
|
||||
pid = os.getpid()
|
||||
self._print_pid(pid)
|
||||
return
|
||||
|
||||
if os.path.exists(self._PID_FILE):
|
||||
with open(self._PID_FILE, "r") as f:
|
||||
pid = f.readline().strip()
|
||||
if pid not in ("", None):
|
||||
self._check_alive_status(int(pid))
|
||||
else:
|
||||
self._write_new_pid()
|
||||
if self.is_pid_alive( int(pid) ):
|
||||
print("PID file exists and PID is alive... Letting downstream errors (sans debug args) handle app closure propigation.")
|
||||
return
|
||||
|
||||
self._write_new_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}")
|
||||
|
||||
try:
|
||||
os.kill(pid, 0)
|
||||
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._write_new_pid()
|
||||
return
|
||||
return False
|
||||
|
||||
print("PID is alive... Let downstream errors (sans debug args) handle app closure propigation.")
|
||||
return True
|
||||
|
||||
def _write_new_pid(self):
|
||||
pid = os.getpid()
|
||||
self._write_pid(pid)
|
||||
self._print_pid(pid)
|
||||
|
||||
def _print_pid(self, pid):
|
||||
print(f"{app_name} PID: {pid}")
|
||||
|
||||
def _clean_pid(self):
|
||||
|
@ -48,4 +60,4 @@ class StartCheckMixin:
|
|||
|
||||
def _write_pid(self, pid):
|
||||
with open(self._PID_FILE, "w") as _pid:
|
||||
_pid.write(f"{pid}")
|
||||
_pid.write(f"{pid}")
|
|
@ -20,6 +20,10 @@ function main() {
|
|||
files[$size]="${target}"
|
||||
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 "$@";
|
||||
|
|
|
@ -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 "$@";
|
|
@ -1,8 +1,9 @@
|
|||
{
|
||||
"Open Actions": {
|
||||
"Open": ["STOCK_OPEN", "open"],
|
||||
"Open With": ["STOCK_OPEN", "open_with"],
|
||||
"Execute": ["STOCK_EXECUTE", "execute"],
|
||||
"Open": ["STOCK_OPEN", "open"],
|
||||
"Open With": ["STOCK_OPEN", "open_with"],
|
||||
"Open 2 Tab": ["STOCK_OPEN", "open_2_new_tab"],
|
||||
"Execute": ["STOCK_EXECUTE", "execute"],
|
||||
"Execute in Terminal": ["STOCK_EXECUTE", "execute_in_terminal"]
|
||||
},
|
||||
"File Actions": {
|
||||
|
@ -16,4 +17,4 @@
|
|||
"Paste": ["STOCK_PASTE", "paste"]
|
||||
},
|
||||
"Plugins": {}
|
||||
}
|
||||
}
|
|
@ -2,8 +2,6 @@
|
|||
"config": {
|
||||
"base_of_home": "",
|
||||
"hide_hidden_files": "true",
|
||||
"thumbnailer_path": "ffmpegthumbnailer",
|
||||
"blender_thumbnailer_path": "",
|
||||
"go_past_home": "true",
|
||||
"lock_folder": "false",
|
||||
"locked_folders": "venv::::flasks",
|
||||
|
@ -16,12 +14,18 @@
|
|||
"code_app": "newton",
|
||||
"text_app": "mousepad",
|
||||
"terminal_app": "terminator",
|
||||
"container_icon_wh": [128, 128],
|
||||
"video_icon_wh": [128, 64],
|
||||
"sys_icon_wh": [56, 56],
|
||||
"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": {
|
||||
"meshs": [".dae", ".fbx", ".gltf", ".obj", ".stl"],
|
||||
|
@ -42,4 +46,4 @@
|
|||
"ch_log_lvl": 20,
|
||||
"fh_log_lvl": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
<property name="can-focus">False</property>
|
||||
<property name="border-width">5</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="skip-taskbar-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="comments" translatable="yes">by ITDominator</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
|
||||
|
||||
|
||||
|
@ -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>
|
||||
<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="wrap-license">True</property>
|
||||
<property name="license-type">custom</property>
|
||||
|
|
Loading…
Reference in New Issue