Compare commits
16 Commits
master
...
9d3a5b9f3b
Author | SHA1 | Date | |
---|---|---|---|
9d3a5b9f3b | |||
ce00970171 | |||
2f954f4c79 | |||
a362039e73 | |||
02c31719d1 | |||
d65ea8dec8 | |||
fec0d26ab7 | |||
a47bd23e78 | |||
44ef6ea2bb | |||
be7be00f78 | |||
8e5ae4824c | |||
37e3265be5 | |||
4cafb7ff9f | |||
9336df2afa | |||
d936b17429 | |||
e6739c3087 |
@@ -14,6 +14,7 @@ class Manifest:
|
|||||||
'ui_target': "plugin_control_list",
|
'ui_target': "plugin_control_list",
|
||||||
'pass_fm_events': "true"
|
'pass_fm_events': "true"
|
||||||
}
|
}
|
||||||
|
pre_launch: bool = False
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@@ -122,7 +122,6 @@ class Plugin(PluginBase):
|
|||||||
uri = state.uris[0]
|
uri = state.uris[0]
|
||||||
path = state.tab.get_current_directory()
|
path = state.tab.get_current_directory()
|
||||||
|
|
||||||
|
|
||||||
properties = self._set_ui_data(uri, path)
|
properties = self._set_ui_data(uri, path)
|
||||||
response = self._properties_dialog.run()
|
response = self._properties_dialog.run()
|
||||||
if response in [Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT]:
|
if response in [Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT]:
|
||||||
@@ -168,13 +167,13 @@ class Plugin(PluginBase):
|
|||||||
|
|
||||||
def _set_ui_data(self, uri, path):
|
def _set_ui_data(self, uri, path):
|
||||||
properties = Properties()
|
properties = Properties()
|
||||||
file_info = Gio.File.new_for_path(uri).query_info(attributes="standard::*,owner::*,time::access,time::changed",
|
file_info = Gio.File.new_for_path(uri).query_info(attributes = "standard::*,owner::*,time::access,time::changed",
|
||||||
flags=Gio.FileQueryInfoFlags.NONE,
|
flags = Gio.FileQueryInfoFlags.NONE,
|
||||||
cancellable=None)
|
cancellable = None)
|
||||||
|
|
||||||
is_symlink = file_info.get_attribute_as_string("standard::is-symlink")
|
is_symlink = file_info.get_attribute_as_string("standard::is-symlink")
|
||||||
properties.file_uri = uri
|
properties.file_uri = uri
|
||||||
properties.file_target = file_info.get_attribute_as_string("standard::symlink-target") if is_symlink else ""
|
properties.file_target = file_info.get_attribute_as_string("standard::symlink-target") if is_symlink in [True, "TRUE"] else ""
|
||||||
properties.file_name = file_info.get_display_name()
|
properties.file_name = file_info.get_display_name()
|
||||||
properties.file_location = path
|
properties.file_location = path
|
||||||
properties.mime_type = file_info.get_content_type()
|
properties.mime_type = file_info.get_content_type()
|
||||||
@@ -186,7 +185,7 @@ class Plugin(PluginBase):
|
|||||||
|
|
||||||
# NOTE: Read = 4, Write = 2, Exec = 1
|
# NOTE: Read = 4, Write = 2, Exec = 1
|
||||||
command = ["stat", "-c", "%a", uri]
|
command = ["stat", "-c", "%a", uri]
|
||||||
with subprocess.Popen(command, stdout=subprocess.PIPE) as proc:
|
with subprocess.Popen(command, stdout = subprocess.PIPE) as proc:
|
||||||
properties.chmod_stat = list(proc.stdout.read().decode("UTF-8").strip())
|
properties.chmod_stat = list(proc.stdout.read().decode("UTF-8").strip())
|
||||||
owner = self._chmod_map[f"{properties.chmod_stat[0]}"]
|
owner = self._chmod_map[f"{properties.chmod_stat[0]}"]
|
||||||
group = self._chmod_map[f"{properties.chmod_stat[1]}"]
|
group = self._chmod_map[f"{properties.chmod_stat[1]}"]
|
||||||
|
@@ -8,6 +8,7 @@
|
|||||||
"ui_target": "plugin_control_list",
|
"ui_target": "plugin_control_list",
|
||||||
"pass_fm_events": "true",
|
"pass_fm_events": "true",
|
||||||
"bind_keys": ["Example Plugin||send_message:<Control>f"]
|
"bind_keys": ["Example Plugin||send_message:<Control>f"]
|
||||||
}
|
},
|
||||||
|
"pre_launch": "false"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
3
plugins/thumbnailer/__init__.py
Normal file
3
plugins/thumbnailer/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
"""
|
||||||
|
Pligin Module
|
||||||
|
"""
|
3
plugins/thumbnailer/__main__.py
Normal file
3
plugins/thumbnailer/__main__.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
"""
|
||||||
|
Pligin Package
|
||||||
|
"""
|
73
plugins/thumbnailer/icons/controller.py
Normal file
73
plugins/thumbnailer/icons/controller.py
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
# Python imports
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from os import path
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
import gi
|
||||||
|
gi.require_version('Gtk', '3.0')
|
||||||
|
from gi.repository import Gtk
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from .icon import Icon
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class IconController(Icon):
|
||||||
|
def __init__(self):
|
||||||
|
CURRENT_PATH = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
|
||||||
|
# NOTE: app_name should be defined using python 'builtins' and so too must be logger used in the various classes
|
||||||
|
app_name_exists = False
|
||||||
|
try:
|
||||||
|
app_name
|
||||||
|
app_name_exists = True
|
||||||
|
except Exception as e:
|
||||||
|
...
|
||||||
|
|
||||||
|
APP_CONTEXT = f"{app_name.lower()}" if app_name_exists else "shellfm"
|
||||||
|
USR_APP_CONTEXT = f"/usr/share/{APP_CONTEXT}"
|
||||||
|
USER_HOME = path.expanduser('~')
|
||||||
|
CONFIG_PATH = f"{USER_HOME}/.config/{APP_CONTEXT}"
|
||||||
|
self.DEFAULT_ICONS = f"{CONFIG_PATH}/icons"
|
||||||
|
self.DEFAULT_ICON = f"{self.DEFAULT_ICONS}/text.png"
|
||||||
|
self.FFMPG_THUMBNLR = f"{CONFIG_PATH}/ffmpegthumbnailer" # Thumbnail generator binary
|
||||||
|
self.BLENDER_THUMBNLR = f"{CONFIG_PATH}/blender-thumbnailer" # Blender thumbnail generator binary
|
||||||
|
|
||||||
|
self.ICON_DIRS = ["/usr/share/icons", f"{USER_HOME}/.icons" "/usr/share/pixmaps"]
|
||||||
|
self.BASE_THUMBS_PTH = f"{USER_HOME}/.thumbnails"
|
||||||
|
self.ABS_THUMBS_PTH = f"{self.BASE_THUMBS_PTH}/normal"
|
||||||
|
self.STEAM_ICONS_PTH = f"{self.BASE_THUMBS_PTH}/steam_icons"
|
||||||
|
|
||||||
|
if not path.isdir(self.BASE_THUMBS_PTH):
|
||||||
|
os.mkdir(self.BASE_THUMBS_PTH)
|
||||||
|
|
||||||
|
if not path.isdir(self.ABS_THUMBS_PTH):
|
||||||
|
os.mkdir(self.ABS_THUMBS_PTH)
|
||||||
|
|
||||||
|
if not path.isdir(self.STEAM_ICONS_PTH):
|
||||||
|
os.mkdir(self.STEAM_ICONS_PTH)
|
||||||
|
|
||||||
|
if not os.path.exists(self.DEFAULT_ICONS):
|
||||||
|
self.DEFAULT_ICONS = f"{USR_APP_CONTEXT}/icons"
|
||||||
|
self.DEFAULT_ICON = f"{self.DEFAULT_ICONS}/text.png"
|
||||||
|
|
||||||
|
CONFIG_FILE = f"{CURRENT_PATH}/../settings.json"
|
||||||
|
with open(CONFIG_FILE) as f:
|
||||||
|
settings = json.load(f)
|
||||||
|
config = settings["config"]
|
||||||
|
|
||||||
|
self.container_icon_wh = config["container_icon_wh"]
|
||||||
|
self.video_icon_wh = config["video_icon_wh"]
|
||||||
|
self.sys_icon_wh = config["sys_icon_wh"]
|
||||||
|
|
||||||
|
# Filters
|
||||||
|
filters = settings["filters"]
|
||||||
|
self.fmeshs = tuple(filters["meshs"])
|
||||||
|
self.fcode = tuple(filters["code"])
|
||||||
|
self.fvideos = tuple(filters["videos"])
|
||||||
|
self.foffice = tuple(filters["office"])
|
||||||
|
self.fimages = tuple(filters["images"])
|
||||||
|
self.ftext = tuple(filters["text"])
|
||||||
|
self.fmusic = tuple(filters["music"])
|
||||||
|
self.fpdf = tuple(filters["pdf"])
|
@@ -138,6 +138,7 @@ class Icon(DesktopIconMixin, VideoIconMixin, MeshsIconMixin):
|
|||||||
def _call_gtk_thread(event, result):
|
def _call_gtk_thread(event, result):
|
||||||
result.append( self.get_system_thumbnail(full_path, size) )
|
result.append( self.get_system_thumbnail(full_path, size) )
|
||||||
event.set()
|
event.set()
|
||||||
|
return False
|
||||||
|
|
||||||
result = []
|
result = []
|
||||||
event = threading.Event()
|
event = threading.Event()
|
||||||
@@ -151,11 +152,11 @@ class Icon(DesktopIconMixin, VideoIconMixin, MeshsIconMixin):
|
|||||||
gio_file = Gio.File.new_for_path(full_path)
|
gio_file = Gio.File.new_for_path(full_path)
|
||||||
info = gio_file.query_info('standard::icon' , 0, None)
|
info = gio_file.query_info('standard::icon' , 0, None)
|
||||||
icon = info.get_icon().get_names()[0]
|
icon = info.get_icon().get_names()[0]
|
||||||
data = settings_manager.get_icon_theme().lookup_icon(icon , size , 0)
|
data = settings_manager.get_icon_theme().lookup_icon(icon , size, 0)
|
||||||
|
|
||||||
if data:
|
if data:
|
||||||
icon_path = data.get_filename()
|
icon_path = data.get_filename()
|
||||||
return GdkPixbuf.Pixbuf.new_from_file(icon_path)
|
return GdkPixbuf.Pixbuf.new_from_file_at_size(icon_path, width = size, height = size)
|
||||||
|
|
||||||
raise IconException("No system icon found...")
|
raise IconException("No system icon found...")
|
||||||
except IconException:
|
except IconException:
|
@@ -14,4 +14,4 @@ class MeshsIconMixin:
|
|||||||
proc = subprocess.Popen([self.BLENDER_THUMBNLR, full_path, hash_img_path])
|
proc = subprocess.Popen([self.BLENDER_THUMBNLR, full_path, hash_img_path])
|
||||||
proc.wait()
|
proc.wait()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.debug(repr(e))
|
logger.debug(repr(e))
|
@@ -14,7 +14,7 @@ class VideoIconMixin:
|
|||||||
proc = subprocess.Popen([self.FFMPG_THUMBNLR, "-t", scrub_percent, "-s", "300", "-c", "jpg", "-i", full_path, "-o", hash_img_path])
|
proc = subprocess.Popen([self.FFMPG_THUMBNLR, "-t", scrub_percent, "-s", "300", "-c", "jpg", "-i", full_path, "-o", hash_img_path])
|
||||||
proc.wait()
|
proc.wait()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.logger.debug(repr(e))
|
logger.info(repr(e))
|
||||||
self.ffprobe_generate_video_thumbnail(full_path, hash_img_path)
|
self.ffprobe_generate_video_thumbnail(full_path, hash_img_path)
|
||||||
|
|
||||||
|
|
||||||
@@ -51,5 +51,4 @@ class VideoIconMixin:
|
|||||||
proc.wait()
|
proc.wait()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print("Video thumbnail generation issue in thread:")
|
print("Video thumbnail generation issue in thread:")
|
||||||
print( repr(e) )
|
logger.info(repr(e))
|
||||||
self.logger.debug(repr(e))
|
|
12
plugins/thumbnailer/manifest.json
Normal file
12
plugins/thumbnailer/manifest.json
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"manifest": {
|
||||||
|
"name": "Thumbnailer",
|
||||||
|
"author": "ITDominator",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"support": "",
|
||||||
|
"requests": {
|
||||||
|
"pass_fm_events": "true"
|
||||||
|
},
|
||||||
|
"pre_launch": "true"
|
||||||
|
}
|
||||||
|
}
|
59
plugins/thumbnailer/plugin.py
Normal file
59
plugins/thumbnailer/plugin.py
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# Python imports
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
from plugins.plugin_base import PluginBase
|
||||||
|
from .icons.controller import IconController
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class Plugin(PluginBase):
|
||||||
|
def __init__(self):
|
||||||
|
super().__init__()
|
||||||
|
|
||||||
|
self.name = "Thumbnailer" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus
|
||||||
|
# where self.name should not be needed for message comms
|
||||||
|
# self.path = os.path.dirname(os.path.realpath(__file__))
|
||||||
|
|
||||||
|
|
||||||
|
def run(self):
|
||||||
|
self.icon_controller = IconController()
|
||||||
|
self._event_system.subscribe("create-thumbnail", self.create_thumbnail)
|
||||||
|
|
||||||
|
def generate_reference_ui_element(self):
|
||||||
|
...
|
||||||
|
|
||||||
|
def create_thumbnail(self, dir, file) -> str:
|
||||||
|
return self.icon_controller.create_icon(dir, file)
|
||||||
|
|
||||||
|
def get_video_icons(self, dir) -> list:
|
||||||
|
data = []
|
||||||
|
|
||||||
|
def get_video_icons(self) -> list:
|
||||||
|
data = []
|
||||||
|
fvideos = self.icon_controller.fvideos
|
||||||
|
vids = [ file for file in os.path.list_dir(dir) if file.lower().endswith(fvideos) ]
|
||||||
|
|
||||||
|
for file in vids:
|
||||||
|
img_hash, hash_img_path = self.create_video_thumbnail(full_path = f"{dir}/{file}", returnHashInstead = True)
|
||||||
|
data.append([img_hash, hash_img_path])
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def get_pixbuf_icon_str_combo(self, dir) -> list:
|
||||||
|
data = []
|
||||||
|
for file in os.path.list_dir(dir):
|
||||||
|
icon = self.icon_controller.create_icon(dir, file).get_pixbuf()
|
||||||
|
data.append([icon, file])
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
def get_gtk_icon_str_combo(self, dir) -> list:
|
||||||
|
data = []
|
||||||
|
for file in os.path.list_dir(dir):
|
||||||
|
icon = self.icon_controller.create_icon(dir, file)
|
||||||
|
data.append([icon, file[0]])
|
||||||
|
|
||||||
|
return data
|
101
plugins/thumbnailer/settings.json
Normal file
101
plugins/thumbnailer/settings.json
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
{
|
||||||
|
"config":{
|
||||||
|
"thumbnailer_path":"ffmpegthumbnailer",
|
||||||
|
"blender_thumbnailer_path":"",
|
||||||
|
"container_icon_wh":[
|
||||||
|
128,
|
||||||
|
128
|
||||||
|
],
|
||||||
|
"video_icon_wh":[
|
||||||
|
128,
|
||||||
|
64
|
||||||
|
],
|
||||||
|
"sys_icon_wh":[
|
||||||
|
56,
|
||||||
|
56
|
||||||
|
],
|
||||||
|
"steam_cdn_url":"https://steamcdn-a.akamaihd.net/steam/apps/",
|
||||||
|
"remux_folder_max_disk_usage":"8589934592"
|
||||||
|
},
|
||||||
|
"filters":{
|
||||||
|
"meshs":[
|
||||||
|
".dae",
|
||||||
|
".fbx",
|
||||||
|
".gltf",
|
||||||
|
".obj",
|
||||||
|
".stl"
|
||||||
|
],
|
||||||
|
"code":[
|
||||||
|
".cpp",
|
||||||
|
".css",
|
||||||
|
".c",
|
||||||
|
".go",
|
||||||
|
".html",
|
||||||
|
".htm",
|
||||||
|
".java",
|
||||||
|
".js",
|
||||||
|
".json",
|
||||||
|
".lua",
|
||||||
|
".md",
|
||||||
|
".py",
|
||||||
|
".rs",
|
||||||
|
".toml",
|
||||||
|
".xml",
|
||||||
|
".pom"
|
||||||
|
],
|
||||||
|
"videos":[
|
||||||
|
".mkv",
|
||||||
|
".mp4",
|
||||||
|
".webm",
|
||||||
|
".avi",
|
||||||
|
".mov",
|
||||||
|
".m4v",
|
||||||
|
".mpg",
|
||||||
|
".mpeg",
|
||||||
|
".wmv",
|
||||||
|
".flv"
|
||||||
|
],
|
||||||
|
"office":[
|
||||||
|
".doc",
|
||||||
|
".docx",
|
||||||
|
".xls",
|
||||||
|
".xlsx",
|
||||||
|
".xlt",
|
||||||
|
".xltx",
|
||||||
|
".xlm",
|
||||||
|
".ppt",
|
||||||
|
".pptx",
|
||||||
|
".pps",
|
||||||
|
".ppsx",
|
||||||
|
".odt",
|
||||||
|
".rtf"
|
||||||
|
],
|
||||||
|
"images":[
|
||||||
|
".png",
|
||||||
|
".jpg",
|
||||||
|
".jpeg",
|
||||||
|
".gif",
|
||||||
|
".ico",
|
||||||
|
".tga",
|
||||||
|
".webp"
|
||||||
|
],
|
||||||
|
"text":[
|
||||||
|
".txt",
|
||||||
|
".text",
|
||||||
|
".sh",
|
||||||
|
".cfg",
|
||||||
|
".conf",
|
||||||
|
".log"
|
||||||
|
],
|
||||||
|
"music":[
|
||||||
|
".psf",
|
||||||
|
".mp3",
|
||||||
|
".ogg",
|
||||||
|
".flac",
|
||||||
|
".m4a"
|
||||||
|
],
|
||||||
|
"pdf":[
|
||||||
|
".pdf"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@@ -184,8 +184,8 @@ class Plugin(PluginBase):
|
|||||||
response = requests.post(self.vqd_link, headers=self.vqd_headers, data=self.vqd_data, timeout=2)
|
response = requests.post(self.vqd_link, headers=self.vqd_headers, data=self.vqd_data, timeout=2)
|
||||||
if response.status_code == 200:
|
if response.status_code == 200:
|
||||||
data = response.content
|
data = response.content
|
||||||
vqd_start_index = data.index(b"vqd='") + 5
|
vqd_start_index = data.index(b"vqd=\"") + 5
|
||||||
vqd_end_index = data.index(b"'", vqd_start_index)
|
vqd_end_index = data.index(b"\"", vqd_start_index)
|
||||||
self._vqd_attrib = data[vqd_start_index:vqd_end_index].decode("utf-8")
|
self._vqd_attrib = data[vqd_start_index:vqd_end_index].decode("utf-8")
|
||||||
|
|
||||||
print(f"Translation VQD: {self._vqd_attrib}")
|
print(f"Translation VQD: {self._vqd_attrib}")
|
||||||
|
@@ -111,6 +111,8 @@ class Plugin(PluginBase):
|
|||||||
for uri in state.uris:
|
for uri in state.uris:
|
||||||
self.trashman.trash(uri, verbocity)
|
self.trashman.trash(uri, verbocity)
|
||||||
|
|
||||||
|
self.trashman.regenerate()
|
||||||
|
|
||||||
def restore_trash_files(self, widget = None, eve = None, verbocity = False):
|
def restore_trash_files(self, widget = None, eve = None, verbocity = False):
|
||||||
self._event_system.emit("get_current_state")
|
self._event_system.emit("get_current_state")
|
||||||
state = self._fm_state
|
state = self._fm_state
|
||||||
|
@@ -43,4 +43,4 @@ class Trash(object):
|
|||||||
|
|
||||||
def restore(self, filename, verbose):
|
def restore(self, filename, verbose):
|
||||||
"""Restore a file from trash."""
|
"""Restore a file from trash."""
|
||||||
raise NotImplementedError(_('Backend didn’t \ implement this functionality'))
|
raise NotImplementedError(_('Backend didn’t implement this functionality'))
|
||||||
|
@@ -127,7 +127,7 @@ DeletionDate={}
|
|||||||
f.write(infofile)
|
f.write(infofile)
|
||||||
f.close()
|
f.close()
|
||||||
|
|
||||||
self.regenerate()
|
# self.regenerate()
|
||||||
|
|
||||||
if verbose:
|
if verbose:
|
||||||
sys.stderr.write(_('trashed \'{}\'\n').format(filename))
|
sys.stderr.write(_('trashed \'{}\'\n').format(filename))
|
||||||
|
@@ -16,13 +16,17 @@ from utils.settings_manager.manager import SettingsManager
|
|||||||
# NOTE: Threads WILL NOT die with parent's destruction.
|
# NOTE: Threads WILL NOT die with parent's destruction.
|
||||||
def threaded_wrapper(fn):
|
def threaded_wrapper(fn):
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start()
|
thread = threading.Thread(target = fn, args = args, kwargs = kwargs, daemon = False)
|
||||||
|
thread.start()
|
||||||
|
return thread
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
# NOTE: Threads WILL die with parent's destruction.
|
# NOTE: Threads WILL die with parent's destruction.
|
||||||
def daemon_threaded_wrapper(fn):
|
def daemon_threaded_wrapper(fn):
|
||||||
def wrapper(*args, **kwargs):
|
def wrapper(*args, **kwargs):
|
||||||
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start()
|
thread = threading.Thread(target = fn, args = args, kwargs = kwargs, daemon = True)
|
||||||
|
thread.start()
|
||||||
|
return thread
|
||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
def sizeof_fmt_def(num, suffix="B"):
|
def sizeof_fmt_def(num, suffix="B"):
|
||||||
|
@@ -3,14 +3,13 @@
|
|||||||
# Python imports
|
# Python imports
|
||||||
import argparse
|
import argparse
|
||||||
import faulthandler
|
import faulthandler
|
||||||
import locale
|
|
||||||
import traceback
|
import traceback
|
||||||
from setproctitle import setproctitle
|
from setproctitle import setproctitle
|
||||||
|
|
||||||
|
import tracemalloc
|
||||||
|
tracemalloc.start()
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
import gi
|
|
||||||
gi.require_version('Gtk', '3.0')
|
|
||||||
from gi.repository import Gtk
|
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from __builtins__ import *
|
from __builtins__ import *
|
||||||
@@ -18,39 +17,38 @@ from app import Application
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
def run():
|
def main(args, unknownargs):
|
||||||
try:
|
setproctitle(f'{app_name}')
|
||||||
locale.setlocale(locale.LC_NUMERIC, 'C')
|
|
||||||
|
|
||||||
setproctitle(f"{app_name}")
|
if args.debug == "true":
|
||||||
faulthandler.enable() # For better debug info
|
settings_manager.set_debug(True)
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
if args.trace_debug == "true":
|
||||||
# Add long and short arguments
|
settings_manager.set_trace_debug(True)
|
||||||
parser.add_argument("--debug", "-d", default="false", help="Do extra console messaging.")
|
|
||||||
parser.add_argument("--trace-debug", "-td", default="false", help="Disable saves, ignore IPC lock, do extra console messaging.")
|
|
||||||
parser.add_argument("--no-plugins", "-np", default="false", help="Do not load plugins.")
|
|
||||||
|
|
||||||
parser.add_argument("--new-tab", "-t", default="", help="Open a file into new tab.")
|
settings_manager.do_dirty_start_check()
|
||||||
parser.add_argument("--new-window", "-w", default="", help="Open a file into a new window.")
|
Application(args, unknownargs)
|
||||||
|
|
||||||
# Read arguments (If any...)
|
|
||||||
args, unknownargs = parser.parse_known_args()
|
|
||||||
|
|
||||||
if args.debug == "true":
|
|
||||||
settings_manager.set_debug(True)
|
|
||||||
|
|
||||||
if args.trace_debug == "true":
|
|
||||||
settings_manager.set_trace_debug(True)
|
|
||||||
|
|
||||||
settings_manager.do_dirty_start_check()
|
|
||||||
Application(args, unknownargs)
|
|
||||||
Gtk.main()
|
|
||||||
except Exception as e:
|
|
||||||
traceback.print_exc()
|
|
||||||
quit()
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
""" Set process title, get arguments, and create GTK main thread. """
|
''' Set process title, get arguments, and create GTK main thread. '''
|
||||||
run()
|
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
# Add long and short arguments
|
||||||
|
parser.add_argument("--debug", "-d", default="false", help="Do extra console messaging.")
|
||||||
|
parser.add_argument("--trace-debug", "-td", default="false", help="Disable saves, ignore IPC lock, do extra console messaging.")
|
||||||
|
parser.add_argument("--no-plugins", "-np", default="false", help="Do not load plugins.")
|
||||||
|
|
||||||
|
parser.add_argument("--new-tab", "-nt", default="false", help="Opens a 'New Tab' if a handler is set for it.")
|
||||||
|
parser.add_argument("--file", "-f", default="default", help="JUST SOME FILE ARG.")
|
||||||
|
|
||||||
|
# Read arguments (If any...)
|
||||||
|
args, unknownargs = parser.parse_known_args()
|
||||||
|
|
||||||
|
try:
|
||||||
|
faulthandler.enable() # For better debug info
|
||||||
|
main(args, unknownargs)
|
||||||
|
except Exception as e:
|
||||||
|
traceback.print_exc()
|
||||||
|
quit()
|
||||||
|
@@ -15,35 +15,40 @@ class AppLaunchException(Exception):
|
|||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
class Application(IPCServer):
|
|
||||||
|
class Application:
|
||||||
""" docstring for Application. """
|
""" docstring for Application. """
|
||||||
|
|
||||||
def __init__(self, args, unknownargs):
|
def __init__(self, args, unknownargs):
|
||||||
super(Application, self).__init__()
|
super(Application, self).__init__()
|
||||||
|
|
||||||
if not settings_manager.is_trace_debug():
|
if not settings_manager.is_trace_debug():
|
||||||
self.socket_realization_check()
|
self.load_ipc(args, unknownargs)
|
||||||
|
|
||||||
if not self.is_ipc_alive:
|
|
||||||
for arg in unknownargs + [args.new_tab,]:
|
|
||||||
if os.path.isdir(arg):
|
|
||||||
message = f"FILE|{arg}"
|
|
||||||
self.send_ipc_message(message)
|
|
||||||
|
|
||||||
raise AppLaunchException(f"{app_name} IPC Server Exists: Will send path(s) to it and close...")
|
|
||||||
|
|
||||||
self.setup_debug_hook()
|
self.setup_debug_hook()
|
||||||
Window(args, unknownargs)
|
Window(args, unknownargs).main()
|
||||||
|
|
||||||
|
|
||||||
def socket_realization_check(self):
|
def load_ipc(self, args, unknownargs):
|
||||||
|
ipc_server = IPCServer()
|
||||||
|
self.ipc_realization_check(ipc_server)
|
||||||
|
|
||||||
|
if not ipc_server.is_ipc_alive:
|
||||||
|
for arg in unknownargs + [args.new_tab,]:
|
||||||
|
if os.path.isfile(arg):
|
||||||
|
message = f"FILE|{arg}"
|
||||||
|
ipc_server.send_ipc_message(message)
|
||||||
|
|
||||||
|
raise AppLaunchException(f"{app_name} IPC Server Exists: Have sent path(s) to it and closing...")
|
||||||
|
|
||||||
|
def ipc_realization_check(self, ipc_server):
|
||||||
try:
|
try:
|
||||||
self.create_ipc_listener()
|
ipc_server.create_ipc_listener()
|
||||||
except Exception:
|
except Exception:
|
||||||
self.send_test_ipc_message()
|
ipc_server.send_test_ipc_message()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.create_ipc_listener()
|
ipc_server.create_ipc_listener()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
...
|
...
|
||||||
|
|
||||||
@@ -51,7 +56,7 @@ class Application(IPCServer):
|
|||||||
try:
|
try:
|
||||||
# kill -SIGUSR2 <pid> from Linux/Unix or SIGBREAK signal from Windows
|
# kill -SIGUSR2 <pid> from Linux/Unix or SIGBREAK signal from Windows
|
||||||
signal.signal(
|
signal.signal(
|
||||||
vars(signal).get("SIGBREAK") or vars(signal).get("SIGUSR1"),
|
vars(signal).get("SIGBREAK") or vars(signal).get("SIGUSR2"),
|
||||||
debug_signal_handler
|
debug_signal_handler
|
||||||
)
|
)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
@@ -44,10 +44,13 @@ class Controller(UIMixin, SignalsMixins, Controller_Data):
|
|||||||
self._subscribe_to_events()
|
self._subscribe_to_events()
|
||||||
self._load_widgets()
|
self._load_widgets()
|
||||||
|
|
||||||
|
if args.no_plugins == "false":
|
||||||
|
self.plugins_controller.pre_launch_plugins()
|
||||||
|
|
||||||
self._generate_file_views(self.fm_controller_data)
|
self._generate_file_views(self.fm_controller_data)
|
||||||
|
|
||||||
if args.no_plugins == "false":
|
if args.no_plugins == "false":
|
||||||
self.plugins.launch_plugins()
|
self.plugins_controller.post_launch_plugins()
|
||||||
|
|
||||||
for arg in unknownargs + [args.new_tab,]:
|
for arg in unknownargs + [args.new_tab,]:
|
||||||
if os.path.isdir(arg):
|
if os.path.isdir(arg):
|
||||||
@@ -78,6 +81,7 @@ class Controller(UIMixin, SignalsMixins, Controller_Data):
|
|||||||
event_system.subscribe("do_action_from_menu_controls", self.do_action_from_menu_controls)
|
event_system.subscribe("do_action_from_menu_controls", self.do_action_from_menu_controls)
|
||||||
event_system.subscribe("set_clipboard_data", self.set_clipboard_data)
|
event_system.subscribe("set_clipboard_data", self.set_clipboard_data)
|
||||||
|
|
||||||
|
|
||||||
def _load_glade_file(self):
|
def _load_glade_file(self):
|
||||||
self.builder.add_from_file( settings_manager.get_glade_file() )
|
self.builder.add_from_file( settings_manager.get_glade_file() )
|
||||||
self.builder.expose_object("main_window", self.window)
|
self.builder.expose_object("main_window", self.window)
|
||||||
@@ -113,8 +117,9 @@ class Controller(UIMixin, SignalsMixins, Controller_Data):
|
|||||||
if not settings_manager.is_trace_debug():
|
if not settings_manager.is_trace_debug():
|
||||||
self.fm_controller.save_state()
|
self.fm_controller.save_state()
|
||||||
|
|
||||||
|
|
||||||
def reload_plugins(self, widget=None, eve=None):
|
def reload_plugins(self, widget=None, eve=None):
|
||||||
self.plugins.reload_plugins()
|
self.plugins_controller.reload_plugins()
|
||||||
|
|
||||||
|
|
||||||
def do_action_from_menu_controls(self, _action=None, eve=None):
|
def do_action_from_menu_controls(self, _action=None, eve=None):
|
||||||
@@ -130,44 +135,48 @@ class Controller(UIMixin, SignalsMixins, Controller_Data):
|
|||||||
event_system.emit("hide_rename_file_menu")
|
event_system.emit("hide_rename_file_menu")
|
||||||
|
|
||||||
if action == "open":
|
if action == "open":
|
||||||
event_system.emit("open_files")
|
event_system.emit_and_await("open_files")
|
||||||
if action == "open_with":
|
if action == "open_with":
|
||||||
event_system.emit("show_appchooser_menu")
|
event_system.emit_and_await("show_appchooser_menu")
|
||||||
|
if action == "open_2_new_tab":
|
||||||
|
event_system.emit_and_await("open_2_new_tab")
|
||||||
if action == "execute":
|
if action == "execute":
|
||||||
event_system.emit("execute_files")
|
event_system.emit_and_await("execute_files")
|
||||||
if action == "execute_in_terminal":
|
if action == "execute_in_terminal":
|
||||||
event_system.emit("execute_files", (True,))
|
event_system.emit_and_await("execute_files", (True,))
|
||||||
if action == "rename":
|
if action == "rename":
|
||||||
event_system.emit("rename_files")
|
event_system.emit_and_await("rename_files")
|
||||||
if action == "cut":
|
if action == "cut":
|
||||||
event_system.emit("cut_files")
|
event_system.emit_and_await("cut_files")
|
||||||
if action == "copy":
|
if action == "copy":
|
||||||
event_system.emit("copy_files")
|
event_system.emit_and_await("copy_files")
|
||||||
if action == "copy_path":
|
if action == "copy_path":
|
||||||
event_system.emit("copy_path")
|
event_system.emit_and_await("copy_path")
|
||||||
if action == "copy_name":
|
if action == "copy_name":
|
||||||
event_system.emit("copy_name")
|
event_system.emit_and_await("copy_name")
|
||||||
if action == "copy_path_name":
|
if action == "copy_path_name":
|
||||||
event_system.emit("copy_path_name")
|
event_system.emit_and_await("copy_path_name")
|
||||||
if action == "paste":
|
if action == "paste":
|
||||||
event_system.emit("paste_files")
|
event_system.emit_and_await("paste_files")
|
||||||
if action == "create":
|
if action == "create":
|
||||||
event_system.emit("create_files")
|
event_system.emit_and_await("create_files")
|
||||||
if action in ["save_session", "save_session_as", "load_session"]:
|
if action in ["save_session", "save_session_as", "load_session"]:
|
||||||
event_system.emit("save_load_session", (action))
|
event_system.emit_and_await("save_load_session", (action))
|
||||||
|
|
||||||
if action == "about_page":
|
if action == "about_page":
|
||||||
event_system.emit("show_about_page")
|
event_system.emit_and_await("show_about_page")
|
||||||
if action == "io_popup":
|
if action == "io_popup":
|
||||||
event_system.emit("show_io_popup")
|
event_system.emit_and_await("show_io_popup")
|
||||||
if action == "plugins_popup":
|
if action == "plugins_popup":
|
||||||
event_system.emit("show_plugins_popup")
|
event_system.emit_and_await("show_plugins_popup")
|
||||||
if action == "messages_popup":
|
if action == "messages_popup":
|
||||||
event_system.emit("show_messages_popup")
|
event_system.emit_and_await("show_messages_popup")
|
||||||
if action == "ui_debug":
|
if action == "ui_debug":
|
||||||
event_system.emit("load_interactive_debug")
|
event_system.emit_and_await("load_interactive_debug")
|
||||||
if action == "tear_down":
|
if action == "tear_down":
|
||||||
event_system.emit("tear_down")
|
event_system.emit_and_await("tear_down")
|
||||||
|
|
||||||
|
action = None
|
||||||
|
|
||||||
|
|
||||||
def go_home(self, widget=None, eve=None):
|
def go_home(self, widget=None, eve=None):
|
||||||
@@ -185,11 +194,14 @@ class Controller(UIMixin, SignalsMixins, Controller_Data):
|
|||||||
def tggl_top_main_menubar(self, widget=None, eve=None):
|
def tggl_top_main_menubar(self, widget=None, eve=None):
|
||||||
top_main_menubar = self.builder.get_object("top_main_menubar")
|
top_main_menubar = self.builder.get_object("top_main_menubar")
|
||||||
top_main_menubar.hide() if top_main_menubar.is_visible() else top_main_menubar.show()
|
top_main_menubar.hide() if top_main_menubar.is_visible() else top_main_menubar.show()
|
||||||
|
top_main_menubar = None
|
||||||
|
|
||||||
def open_terminal(self, widget=None, eve=None):
|
def open_terminal(self, widget=None, eve=None):
|
||||||
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
||||||
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
||||||
tab.execute([f"{tab.terminal_app}"], start_dir=tab.get_current_directory())
|
tab.execute([f"{tab.terminal_app}"], start_dir=tab.get_current_directory())
|
||||||
|
|
||||||
|
wid, tid, tab = None, None, None
|
||||||
|
|
||||||
def go_to_path(self, path: str):
|
def go_to_path(self, path: str):
|
||||||
self.builder.get_object("path_entry").set_text(path)
|
self.builder.get_object("path_entry").set_text(path)
|
@@ -29,7 +29,7 @@ class Controller_Data:
|
|||||||
|
|
||||||
self._load_glade_file()
|
self._load_glade_file()
|
||||||
self.fm_controller = WindowController()
|
self.fm_controller = WindowController()
|
||||||
self.plugins = PluginsController()
|
self.plugins_controller = PluginsController()
|
||||||
self.fm_controller_data = self.fm_controller.get_state_from_file()
|
self.fm_controller_data = self.fm_controller.get_state_from_file()
|
||||||
|
|
||||||
self.window1 = self.builder.get_object("window_1")
|
self.window1 = self.builder.get_object("window_1")
|
||||||
@@ -70,23 +70,16 @@ class Controller_Data:
|
|||||||
Returns:
|
Returns:
|
||||||
state (obj): State
|
state (obj): State
|
||||||
'''
|
'''
|
||||||
# state = State()
|
|
||||||
state = self._state
|
state = self._state
|
||||||
state.fm_controller = self.fm_controller
|
state.fm_controller = self.fm_controller
|
||||||
state.notebooks = self.notebooks
|
state.notebooks = self.notebooks
|
||||||
state.wid, state.tid = self.fm_controller.get_active_wid_and_tid()
|
state.wid, state.tid = self.fm_controller.get_active_wid_and_tid()
|
||||||
state.tab = self.get_fm_window(state.wid).get_tab_by_id(state.tid)
|
state.tab = self.get_fm_window(state.wid).get_tab_by_id(state.tid)
|
||||||
state.icon_grid = self.builder.get_object(f"{state.wid}|{state.tid}|icon_grid", use_gtk = False)
|
state.icon_grid = self.builder.get_object(f"{state.wid}|{state.tid}|icon_grid", use_gtk = False)
|
||||||
# state.icon_grid = event_system.emit_and_await("get_files_view_icon_grid", (state.wid, state.tid))
|
|
||||||
state.store = state.icon_grid.get_model()
|
state.store = state.icon_grid.get_model()
|
||||||
|
|
||||||
# NOTE: Need to watch this as I thought we had issues with just using single reference upon closing it.
|
|
||||||
# But, I found that not doing it this way caused objects to generate upon every click... (Because we're getting state info, duh)
|
|
||||||
# Yet interactive debug view shows them just pilling on and never clearing...
|
|
||||||
state.message_dialog = self.message_dialog
|
state.message_dialog = self.message_dialog
|
||||||
state.user_pass_dialog = self.user_pass_dialog
|
state.user_pass_dialog = self.user_pass_dialog
|
||||||
# state.message_dialog = MessageWidget()
|
|
||||||
# state.user_pass_dialog = UserPassWidget()
|
|
||||||
|
|
||||||
selected_files = state.icon_grid.get_selected_items()
|
selected_files = state.icon_grid.get_selected_items()
|
||||||
if selected_files:
|
if selected_files:
|
||||||
@@ -121,6 +114,9 @@ class Controller_Data:
|
|||||||
|
|
||||||
uris.append(fpath)
|
uris.append(fpath)
|
||||||
|
|
||||||
|
tab = None
|
||||||
|
dir = None
|
||||||
|
|
||||||
return uris
|
return uris
|
||||||
|
|
||||||
|
|
||||||
|
@@ -40,6 +40,7 @@ class FileSystemActions(HandlerMixin, CRUDMixin):
|
|||||||
|
|
||||||
event_system.subscribe("open_files", self.open_files)
|
event_system.subscribe("open_files", self.open_files)
|
||||||
event_system.subscribe("open_with_files", self.open_with_files)
|
event_system.subscribe("open_with_files", self.open_with_files)
|
||||||
|
event_system.subscribe("open_2_new_tab", self.open_2_new_tab)
|
||||||
event_system.subscribe("execute_files", self.execute_files)
|
event_system.subscribe("execute_files", self.execute_files)
|
||||||
|
|
||||||
event_system.subscribe("cut_files", self.cut_files)
|
event_system.subscribe("cut_files", self.cut_files)
|
||||||
@@ -104,6 +105,12 @@ class FileSystemActions(HandlerMixin, CRUDMixin):
|
|||||||
|
|
||||||
state.tab.app_chooser_exec(app_info, uris)
|
state.tab.app_chooser_exec(app_info, uris)
|
||||||
|
|
||||||
|
def open_2_new_tab(self):
|
||||||
|
state = event_system.emit_and_await("get_current_state")
|
||||||
|
uri = state.uris[0]
|
||||||
|
message = f"FILE|{uri}"
|
||||||
|
logger.info(message)
|
||||||
|
event_system.emit("post_file_to_ipc", message)
|
||||||
|
|
||||||
def execute_files(self, in_terminal=False):
|
def execute_files(self, in_terminal=False):
|
||||||
state = event_system.emit_and_await("get_current_state")
|
state = event_system.emit_and_await("get_current_state")
|
||||||
|
@@ -30,57 +30,53 @@ class FileActionSignalsMixin:
|
|||||||
|
|
||||||
wid = tab.get_wid()
|
wid = tab.get_wid()
|
||||||
tid = tab.get_id()
|
tid = tab.get_id()
|
||||||
dir_watcher.connect("changed", self.dir_watch_updates, (f"{wid}|{tid}",))
|
dir_watcher.connect("changed", self.dir_watch_updates, *(f"{wid}|{tid}",))
|
||||||
tab.set_dir_watcher(dir_watcher)
|
tab.set_dir_watcher(dir_watcher)
|
||||||
|
|
||||||
# NOTE: Too lazy to impliment a proper update handler and so just regen store and update tab.
|
def dir_watch_updates(self, file_monitor, file, other_file = None, eve_type = None, tab_widget_id = None):
|
||||||
# Use a lock system to prevent too many update calls for certain instances but user can manually refresh if they have urgency
|
|
||||||
def dir_watch_updates(self, file_monitor, file, other_file = None, eve_type = None, data = None):
|
|
||||||
if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED,
|
if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED,
|
||||||
Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN,
|
Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN,
|
||||||
Gio.FileMonitorEvent.MOVED_OUT]:
|
Gio.FileMonitorEvent.MOVED_OUT]:
|
||||||
logger.debug(eve_type)
|
|
||||||
|
|
||||||
if eve_type in [Gio.FileMonitorEvent.MOVED_IN, Gio.FileMonitorEvent.MOVED_OUT]:
|
self.soft_lock_countdown(tab_widget_id)
|
||||||
self.update_on_soft_lock_end(data[0])
|
|
||||||
elif data[0] in self.soft_update_lock.keys():
|
|
||||||
self.soft_update_lock[data[0]]["last_update_time"] = time.time()
|
|
||||||
else:
|
|
||||||
self.soft_lock_countdown(data[0])
|
|
||||||
|
|
||||||
@threaded
|
def soft_lock_countdown(self, tab_widget_id):
|
||||||
def soft_lock_countdown(self, tab_widget):
|
if tab_widget_id in self.soft_update_lock:
|
||||||
self.soft_update_lock[tab_widget] = { "last_update_time": time.time()}
|
timeout_id = self.soft_update_lock[tab_widget_id]["timeout_id"]
|
||||||
|
GLib.source_remove(timeout_id)
|
||||||
|
|
||||||
lock = True
|
timeout_id = GLib.timeout_add(0, self.update_on_soft_lock_end, 600, *(tab_widget_id,))
|
||||||
while lock:
|
self.soft_update_lock[tab_widget_id] = { "timeout_id": timeout_id }
|
||||||
time.sleep(0.6)
|
|
||||||
last_update_time = self.soft_update_lock[tab_widget]["last_update_time"]
|
|
||||||
current_time = time.time()
|
|
||||||
if (current_time - last_update_time) > 0.6:
|
|
||||||
lock = False
|
|
||||||
|
|
||||||
self.soft_update_lock.pop(tab_widget, None)
|
|
||||||
GLib.idle_add(self.update_on_soft_lock_end, *(tab_widget,))
|
|
||||||
|
|
||||||
|
|
||||||
def update_on_soft_lock_end(self, tab_widget):
|
def update_on_soft_lock_end(self, timout_ms, tab_widget_id):
|
||||||
wid, tid = tab_widget.split("|")
|
self.soft_update_lock.pop(tab_widget_id, None)
|
||||||
|
|
||||||
|
wid, tid = tab_widget_id.split("|")
|
||||||
notebook = self.builder.get_object(f"window_{wid}")
|
notebook = self.builder.get_object(f"window_{wid}")
|
||||||
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
||||||
icon_grid = self.builder.get_object(f"{wid}|{tid}|icon_grid", use_gtk = False)
|
icon_grid = self.builder.get_object(f"{wid}|{tid}|icon_grid", use_gtk = False)
|
||||||
store = icon_grid.get_model()
|
store = icon_grid.get_model()
|
||||||
_store, tab_widget_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}")
|
_store, tab_widget_id_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}")
|
||||||
|
|
||||||
tab.load_directory()
|
tab.load_directory()
|
||||||
icon_grid.clear_and_set_new_store()
|
icon_grid.clear_and_set_new_store()
|
||||||
self.load_store(tab, icon_grid.get_store())
|
self.load_store(tab, icon_grid.get_store())
|
||||||
|
|
||||||
tab_widget_label.set_label(tab.get_end_of_path())
|
tab_widget_id_label.set_label(tab.get_end_of_path())
|
||||||
state = self.get_current_state()
|
state = self.get_current_state()
|
||||||
if [wid, tid] in [state.wid, state.tid]:
|
if [wid, tid] in [state.wid, state.tid]:
|
||||||
self.set_bottom_labels(tab)
|
self.set_bottom_labels(tab)
|
||||||
|
|
||||||
|
wid, tid = None, None
|
||||||
|
notebook = None
|
||||||
|
tab = None
|
||||||
|
icon_grid = None
|
||||||
|
store = None
|
||||||
|
_store, tab_widget_id_label = None, None
|
||||||
|
state = None
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def do_file_search(self, widget, eve = None):
|
def do_file_search(self, widget, eve = None):
|
||||||
if not self.ctrl_down and not self.shift_down and not self.alt_down:
|
if not self.ctrl_down and not self.shift_down and not self.alt_down:
|
||||||
|
@@ -11,8 +11,8 @@ from gi.repository import Gdk
|
|||||||
# Application imports
|
# Application imports
|
||||||
|
|
||||||
|
|
||||||
valid_keyvalue_pat = re.compile(r"[a-z0-9A-Z-_\[\]\(\)\| ]")
|
|
||||||
|
|
||||||
|
valid_keyvalue_pat = re.compile(r"[a-z0-9A-Z-_\[\]\(\)\| ]")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -20,13 +20,25 @@ class KeyboardSignalsMixin:
|
|||||||
""" KeyboardSignalsMixin keyboard hooks controller. """
|
""" KeyboardSignalsMixin keyboard hooks controller. """
|
||||||
|
|
||||||
# TODO: Need to set methods that use this to somehow check the keybindings state instead.
|
# TODO: Need to set methods that use this to somehow check the keybindings state instead.
|
||||||
def unset_keys_and_data(self, widget=None, eve=None):
|
def unset_keys_and_data(self, widget = None, eve = None):
|
||||||
self.ctrl_down = False
|
self.ctrl_down = False
|
||||||
self.shift_down = False
|
self.shift_down = False
|
||||||
self.alt_down = False
|
self.alt_down = False
|
||||||
|
|
||||||
|
def unmap_special_keys(self, keyname):
|
||||||
|
if "control" in keyname:
|
||||||
|
self.ctrl_down = False
|
||||||
|
if "shift" in keyname:
|
||||||
|
self.shift_down = False
|
||||||
|
if "alt" in keyname:
|
||||||
|
self.alt_down = False
|
||||||
|
|
||||||
def on_global_key_press_controller(self, eve, user_data):
|
def on_global_key_press_controller(self, eve, user_data):
|
||||||
keyname = Gdk.keyval_name(user_data.keyval).lower()
|
keyname = Gdk.keyval_name(user_data.keyval).lower()
|
||||||
|
modifiers = Gdk.ModifierType(user_data.get_state() & ~Gdk.ModifierType.LOCK_MASK)
|
||||||
|
|
||||||
|
self.was_midified_key = True if modifiers != 0 else False
|
||||||
|
|
||||||
if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]:
|
if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]:
|
||||||
if "control" in keyname:
|
if "control" in keyname:
|
||||||
self.ctrl_down = True
|
self.ctrl_down = True
|
||||||
@@ -36,52 +48,49 @@ class KeyboardSignalsMixin:
|
|||||||
self.alt_down = True
|
self.alt_down = True
|
||||||
|
|
||||||
def on_global_key_release_controller(self, widget, event):
|
def on_global_key_release_controller(self, widget, event):
|
||||||
"""Handler for keyboard events"""
|
""" Handler for keyboard events """
|
||||||
keyname = Gdk.keyval_name(event.keyval).lower()
|
keyname = Gdk.keyval_name(event.keyval).lower()
|
||||||
|
modifiers = Gdk.ModifierType(event.get_state() & ~Gdk.ModifierType.LOCK_MASK)
|
||||||
|
|
||||||
if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]:
|
if keyname.replace("_l", "").replace("_r", "") in ["control", "alt", "shift"]:
|
||||||
if "control" in keyname:
|
should_return = self.was_midified_key and (self.ctrl_down or self.shift_down or self.alt_down)
|
||||||
self.ctrl_down = False
|
self.unmap_special_keys(keyname)
|
||||||
if "shift" in keyname:
|
|
||||||
self.shift_down = False
|
if should_return:
|
||||||
if "alt" in keyname:
|
self.was_midified_key = False
|
||||||
self.alt_down = False
|
return
|
||||||
|
|
||||||
mapping = keybindings.lookup(event)
|
mapping = keybindings.lookup(event)
|
||||||
|
logger.debug(f"on_global_key_release_controller > key > {keyname}")
|
||||||
|
logger.debug(f"on_global_key_release_controller > keyval > {event.keyval}")
|
||||||
|
logger.debug(f"on_global_key_release_controller > mapping > {mapping}")
|
||||||
|
|
||||||
if mapping:
|
if mapping:
|
||||||
# See if in filemanager scope
|
self.handle_mapped_key_event(mapping)
|
||||||
try:
|
|
||||||
getattr(self, mapping)()
|
|
||||||
return True
|
|
||||||
except Exception:
|
|
||||||
# Must be plugins scope, event call, OR we forgot to add method to file manager scope
|
|
||||||
if "||" in mapping:
|
|
||||||
sender, eve_type = mapping.split("||")
|
|
||||||
else:
|
|
||||||
sender = ""
|
|
||||||
eve_type = mapping
|
|
||||||
|
|
||||||
self.handle_plugin_key_event(sender, eve_type)
|
|
||||||
else:
|
else:
|
||||||
logger.debug(f"on_global_key_release_controller > key > {keyname}")
|
self.handle_as_key_event_scope(keyname)
|
||||||
|
|
||||||
if self.ctrl_down:
|
def handle_mapped_key_event(self, mapping):
|
||||||
if keyname in ["1", "kp_1", "2", "kp_2", "3", "kp_3", "4", "kp_4"]:
|
try:
|
||||||
self.builder.get_object(f"tggl_notebook_{keyname.strip('kp_')}").released()
|
self.handle_as_controller_scope(mapping)
|
||||||
|
except Exception:
|
||||||
|
self.handle_as_plugin_scope(mapping)
|
||||||
|
|
||||||
def handle_plugin_key_event(self, sender, eve_type):
|
def handle_as_controller_scope(self, mapping):
|
||||||
|
getattr(self, mapping)()
|
||||||
|
|
||||||
|
def handle_as_plugin_scope(self, mapping):
|
||||||
|
if "||" in mapping:
|
||||||
|
sender, eve_type = mapping.split("||")
|
||||||
|
else:
|
||||||
|
sender = ""
|
||||||
|
eve_type = mapping
|
||||||
|
|
||||||
|
self.handle_key_event_system(sender, eve_type)
|
||||||
|
|
||||||
|
def handle_as_key_event_scope(self, keyname):
|
||||||
|
if self.ctrl_down and not keyname in ["1", "kp_1", "2", "kp_2", "3", "kp_3", "4", "kp_4"]:
|
||||||
|
self.handle_key_event_system(None, keyname)
|
||||||
|
|
||||||
|
def handle_key_event_system(self, sender, eve_type):
|
||||||
event_system.emit(eve_type)
|
event_system.emit(eve_type)
|
||||||
|
|
||||||
def keyboard_close_tab(self):
|
|
||||||
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
|
||||||
notebook = self.builder.get_object(f"window_{wid}")
|
|
||||||
scroll = self.builder.get_object(f"{wid}|{tid}", use_gtk = False)
|
|
||||||
page = notebook.page_num(scroll)
|
|
||||||
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
|
||||||
watcher = tab.get_dir_watcher()
|
|
||||||
watcher.cancel()
|
|
||||||
|
|
||||||
self.get_fm_window(wid).delete_tab_by_id(tid)
|
|
||||||
notebook.remove_page(page)
|
|
||||||
if not trace_debug:
|
|
||||||
self.fm_controller.save_state()
|
|
||||||
self.set_window_title()
|
|
||||||
|
@@ -7,6 +7,7 @@ import gi
|
|||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
from gi.repository import GLib
|
from gi.repository import GLib
|
||||||
|
from gi.repository import Gio
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from ...widgets.tab_header_widget import TabHeaderWidget
|
from ...widgets.tab_header_widget import TabHeaderWidget
|
||||||
@@ -19,6 +20,17 @@ class GridMixin:
|
|||||||
"""docstring for GridMixin"""
|
"""docstring for GridMixin"""
|
||||||
|
|
||||||
def load_store(self, tab, store, save_state = False, use_generator = False):
|
def load_store(self, tab, store, save_state = False, use_generator = False):
|
||||||
|
# dir = tab.get_current_directory()
|
||||||
|
# file = Gio.File.new_for_path(dir)
|
||||||
|
# dir_list = Gtk.DirectoryList.new("standard::*", file)
|
||||||
|
# store.set(dir_list)
|
||||||
|
|
||||||
|
# file = Gio.File.new_for_path(dir)
|
||||||
|
# for file in file.enumerate_children("standard::*", Gio.FILE_ATTRIBUTE_STANDARD_NAME, None):
|
||||||
|
# store.append(file)
|
||||||
|
|
||||||
|
# return
|
||||||
|
|
||||||
dir = tab.get_current_directory()
|
dir = tab.get_current_directory()
|
||||||
files = tab.get_files()
|
files = tab.get_files()
|
||||||
|
|
||||||
@@ -26,61 +38,76 @@ class GridMixin:
|
|||||||
store.append([None, file[0]])
|
store.append([None, file[0]])
|
||||||
|
|
||||||
Gtk.main_iteration()
|
Gtk.main_iteration()
|
||||||
# for i, file in enumerate(files):
|
self.generate_icons(tab, store, dir, files)
|
||||||
# self.create_icon(i, tab, store, dir, file[0])
|
# GLib.Thread("", self.generate_icons, tab, store, dir, files)
|
||||||
|
|
||||||
if use_generator:
|
|
||||||
# NOTE: tab > icon > _get_system_thumbnail_gtk_thread must not be used
|
|
||||||
# as the attempted promotion back to gtk threading stalls the generator. (We're already in main gtk thread)
|
|
||||||
for i, icon in enumerate( self.create_icons_generator(tab, dir, files) ):
|
|
||||||
self.load_icon(i, store, icon)
|
|
||||||
else:
|
|
||||||
try:
|
|
||||||
loop = asyncio.get_running_loop()
|
|
||||||
except RuntimeError:
|
|
||||||
loop = None
|
|
||||||
|
|
||||||
if loop and loop.is_running():
|
|
||||||
loop.create_task( self.create_icons(tab, store, dir, files) )
|
|
||||||
else:
|
|
||||||
asyncio.run( self.create_icons(tab, store, dir, files) )
|
|
||||||
|
|
||||||
# NOTE: Not likely called often from here but it could be useful
|
# NOTE: Not likely called often from here but it could be useful
|
||||||
if save_state and not trace_debug:
|
if save_state and not trace_debug:
|
||||||
self.fm_controller.save_state()
|
self.fm_controller.save_state()
|
||||||
|
|
||||||
async def create_icons(self, tab, store, dir, files):
|
dir = None
|
||||||
tasks = [self.update_store(i, store, dir, tab, file[0]) for i, file in enumerate(files)]
|
files = None
|
||||||
await asyncio.gather(*tasks)
|
|
||||||
|
|
||||||
async def load_icon(self, i, store, icon):
|
@daemon_threaded
|
||||||
self.update_store(i, store, icon)
|
def generate_icons(self, tab, store, dir, files):
|
||||||
|
for i, file in enumerate(files):
|
||||||
|
# GLib.Thread(f"{i}", self.make_and_load_icon, i, store, tab, dir, file[0])
|
||||||
|
self.make_and_load_icon( i, store, tab, dir, file[0])
|
||||||
|
|
||||||
async def update_store(self, i, store, dir, tab, file):
|
def update_store(self, i, store, icon):
|
||||||
icon = tab.create_icon(dir, file)
|
|
||||||
itr = store.get_iter(i)
|
itr = store.get_iter(i)
|
||||||
|
GLib.idle_add(self.insert_store, store, itr, icon)
|
||||||
|
itr = None
|
||||||
|
|
||||||
|
@daemon_threaded
|
||||||
|
def make_and_load_icon(self, i, store, tab, dir, file):
|
||||||
|
icon = tab.create_icon(dir, file)
|
||||||
|
self.update_store(i, store, icon)
|
||||||
|
icon = None
|
||||||
|
|
||||||
|
def get_icon(self, tab, dir, file):
|
||||||
|
tab.create_icon(dir, file)
|
||||||
|
|
||||||
|
|
||||||
|
# @daemon_threaded
|
||||||
|
# def generate_icons(self, tab, store, dir, files):
|
||||||
|
# try:
|
||||||
|
# loop = asyncio.get_running_loop()
|
||||||
|
# except RuntimeError:
|
||||||
|
# loop = None
|
||||||
|
|
||||||
|
# if loop and loop.is_running():
|
||||||
|
# loop = asyncio.get_event_loop()
|
||||||
|
# loop.create_task( self.create_icons(tab, store, dir, files) )
|
||||||
|
# else:
|
||||||
|
# asyncio.run( self.create_icons(tab, store, dir, files) )
|
||||||
|
|
||||||
|
# async def create_icons(self, tab, store, dir, files):
|
||||||
|
# icons = [self.get_icon(tab, dir, file[0]) for file in files]
|
||||||
|
# data = await asyncio.gather(*icons)
|
||||||
|
# tasks = [self.update_store(i, store, icon) for i, icon in enumerate(data)]
|
||||||
|
# asyncio.gather(*tasks)
|
||||||
|
|
||||||
|
# async def update_store(self, i, store, icon):
|
||||||
|
# itr = store.get_iter(i)
|
||||||
|
# GLib.idle_add(self.insert_store, store, itr, icon)
|
||||||
|
|
||||||
|
# async def get_icon(self, tab, dir, file):
|
||||||
|
# return tab.create_icon(dir, file)
|
||||||
|
|
||||||
|
|
||||||
|
def insert_store(self, store, itr, icon):
|
||||||
store.set_value(itr, 0, icon)
|
store.set_value(itr, 0, icon)
|
||||||
|
|
||||||
def create_icons_generator(self, tab, dir, files):
|
# Note: If the function returns GLib.SOURCE_REMOVE or False it is automatically removed from the list of event sources and will not be called again.
|
||||||
for file in files:
|
return False
|
||||||
icon = tab.create_icon(dir, file[0])
|
|
||||||
yield icon
|
|
||||||
|
|
||||||
# @daemon_threaded
|
def do_ui_update(self):
|
||||||
# def create_icon(self, i, tab, store, dir, file):
|
Gtk.main_iteration()
|
||||||
# icon = tab.create_icon(dir, file)
|
return False
|
||||||
# GLib.idle_add(self.update_store, *(i, store, icon,))
|
|
||||||
#
|
|
||||||
# @daemon_threaded
|
|
||||||
# def load_icon(self, i, store, icon):
|
|
||||||
# GLib.idle_add(self.update_store, *(i, store, icon,))
|
|
||||||
|
|
||||||
# def update_store(self, i, store, icon):
|
def create_tab_widget(self):
|
||||||
# itr = store.get_iter(i)
|
return TabHeaderWidget(self.close_tab)
|
||||||
# store.set_value(itr, 0, icon)
|
|
||||||
|
|
||||||
def create_tab_widget(self, tab):
|
|
||||||
return TabHeaderWidget(tab, self.close_tab)
|
|
||||||
|
|
||||||
def create_scroll_and_store(self, tab, wid, use_tree_view = False):
|
def create_scroll_and_store(self, tab, wid, use_tree_view = False):
|
||||||
scroll = Gtk.ScrolledWindow()
|
scroll = Gtk.ScrolledWindow()
|
||||||
@@ -137,6 +164,7 @@ class GridMixin:
|
|||||||
store = icon_grid.get_model()
|
store = icon_grid.get_model()
|
||||||
tab_label = notebook.get_tab_label(obj).get_children()[0]
|
tab_label = notebook.get_tab_label(obj).get_children()[0]
|
||||||
|
|
||||||
|
icon_grid = None
|
||||||
return store, tab_label
|
return store, tab_label
|
||||||
|
|
||||||
def get_icon_grid_from_notebook(self, notebook, _name):
|
def get_icon_grid_from_notebook(self, notebook, _name):
|
||||||
|
@@ -34,27 +34,46 @@ class TabMixin(GridMixin):
|
|||||||
else:
|
else:
|
||||||
tab.set_path(path)
|
tab.set_path(path)
|
||||||
|
|
||||||
tab_widget = self.create_tab_widget(tab)
|
tab_widget = self.get_tab_widget(tab)
|
||||||
scroll, store = self.create_scroll_and_store(tab, wid)
|
scroll, store = self.create_scroll_and_store(tab, wid)
|
||||||
index = notebook.append_page(scroll, tab_widget)
|
index = notebook.append_page(scroll, tab_widget)
|
||||||
notebook.set_tab_detachable(scroll, True)
|
notebook.set_tab_detachable(scroll, True)
|
||||||
|
notebook.set_tab_reorderable(scroll, True)
|
||||||
|
|
||||||
self.fm_controller.set_wid_and_tid(wid, tab.get_id())
|
self.fm_controller.set_wid_and_tid(wid, tab.get_id())
|
||||||
path_entry.set_text(tab.get_current_directory())
|
# path_entry.set_text(tab.get_current_directory())
|
||||||
|
event_system.emit("go_to_path", (tab.get_current_directory(),)) # NOTE: Not efficent if I understand how
|
||||||
notebook.show_all()
|
notebook.show_all()
|
||||||
notebook.set_current_page(index)
|
notebook.set_current_page(index)
|
||||||
|
|
||||||
ctx = notebook.get_style_context()
|
ctx = notebook.get_style_context()
|
||||||
ctx.add_class("notebook-unselected-focus")
|
ctx.add_class("notebook-unselected-focus")
|
||||||
notebook.set_tab_reorderable(scroll, True)
|
|
||||||
self.load_store(tab, store)
|
self.load_store(tab, store)
|
||||||
self.set_window_title()
|
event_system.emit("set_window_title", (tab.get_current_directory(),))
|
||||||
self.set_file_watcher(tab)
|
self.set_file_watcher(tab)
|
||||||
|
|
||||||
|
tab_widget = None
|
||||||
|
scroll, store = None, None
|
||||||
|
index = None
|
||||||
|
notebook = None
|
||||||
|
path_entry = None
|
||||||
|
tab = None
|
||||||
|
ctx = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_tab_widget(self, tab):
|
||||||
|
tab_widget = self.create_tab_widget()
|
||||||
|
tab_widget.tab = tab
|
||||||
|
|
||||||
|
tab_widget.label.set_label(f"{tab.get_end_of_path()}")
|
||||||
|
tab_widget.label.set_width_chars(len(tab.get_end_of_path()))
|
||||||
|
|
||||||
|
return tab_widget
|
||||||
|
|
||||||
def close_tab(self, button, eve = None):
|
def close_tab(self, button, eve = None):
|
||||||
notebook = button.get_parent().get_parent()
|
notebook = button.get_parent().get_parent()
|
||||||
if notebook.get_n_pages() == 1:
|
if notebook.get_n_pages() == 1:
|
||||||
|
notebook = None
|
||||||
return
|
return
|
||||||
|
|
||||||
tab_box = button.get_parent()
|
tab_box = button.get_parent()
|
||||||
@@ -72,23 +91,35 @@ class TabMixin(GridMixin):
|
|||||||
self.builder.dereference_object(f"{wid}|{tid}|icon_grid")
|
self.builder.dereference_object(f"{wid}|{tid}|icon_grid")
|
||||||
self.builder.dereference_object(f"{wid}|{tid}")
|
self.builder.dereference_object(f"{wid}|{tid}")
|
||||||
|
|
||||||
|
iter = store.get_iter_first()
|
||||||
|
while iter:
|
||||||
|
next_iter = store.iter_next(iter)
|
||||||
|
store.unref_node(iter)
|
||||||
|
iter = next_iter
|
||||||
|
|
||||||
store.clear()
|
store.clear()
|
||||||
icon_grid.destroy()
|
store.run_dispose()
|
||||||
scroll.destroy()
|
|
||||||
tab_box.destroy()
|
|
||||||
|
|
||||||
del store
|
icon_grid.set_model(None)
|
||||||
del icon_grid
|
icon_grid.run_dispose()
|
||||||
del scroll
|
scroll.run_dispose()
|
||||||
del tab_box
|
tab_box.run_dispose()
|
||||||
del watcher
|
|
||||||
del tab
|
iter = None
|
||||||
|
wid, tid = None, None
|
||||||
|
store = None
|
||||||
|
icon_grid = None
|
||||||
|
scroll = None
|
||||||
|
tab_box = None
|
||||||
|
watcher = None
|
||||||
|
tab = None
|
||||||
|
notebook = None
|
||||||
|
|
||||||
gc.collect()
|
|
||||||
if not settings_manager.is_trace_debug():
|
if not settings_manager.is_trace_debug():
|
||||||
self.fm_controller.save_state()
|
self.fm_controller.save_state()
|
||||||
|
|
||||||
self.set_window_title()
|
self.set_window_title()
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
# NOTE: Not actually getting called even tho set in the glade file...
|
# NOTE: Not actually getting called even tho set in the glade file...
|
||||||
def on_tab_dnded(self, notebook, page, x, y):
|
def on_tab_dnded(self, notebook, page, x, y):
|
||||||
@@ -111,15 +142,23 @@ class TabMixin(GridMixin):
|
|||||||
if not settings_manager.is_trace_debug():
|
if not settings_manager.is_trace_debug():
|
||||||
self.fm_controller.save_state()
|
self.fm_controller.save_state()
|
||||||
|
|
||||||
|
wid, tid = None, None
|
||||||
|
window = None
|
||||||
|
tab = None
|
||||||
|
|
||||||
|
|
||||||
def on_tab_switch_update(self, notebook, content = None, index = None):
|
def on_tab_switch_update(self, notebook, content = None, index = None):
|
||||||
self.selected_files.clear()
|
self.selected_files.clear()
|
||||||
wid, tid = content.get_children()[0].get_name().split("|")
|
wid, tid = content.get_children()[0].get_name().split("|")
|
||||||
|
|
||||||
self.fm_controller.set_wid_and_tid(wid, tid)
|
self.fm_controller.set_wid_and_tid(wid, tid)
|
||||||
self.set_path_text(wid, tid)
|
self.set_path_text(wid, tid)
|
||||||
self.set_window_title()
|
self.set_window_title()
|
||||||
|
|
||||||
|
wid, tid = None, None
|
||||||
|
|
||||||
def get_id_from_tab_box(self, tab_box):
|
def get_id_from_tab_box(self, tab_box):
|
||||||
return tab_box.get_children()[2].get_text()
|
return tab_box.tab.get_id()
|
||||||
|
|
||||||
def get_tab_label(self, notebook, icon_grid):
|
def get_tab_label(self, notebook, icon_grid):
|
||||||
return notebook.get_tab_label(icon_grid.get_parent()).get_children()[0]
|
return notebook.get_tab_label(icon_grid.get_parent()).get_children()[0]
|
||||||
@@ -135,6 +174,8 @@ class TabMixin(GridMixin):
|
|||||||
state.tab.load_directory()
|
state.tab.load_directory()
|
||||||
self.load_store(state.tab, state.store)
|
self.load_store(state.tab, state.store)
|
||||||
|
|
||||||
|
state = None
|
||||||
|
|
||||||
def update_tab(self, tab_label, tab, store, wid, tid):
|
def update_tab(self, tab_label, tab, store, wid, tid):
|
||||||
self.load_store(tab, store)
|
self.load_store(tab, store)
|
||||||
self.set_path_text(wid, tid)
|
self.set_path_text(wid, tid)
|
||||||
@@ -175,16 +216,38 @@ class TabMixin(GridMixin):
|
|||||||
if isinstance(focused_obj, Gtk.Entry):
|
if isinstance(focused_obj, Gtk.Entry):
|
||||||
self.process_path_menu(widget, tab, dir)
|
self.process_path_menu(widget, tab, dir)
|
||||||
|
|
||||||
|
action = None
|
||||||
|
store = None
|
||||||
|
|
||||||
if path.endswith(".") or path == dir:
|
if path.endswith(".") or path == dir:
|
||||||
|
tab_label = None
|
||||||
|
notebook = None
|
||||||
|
wid, tid = None, None
|
||||||
|
path = None
|
||||||
|
tab = None
|
||||||
return
|
return
|
||||||
|
|
||||||
if not tab.set_path(path):
|
if not tab.set_path(path):
|
||||||
|
tab_label = None
|
||||||
|
notebook = None
|
||||||
|
wid, tid = None, None
|
||||||
|
path = None
|
||||||
|
tab = None
|
||||||
return
|
return
|
||||||
|
|
||||||
icon_grid = self.get_icon_grid_from_notebook(notebook, f"{wid}|{tid}")
|
icon_grid = self.get_icon_grid_from_notebook(notebook, f"{wid}|{tid}")
|
||||||
icon_grid.clear_and_set_new_store()
|
icon_grid.clear_and_set_new_store()
|
||||||
self.update_tab(tab_label, tab, icon_grid.get_store(), wid, tid)
|
self.update_tab(tab_label, tab, icon_grid.get_store(), wid, tid)
|
||||||
|
|
||||||
|
action = None
|
||||||
|
wid, tid = None, None
|
||||||
|
notebook = None
|
||||||
|
store, tab_label = None, None
|
||||||
|
path = None
|
||||||
|
tab = None
|
||||||
|
icon_grid = None
|
||||||
|
|
||||||
|
|
||||||
def process_path_menu(self, gtk_entry, tab, dir):
|
def process_path_menu(self, gtk_entry, tab, dir):
|
||||||
path_menu_buttons = self.builder.get_object("path_menu_buttons")
|
path_menu_buttons = self.builder.get_object("path_menu_buttons")
|
||||||
query = gtk_entry.get_text().replace(dir, "")
|
query = gtk_entry.get_text().replace(dir, "")
|
||||||
@@ -201,6 +264,10 @@ class TabMixin(GridMixin):
|
|||||||
path_menu_buttons.add(button)
|
path_menu_buttons.add(button)
|
||||||
show_path_menu = True
|
show_path_menu = True
|
||||||
|
|
||||||
|
path_menu_buttons = None
|
||||||
|
query = None
|
||||||
|
files = None
|
||||||
|
|
||||||
if not show_path_menu:
|
if not show_path_menu:
|
||||||
event_system.emit("hide_path_menu")
|
event_system.emit("hide_path_menu")
|
||||||
else:
|
else:
|
||||||
@@ -218,6 +285,7 @@ class TabMixin(GridMixin):
|
|||||||
def do_focused_click(self, button):
|
def do_focused_click(self, button):
|
||||||
button.grab_focus()
|
button.grab_focus()
|
||||||
button.clicked()
|
button.clicked()
|
||||||
|
return False
|
||||||
|
|
||||||
def set_path_entry(self, button = None, eve = None):
|
def set_path_entry(self, button = None, eve = None):
|
||||||
self.path_auto_filled = True
|
self.path_auto_filled = True
|
||||||
@@ -230,6 +298,10 @@ class TabMixin(GridMixin):
|
|||||||
path_entry.set_position(-1)
|
path_entry.set_position(-1)
|
||||||
event_system.emit("hide_path_menu")
|
event_system.emit("hide_path_menu")
|
||||||
|
|
||||||
|
state = None
|
||||||
|
path = None
|
||||||
|
path_entry = None
|
||||||
|
|
||||||
|
|
||||||
def show_hide_hidden_files(self):
|
def show_hide_hidden_files(self):
|
||||||
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
||||||
@@ -237,3 +309,6 @@ class TabMixin(GridMixin):
|
|||||||
tab.set_hiding_hidden(not tab.is_hiding_hidden())
|
tab.set_hiding_hidden(not tab.is_hiding_hidden())
|
||||||
tab.load_directory()
|
tab.load_directory()
|
||||||
self.builder.get_object("refresh_tab").released()
|
self.builder.get_object("refresh_tab").released()
|
||||||
|
|
||||||
|
wid, tid = None, None
|
||||||
|
tab = None
|
||||||
|
@@ -46,11 +46,19 @@ class WindowMixin(TabMixin):
|
|||||||
self.window.set_title(f"{app_name} ~ {dir}")
|
self.window.set_title(f"{app_name} ~ {dir}")
|
||||||
self.set_bottom_labels(tab)
|
self.set_bottom_labels(tab)
|
||||||
|
|
||||||
|
wid, tid = None, None
|
||||||
|
notebook = None
|
||||||
|
tab = None
|
||||||
|
dir = None
|
||||||
|
|
||||||
def set_path_text(self, wid, tid):
|
def set_path_text(self, wid, tid):
|
||||||
path_entry = self.builder.get_object("path_entry")
|
path_entry = self.builder.get_object("path_entry")
|
||||||
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
||||||
path_entry.set_text(tab.get_current_directory())
|
path_entry.set_text(tab.get_current_directory())
|
||||||
|
|
||||||
|
path_entry = None
|
||||||
|
tab = None
|
||||||
|
|
||||||
def grid_set_selected_items(self, icons_grid):
|
def grid_set_selected_items(self, icons_grid):
|
||||||
new_items = icons_grid.get_selected_items()
|
new_items = icons_grid.get_selected_items()
|
||||||
items_size = len(new_items)
|
items_size = len(new_items)
|
||||||
@@ -164,6 +172,12 @@ class WindowMixin(TabMixin):
|
|||||||
if target not in current:
|
if target not in current:
|
||||||
self.fm_controller.set_wid_and_tid(wid, tid)
|
self.fm_controller.set_wid_and_tid(wid, tid)
|
||||||
|
|
||||||
|
current = None
|
||||||
|
target = None
|
||||||
|
wid, tid = None, None
|
||||||
|
store = None
|
||||||
|
path_at_loc = None
|
||||||
|
|
||||||
|
|
||||||
def grid_on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
|
def grid_on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
|
||||||
if info == 80:
|
if info == 80:
|
||||||
@@ -177,6 +191,10 @@ class WindowMixin(TabMixin):
|
|||||||
if from_uri != dest:
|
if from_uri != dest:
|
||||||
event_system.emit("move_files", (uris, dest))
|
event_system.emit("move_files", (uris, dest))
|
||||||
|
|
||||||
|
Gtk.drag_finish(drag_context, True, False, time)
|
||||||
|
return
|
||||||
|
|
||||||
|
Gtk.drag_finish(drag_context, False, False, time)
|
||||||
|
|
||||||
def create_new_tab_notebook(self, widget=None, wid=None, path=None):
|
def create_new_tab_notebook(self, widget=None, wid=None, path=None):
|
||||||
self.create_tab(wid, None, path)
|
self.create_tab(wid, None, path)
|
||||||
|
@@ -6,6 +6,7 @@ gi.require_version('Gtk', '3.0')
|
|||||||
gi.require_version('Gdk', '3.0')
|
gi.require_version('Gdk', '3.0')
|
||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
from gi.repository import Gdk
|
from gi.repository import Gdk
|
||||||
|
from gi.repository import GLib
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from .mixins.ui.pane_mixin import PaneMixin
|
from .mixins.ui.pane_mixin import PaneMixin
|
||||||
@@ -34,18 +35,20 @@ class UIMixin(PaneMixin, WindowMixin):
|
|||||||
nickname = session["window"]["Nickname"]
|
nickname = session["window"]["Nickname"]
|
||||||
tabs = session["window"]["tabs"]
|
tabs = session["window"]["tabs"]
|
||||||
isHidden = True if session["window"]["isHidden"] == "True" else False
|
isHidden = True if session["window"]["isHidden"] == "True" else False
|
||||||
event_system.emit("load_files_view_state", (nickname, tabs))
|
event_system.emit_and_await("load_files_view_state", (nickname, tabs, isHidden))
|
||||||
|
|
||||||
|
|
||||||
@daemon_threaded
|
|
||||||
def _focus_last_visible_notebook(self, icon_grid):
|
def _focus_last_visible_notebook(self, icon_grid):
|
||||||
import time
|
import time
|
||||||
|
|
||||||
window = settings_manager.get_main_window()
|
window = settings_manager.get_main_window()
|
||||||
while not window.is_visible() and not window.get_realized():
|
while not window.is_visible() and not window.get_realized():
|
||||||
time.sleep(0.1)
|
time.sleep(0.2)
|
||||||
|
|
||||||
icon_grid.event(Gdk.Event().new(type = Gdk.EventType.BUTTON_RELEASE))
|
icon_grid.event(Gdk.Event().new(type = Gdk.EventType.BUTTON_RELEASE))
|
||||||
|
|
||||||
|
window = None
|
||||||
|
|
||||||
def _current_loading_process(self, session_json = None):
|
def _current_loading_process(self, session_json = None):
|
||||||
if session_json:
|
if session_json:
|
||||||
for j, value in enumerate(session_json):
|
for j, value in enumerate(session_json):
|
||||||
@@ -77,7 +80,7 @@ class UIMixin(PaneMixin, WindowMixin):
|
|||||||
|
|
||||||
scroll_win = notebook.get_children()[-1]
|
scroll_win = notebook.get_children()[-1]
|
||||||
icon_grid = scroll_win.get_children()[0]
|
icon_grid = scroll_win.get_children()[0]
|
||||||
self._focus_last_visible_notebook(icon_grid)
|
GLib.Thread("", self._focus_last_visible_notebook, icon_grid)
|
||||||
except UIMixinException as e:
|
except UIMixinException as e:
|
||||||
logger.info("\n: The saved session might be missing window data! :\nLocation: ~/.config/solarfm/session.json\nFix: Back it up and delete it to reset.\n")
|
logger.info("\n: The saved session might be missing window data! :\nLocation: ~/.config/solarfm/session.json\nFix: Back it up and delete it to reset.\n")
|
||||||
logger.debug(repr(e))
|
logger.debug(repr(e))
|
||||||
|
@@ -16,10 +16,7 @@ class ContextMenuWidget(Gtk.Menu):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(ContextMenuWidget, self).__init__()
|
super(ContextMenuWidget, self).__init__()
|
||||||
|
|
||||||
self.builder = settings_manager.get_builder()
|
self._builder = Gtk.Builder()
|
||||||
self._builder = Gtk.Builder()
|
|
||||||
self._context_menu_data = settings_manager.get_context_menu_data()
|
|
||||||
self._window = settings_manager.get_main_window()
|
|
||||||
|
|
||||||
self._setup_styling()
|
self._setup_styling()
|
||||||
self._setup_signals()
|
self._setup_signals()
|
||||||
@@ -32,24 +29,57 @@ class ContextMenuWidget(Gtk.Menu):
|
|||||||
def _setup_signals(self):
|
def _setup_signals(self):
|
||||||
event_system.subscribe("show_context_menu", self.show_context_menu)
|
event_system.subscribe("show_context_menu", self.show_context_menu)
|
||||||
event_system.subscribe("hide_context_menu", self.hide_context_menu)
|
event_system.subscribe("hide_context_menu", self.hide_context_menu)
|
||||||
settings_manager.register_signals_to_builder([self,], self._builder)
|
settings_manager.register_signals_to_builder(self, self._builder)
|
||||||
|
|
||||||
def _load_widgets(self):
|
def _load_widgets(self):
|
||||||
|
self.builder = settings_manager.get_builder()
|
||||||
|
self._window = settings_manager.get_main_window()
|
||||||
|
self._context_menu_data = settings_manager.get_context_menu_data()
|
||||||
|
|
||||||
|
self.builder.expose_object("context_menu", self)
|
||||||
|
|
||||||
self.build_context_menu()
|
self.build_context_menu()
|
||||||
|
|
||||||
def _emit(self, menu_item, type):
|
def _emit(self, menu_item, type):
|
||||||
event_system.emit("do_action_from_menu_controls", type)
|
event_system.emit("do_action_from_menu_controls", type)
|
||||||
|
|
||||||
|
|
||||||
def make_submenu(self, name, data, keys):
|
def build_context_menu(self) -> None:
|
||||||
|
data = self._context_menu_data
|
||||||
|
plugins_entry = None
|
||||||
|
|
||||||
|
for key, value in data.items():
|
||||||
|
entry = self.make_menu_item(key, value)
|
||||||
|
self.append(entry)
|
||||||
|
if key == "Plugins":
|
||||||
|
plugins_entry = entry
|
||||||
|
|
||||||
|
self.attach_to_widget(self._window, None)
|
||||||
|
self.show_all()
|
||||||
|
|
||||||
|
if plugins_entry:
|
||||||
|
self.builder.expose_object("context_menu_plugins", plugins_entry.get_submenu())
|
||||||
|
|
||||||
|
def make_menu_item(self, label, data) -> Gtk.MenuItem:
|
||||||
|
if isinstance(data, dict):
|
||||||
|
return self.make_submenu(label, data)
|
||||||
|
elif isinstance(data, list):
|
||||||
|
entry = Gtk.ImageMenuItem(label)
|
||||||
|
icon = getattr(Gtk, f"{data[0]}")
|
||||||
|
entry.set_image( Gtk.Image(stock=icon) )
|
||||||
|
entry.set_always_show_image(True)
|
||||||
|
entry.connect("activate", self._emit, (data[1]))
|
||||||
|
return entry
|
||||||
|
|
||||||
|
def make_submenu(self, name, data):
|
||||||
menu = Gtk.Menu()
|
menu = Gtk.Menu()
|
||||||
menu_item = Gtk.MenuItem(name)
|
menu_item = Gtk.MenuItem(name)
|
||||||
|
|
||||||
for key in keys:
|
for key, value in data.items():
|
||||||
if isinstance(data, dict):
|
if isinstance(data, dict):
|
||||||
entry = self.make_menu_item(key, data[key])
|
entry = self.make_menu_item(key, value)
|
||||||
elif isinstance(data, list):
|
elif isinstance(data, list):
|
||||||
entry = self.make_menu_item(key, data)
|
entry = self.make_menu_item(key, value)
|
||||||
else:
|
else:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -58,36 +88,9 @@ class ContextMenuWidget(Gtk.Menu):
|
|||||||
menu_item.set_submenu(menu)
|
menu_item.set_submenu(menu)
|
||||||
return menu_item
|
return menu_item
|
||||||
|
|
||||||
def make_menu_item(self, name, data) -> Gtk.MenuItem:
|
|
||||||
if isinstance(data, dict):
|
|
||||||
return self.make_submenu(name, data, data.keys())
|
|
||||||
elif isinstance(data, list):
|
|
||||||
entry = Gtk.ImageMenuItem(name)
|
|
||||||
icon = getattr(Gtk, f"{data[0]}")
|
|
||||||
entry.set_image( Gtk.Image(stock=icon) )
|
|
||||||
entry.set_always_show_image(True)
|
|
||||||
entry.connect("activate", self._emit, (data[1]))
|
|
||||||
return entry
|
|
||||||
|
|
||||||
def build_context_menu(self) -> None:
|
def show_context_menu(self, widget = None, eve = None):
|
||||||
data = self._context_menu_data
|
|
||||||
dkeys = data.keys()
|
|
||||||
plugins_entry = None
|
|
||||||
|
|
||||||
for dkey in dkeys:
|
|
||||||
entry = self.make_menu_item(dkey, data[dkey])
|
|
||||||
self.append(entry)
|
|
||||||
if dkey == "Plugins":
|
|
||||||
plugins_entry = entry
|
|
||||||
|
|
||||||
self.attach_to_widget(self._window, None)
|
|
||||||
self.show_all()
|
|
||||||
self.builder.expose_object("context_menu", self)
|
|
||||||
if plugins_entry:
|
|
||||||
self.builder.expose_object("context_menu_plugins", plugins_entry.get_submenu())
|
|
||||||
|
|
||||||
def show_context_menu(self, widget=None, eve=None):
|
|
||||||
self.builder.get_object("context_menu").popup_at_pointer(None)
|
self.builder.get_object("context_menu").popup_at_pointer(None)
|
||||||
|
|
||||||
def hide_context_menu(self, widget=None, eve=None):
|
def hide_context_menu(self, widget = None, eve = None):
|
||||||
self.builder.get_object("context_menu").popdown()
|
self.builder.get_object("context_menu").popdown()
|
||||||
|
@@ -39,11 +39,10 @@ class AboutWidget:
|
|||||||
self.about_page = self._builder.get_object("about_page")
|
self.about_page = self._builder.get_object("about_page")
|
||||||
builder.expose_object(f"about_page", self.about_page)
|
builder.expose_object(f"about_page", self.about_page)
|
||||||
|
|
||||||
|
def show_about_page(self, widget = None, eve = None):
|
||||||
def show_about_page(self, widget=None, eve=None):
|
|
||||||
response = self.about_page.run()
|
response = self.about_page.run()
|
||||||
if response in [Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT]:
|
if response in [Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT]:
|
||||||
self.hide_about_page()
|
self.hide_about_page()
|
||||||
|
|
||||||
def hide_about_page(self, widget=None, eve=None):
|
def hide_about_page(self, widget = None, eve = None):
|
||||||
self.about_page.hide()
|
self.about_page.hide()
|
||||||
|
@@ -50,6 +50,9 @@ class RenameWidget:
|
|||||||
def show_rename_file_menu(self, widget=None, eve=None):
|
def show_rename_file_menu(self, widget=None, eve=None):
|
||||||
if widget:
|
if widget:
|
||||||
widget.grab_focus()
|
widget.grab_focus()
|
||||||
|
end_i = widget.get_text().rfind(".")
|
||||||
|
if end_i > 0:
|
||||||
|
widget.select_region(0, end_i)
|
||||||
|
|
||||||
response = self._rename_file_menu.run()
|
response = self._rename_file_menu.run()
|
||||||
if response == Gtk.ResponseType.CLOSE:
|
if response == Gtk.ResponseType.CLOSE:
|
||||||
|
@@ -1,11 +1,9 @@
|
|||||||
# Python imports
|
# Python imports
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
import gi
|
|
||||||
gi.require_version('Gtk', '3.0')
|
|
||||||
from gi.repository import Gtk
|
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
|
from ...sfm_builder import SFMBuilder
|
||||||
from ...mixins.signals.file_action_signals_mixin import FileActionSignalsMixin
|
from ...mixins.signals.file_action_signals_mixin import FileActionSignalsMixin
|
||||||
from .window_mixin import WindowMixin
|
from .window_mixin import WindowMixin
|
||||||
|
|
||||||
@@ -27,7 +25,8 @@ class FilesWidget(FileActionSignalsMixin, WindowMixin):
|
|||||||
|
|
||||||
self.INDEX = self.ccount
|
self.INDEX = self.ccount
|
||||||
self.NAME = f"window_{self.INDEX}"
|
self.NAME = f"window_{self.INDEX}"
|
||||||
self.builder = Gtk.Builder()
|
self.builder = SFMBuilder()
|
||||||
|
|
||||||
self.files_view = None
|
self.files_view = None
|
||||||
self.fm_controller = None
|
self.fm_controller = None
|
||||||
|
|
||||||
@@ -41,20 +40,21 @@ class FilesWidget(FileActionSignalsMixin, WindowMixin):
|
|||||||
...
|
...
|
||||||
|
|
||||||
def _setup_signals(self):
|
def _setup_signals(self):
|
||||||
settings_manager.register_signals_to_builder([self,], self.builder)
|
settings_manager.register_signals_to_builder([self], self.builder)
|
||||||
|
|
||||||
def _subscribe_to_events(self):
|
def _subscribe_to_events(self):
|
||||||
event_system.subscribe("load_files_view_state", self._load_files_view_state)
|
event_system.subscribe("load_files_view_state", self._load_files_view_state)
|
||||||
event_system.subscribe("get_files_view_icon_grid", self._get_files_view_icon_grid)
|
event_system.subscribe("get_files_view_icon_grid", self._get_files_view_icon_grid)
|
||||||
|
|
||||||
def _load_widgets(self):
|
def _load_widgets(self):
|
||||||
_builder = settings_manager.get_builder()
|
_builder = settings_manager.get_builder()
|
||||||
self.files_view = _builder.get_object(f"{self.NAME}")
|
self.files_view = _builder.get_object(f"{self.NAME}")
|
||||||
|
|
||||||
self.files_view.set_group_name("files_widget")
|
self.files_view.set_group_name("files_widget")
|
||||||
|
|
||||||
self.builder.expose_object(f"{self.NAME}", self.files_view)
|
self.builder.expose_object(f"{self.NAME}", self.files_view)
|
||||||
|
|
||||||
def _load_files_view_state(self, win_name = None, tabs = None):
|
def _load_files_view_state(self, win_name = None, tabs = None, isHidden = False):
|
||||||
if win_name == self.NAME:
|
if win_name == self.NAME:
|
||||||
if tabs:
|
if tabs:
|
||||||
for tab in tabs:
|
for tab in tabs:
|
||||||
@@ -62,6 +62,9 @@ class FilesWidget(FileActionSignalsMixin, WindowMixin):
|
|||||||
else:
|
else:
|
||||||
self.create_new_tab_notebook(None, self.INDEX, None)
|
self.create_new_tab_notebook(None, self.INDEX, None)
|
||||||
|
|
||||||
|
if isHidden:
|
||||||
|
self.files_view.hide()
|
||||||
|
|
||||||
def _get_files_view_icon_grid(self, win_index = None, tid = None):
|
def _get_files_view_icon_grid(self, win_index = None, tid = None):
|
||||||
if win_index == str(self.INDEX):
|
if win_index == str(self.INDEX):
|
||||||
return self.builder.get_object(f"{self.INDEX}|{tid}|icon_grid", use_gtk = False)
|
return self.builder.get_object(f"{self.INDEX}|{tid}|icon_grid", use_gtk = False)
|
||||||
|
@@ -7,6 +7,7 @@ import gi
|
|||||||
gi.require_version("Gtk", "3.0")
|
gi.require_version("Gtk", "3.0")
|
||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
from gi.repository import GLib
|
from gi.repository import GLib
|
||||||
|
from gi.repository import Gio
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from ...widgets.tab_header_widget import TabHeaderWidget
|
from ...widgets.tab_header_widget import TabHeaderWidget
|
||||||
@@ -19,6 +20,17 @@ class GridMixin:
|
|||||||
"""docstring for GridMixin"""
|
"""docstring for GridMixin"""
|
||||||
|
|
||||||
def load_store(self, tab, store, save_state = False, use_generator = False):
|
def load_store(self, tab, store, save_state = False, use_generator = False):
|
||||||
|
# dir = tab.get_current_directory()
|
||||||
|
# file = Gio.File.new_for_path(dir)
|
||||||
|
# dir_list = Gtk.DirectoryList.new("standard::*", file)
|
||||||
|
# store.set(dir_list)
|
||||||
|
|
||||||
|
# file = Gio.File.new_for_path(dir)
|
||||||
|
# for file in file.enumerate_children("standard::*", Gio.FILE_ATTRIBUTE_STANDARD_NAME, None):
|
||||||
|
# store.append(file)
|
||||||
|
|
||||||
|
# return
|
||||||
|
|
||||||
dir = tab.get_current_directory()
|
dir = tab.get_current_directory()
|
||||||
files = tab.get_files()
|
files = tab.get_files()
|
||||||
|
|
||||||
@@ -26,60 +38,76 @@ class GridMixin:
|
|||||||
store.append([None, file[0]])
|
store.append([None, file[0]])
|
||||||
|
|
||||||
Gtk.main_iteration()
|
Gtk.main_iteration()
|
||||||
if use_generator:
|
self.generate_icons(tab, store, dir, files)
|
||||||
# NOTE: tab > icon > _get_system_thumbnail_gtk_thread must not be used
|
# GLib.Thread("", self.generate_icons, tab, store, dir, files)
|
||||||
# as the attempted promotion back to gtk threading stalls the generator. (We're already in main gtk thread)
|
|
||||||
for i, icon in enumerate( self.create_icons_generator(tab, dir, files) ):
|
|
||||||
self.load_icon(i, store, icon)
|
|
||||||
else:
|
|
||||||
# for i, file in enumerate(files):
|
|
||||||
# self.create_icon(i, tab, store, dir, file[0])
|
|
||||||
try:
|
|
||||||
loop = asyncio.get_running_loop()
|
|
||||||
except RuntimeError:
|
|
||||||
loop = None
|
|
||||||
|
|
||||||
if loop and loop.is_running():
|
|
||||||
loop.create_task( self.create_icons(tab, store, dir, files) )
|
|
||||||
else:
|
|
||||||
asyncio.run( self.create_icons(tab, store, dir, files) )
|
|
||||||
|
|
||||||
# NOTE: Not likely called often from here but it could be useful
|
# NOTE: Not likely called often from here but it could be useful
|
||||||
if save_state and not trace_debug:
|
if save_state and not trace_debug:
|
||||||
self.fm_controller.save_state()
|
self.fm_controller.save_state()
|
||||||
|
|
||||||
async def create_icons(self, tab, store, dir, files):
|
dir = None
|
||||||
tasks = [self.update_store(i, store, dir, tab, file[0]) for i, file in enumerate(files)]
|
files = None
|
||||||
await asyncio.gather(*tasks)
|
|
||||||
|
|
||||||
async def load_icon(self, i, store, icon):
|
@daemon_threaded
|
||||||
self.update_store(i, store, icon)
|
def generate_icons(self, tab, store, dir, files):
|
||||||
|
for i, file in enumerate(files):
|
||||||
|
# GLib.Thread(f"{i}", self.make_and_load_icon, i, store, tab, dir, file[0])
|
||||||
|
self.make_and_load_icon( i, store, tab, dir, file[0])
|
||||||
|
|
||||||
async def update_store(self, i, store, dir, tab, file):
|
def update_store(self, i, store, icon):
|
||||||
icon = tab.create_icon(dir, file)
|
|
||||||
itr = store.get_iter(i)
|
itr = store.get_iter(i)
|
||||||
|
GLib.idle_add(self.insert_store, store, itr, icon)
|
||||||
|
itr = None
|
||||||
|
|
||||||
|
@daemon_threaded
|
||||||
|
def make_and_load_icon(self, i, store, tab, dir, file):
|
||||||
|
icon = tab.create_icon(dir, file)
|
||||||
|
self.update_store(i, store, icon)
|
||||||
|
icon = None
|
||||||
|
|
||||||
|
def get_icon(self, tab, dir, file):
|
||||||
|
tab.create_icon(dir, file)
|
||||||
|
|
||||||
|
|
||||||
|
# @daemon_threaded
|
||||||
|
# def generate_icons(self, tab, store, dir, files):
|
||||||
|
# try:
|
||||||
|
# loop = asyncio.get_running_loop()
|
||||||
|
# except RuntimeError:
|
||||||
|
# loop = None
|
||||||
|
|
||||||
|
# if loop and loop.is_running():
|
||||||
|
# loop = asyncio.get_event_loop()
|
||||||
|
# loop.create_task( self.create_icons(tab, store, dir, files) )
|
||||||
|
# else:
|
||||||
|
# asyncio.run( self.create_icons(tab, store, dir, files) )
|
||||||
|
|
||||||
|
# async def create_icons(self, tab, store, dir, files):
|
||||||
|
# icons = [self.get_icon(tab, dir, file[0]) for file in files]
|
||||||
|
# data = await asyncio.gather(*icons)
|
||||||
|
# tasks = [self.update_store(i, store, icon) for i, icon in enumerate(data)]
|
||||||
|
# asyncio.gather(*tasks)
|
||||||
|
|
||||||
|
# async def update_store(self, i, store, icon):
|
||||||
|
# itr = store.get_iter(i)
|
||||||
|
# GLib.idle_add(self.insert_store, store, itr, icon)
|
||||||
|
|
||||||
|
# async def get_icon(self, tab, dir, file):
|
||||||
|
# return tab.create_icon(dir, file)
|
||||||
|
|
||||||
|
|
||||||
|
def insert_store(self, store, itr, icon):
|
||||||
store.set_value(itr, 0, icon)
|
store.set_value(itr, 0, icon)
|
||||||
|
|
||||||
def create_icons_generator(self, tab, dir, files):
|
# Note: If the function returns GLib.SOURCE_REMOVE or False it is automatically removed from the list of event sources and will not be called again.
|
||||||
for file in files:
|
return False
|
||||||
icon = tab.create_icon(dir, file[0])
|
|
||||||
yield icon
|
|
||||||
|
|
||||||
# @daemon_threaded
|
def do_ui_update(self):
|
||||||
# def create_icon(self, i, tab, store, dir, file):
|
Gtk.main_iteration()
|
||||||
# icon = tab.create_icon(dir, file)
|
return False
|
||||||
# GLib.idle_add(self.update_store, *(i, store, icon,))
|
|
||||||
#
|
|
||||||
# @daemon_threaded
|
|
||||||
# def load_icon(self, i, store, icon):
|
|
||||||
# GLib.idle_add(self.update_store, *(i, store, icon,))
|
|
||||||
#
|
|
||||||
# def update_store(self, i, store, icon):
|
|
||||||
# itr = store.get_iter(i)
|
|
||||||
# store.set_value(itr, 0, icon)
|
|
||||||
|
|
||||||
def create_tab_widget(self, tab):
|
def create_tab_widget(self):
|
||||||
return TabHeaderWidget(tab, self.close_tab)
|
return TabHeaderWidget(self.close_tab)
|
||||||
|
|
||||||
def create_scroll_and_store(self, tab, wid, use_tree_view = False):
|
def create_scroll_and_store(self, tab, wid, use_tree_view = False):
|
||||||
scroll = Gtk.ScrolledWindow()
|
scroll = Gtk.ScrolledWindow()
|
||||||
@@ -137,3 +165,10 @@ class GridMixin:
|
|||||||
tab_label = notebook.get_tab_label(obj).get_children()[0]
|
tab_label = notebook.get_tab_label(obj).get_children()[0]
|
||||||
|
|
||||||
return store, tab_label
|
return store, tab_label
|
||||||
|
|
||||||
|
def get_icon_grid_from_notebook(self, notebook, _name):
|
||||||
|
for obj in notebook.get_children():
|
||||||
|
icon_grid = obj.get_children()[0]
|
||||||
|
name = icon_grid.get_name()
|
||||||
|
if name == _name:
|
||||||
|
return icon_grid
|
||||||
|
@@ -34,29 +34,46 @@ class TabMixin(GridMixin):
|
|||||||
else:
|
else:
|
||||||
tab.set_path(path)
|
tab.set_path(path)
|
||||||
|
|
||||||
tab_widget = self.create_tab_widget(tab)
|
tab_widget = self.get_tab_widget(tab)
|
||||||
scroll, store = self.create_scroll_and_store(tab, wid)
|
scroll, store = self.create_scroll_and_store(tab, wid)
|
||||||
index = notebook.append_page(scroll, tab_widget)
|
index = notebook.append_page(scroll, tab_widget)
|
||||||
notebook.set_tab_detachable(scroll, True)
|
notebook.set_tab_detachable(scroll, True)
|
||||||
|
notebook.set_tab_reorderable(scroll, True)
|
||||||
|
|
||||||
self.fm_controller.set_wid_and_tid(wid, tab.get_id())
|
self.fm_controller.set_wid_and_tid(wid, tab.get_id())
|
||||||
event_system.emit("go_to_path", (tab.get_current_directory(),)) # NOTE: Not efficent if I understand how
|
|
||||||
# path_entry.set_text(tab.get_current_directory())
|
# path_entry.set_text(tab.get_current_directory())
|
||||||
|
event_system.emit("go_to_path", (tab.get_current_directory(),)) # NOTE: Not efficent if I understand how
|
||||||
notebook.show_all()
|
notebook.show_all()
|
||||||
notebook.set_current_page(index)
|
notebook.set_current_page(index)
|
||||||
|
|
||||||
ctx = notebook.get_style_context()
|
ctx = notebook.get_style_context()
|
||||||
ctx.add_class("notebook-unselected-focus")
|
ctx.add_class("notebook-unselected-focus")
|
||||||
notebook.set_tab_reorderable(scroll, True)
|
|
||||||
self.load_store(tab, store)
|
self.load_store(tab, store)
|
||||||
# self.set_window_title()
|
|
||||||
event_system.emit("set_window_title", (tab.get_current_directory(),))
|
event_system.emit("set_window_title", (tab.get_current_directory(),))
|
||||||
self.set_file_watcher(tab)
|
self.set_file_watcher(tab)
|
||||||
|
|
||||||
|
tab_widget = None
|
||||||
|
scroll, store = None, None
|
||||||
|
index = None
|
||||||
|
notebook = None
|
||||||
|
# path_entry = None
|
||||||
|
tab = None
|
||||||
|
ctx = None
|
||||||
|
|
||||||
|
|
||||||
|
def get_tab_widget(self, tab):
|
||||||
|
tab_widget = self.create_tab_widget()
|
||||||
|
tab_widget.tab_id = tab.get_id()
|
||||||
|
|
||||||
|
tab_widget.label.set_label(f"{tab.get_end_of_path()}")
|
||||||
|
tab_widget.label.set_width_chars(len(tab.get_end_of_path()))
|
||||||
|
|
||||||
|
return tab_widget
|
||||||
|
|
||||||
def close_tab(self, button, eve = None):
|
def close_tab(self, button, eve = None):
|
||||||
notebook = button.get_parent().get_parent()
|
notebook = button.get_parent().get_parent()
|
||||||
if notebook.get_n_pages() == 1:
|
if notebook.get_n_pages() == 1:
|
||||||
|
notebook = None
|
||||||
return
|
return
|
||||||
|
|
||||||
tab_box = button.get_parent()
|
tab_box = button.get_parent()
|
||||||
@@ -74,28 +91,37 @@ class TabMixin(GridMixin):
|
|||||||
self.builder.dereference_object(f"{wid}|{tid}|icon_grid")
|
self.builder.dereference_object(f"{wid}|{tid}|icon_grid")
|
||||||
self.builder.dereference_object(f"{wid}|{tid}")
|
self.builder.dereference_object(f"{wid}|{tid}")
|
||||||
|
|
||||||
|
iter = store.get_iter_first()
|
||||||
|
while iter:
|
||||||
|
next_iter = store.iter_next(iter)
|
||||||
|
store.unref_node(iter)
|
||||||
|
iter = next_iter
|
||||||
|
|
||||||
store.clear()
|
store.clear()
|
||||||
# store.run_dispose()
|
store.run_dispose()
|
||||||
icon_grid.destroy()
|
|
||||||
# icon_grid.run_dispose()
|
|
||||||
scroll.destroy()
|
|
||||||
#scroll.run_dispose()
|
|
||||||
tab_box.destroy()
|
|
||||||
#tab_box.run_dispose()
|
|
||||||
|
|
||||||
del store
|
icon_grid.set_model(None)
|
||||||
del icon_grid
|
icon_grid.run_dispose()
|
||||||
del scroll
|
scroll.run_dispose()
|
||||||
del tab_box
|
tab_box.run_dispose()
|
||||||
del watcher
|
|
||||||
del tab
|
iter = None
|
||||||
|
wid, tid = None, None
|
||||||
|
store = None
|
||||||
|
icon_grid = None
|
||||||
|
scroll = None
|
||||||
|
tab_box = None
|
||||||
|
watcher = None
|
||||||
|
tab = None
|
||||||
|
notebook = None
|
||||||
|
|
||||||
gc.collect()
|
|
||||||
if not settings_manager.is_trace_debug():
|
if not settings_manager.is_trace_debug():
|
||||||
self.fm_controller.save_state()
|
self.fm_controller.save_state()
|
||||||
|
|
||||||
self.set_window_title()
|
self.set_window_title()
|
||||||
|
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
# NOTE: Not actually getting called even tho set in the glade file...
|
# NOTE: Not actually getting called even tho set in the glade file...
|
||||||
def on_tab_dnded(self, notebook, page, x, y):
|
def on_tab_dnded(self, notebook, page, x, y):
|
||||||
...
|
...
|
||||||
@@ -117,15 +143,22 @@ class TabMixin(GridMixin):
|
|||||||
if not settings_manager.is_trace_debug():
|
if not settings_manager.is_trace_debug():
|
||||||
self.fm_controller.save_state()
|
self.fm_controller.save_state()
|
||||||
|
|
||||||
|
wid, tid = None, None
|
||||||
|
window = None
|
||||||
|
tab = None
|
||||||
|
|
||||||
|
|
||||||
def on_tab_switch_update(self, notebook, content = None, index = None):
|
def on_tab_switch_update(self, notebook, content = None, index = None):
|
||||||
self.selected_files.clear()
|
self.selected_files.clear()
|
||||||
wid, tid = content.get_children()[0].get_name().split("|")
|
wid, tid = content.get_children()[0].tab.get_name().split("|")
|
||||||
self.fm_controller.set_wid_and_tid(wid, tid)
|
self.fm_controller.set_wid_and_tid(wid, tid)
|
||||||
self.set_path_text(wid, tid)
|
self.set_path_text(wid, tid)
|
||||||
self.set_window_title()
|
self.set_window_title()
|
||||||
|
|
||||||
|
wid, tid = None, None
|
||||||
|
|
||||||
def get_id_from_tab_box(self, tab_box):
|
def get_id_from_tab_box(self, tab_box):
|
||||||
return tab_box.get_children()[2].get_text()
|
return tab_box.tab.get_id()
|
||||||
|
|
||||||
def get_tab_label(self, notebook, icon_grid):
|
def get_tab_label(self, notebook, icon_grid):
|
||||||
return notebook.get_tab_label(icon_grid.get_parent()).get_children()[0]
|
return notebook.get_tab_label(icon_grid.get_parent()).get_children()[0]
|
||||||
@@ -141,6 +174,8 @@ class TabMixin(GridMixin):
|
|||||||
state.tab.load_directory()
|
state.tab.load_directory()
|
||||||
self.load_store(state.tab, state.store)
|
self.load_store(state.tab, state.store)
|
||||||
|
|
||||||
|
state = None
|
||||||
|
|
||||||
def update_tab(self, tab_label, tab, store, wid, tid):
|
def update_tab(self, tab_label, tab, store, wid, tid):
|
||||||
self.load_store(tab, store)
|
self.load_store(tab, store)
|
||||||
self.set_path_text(wid, tid)
|
self.set_path_text(wid, tid)
|
||||||
@@ -181,16 +216,38 @@ class TabMixin(GridMixin):
|
|||||||
if isinstance(focused_obj, Gtk.Entry):
|
if isinstance(focused_obj, Gtk.Entry):
|
||||||
self.process_path_menu(widget, tab, dir)
|
self.process_path_menu(widget, tab, dir)
|
||||||
|
|
||||||
|
action = None
|
||||||
|
store = None
|
||||||
|
|
||||||
if path.endswith(".") or path == dir:
|
if path.endswith(".") or path == dir:
|
||||||
|
tab_label = None
|
||||||
|
notebook = None
|
||||||
|
wid, tid = None, None
|
||||||
|
path = None
|
||||||
|
tab = None
|
||||||
return
|
return
|
||||||
|
|
||||||
if not tab.set_path(path):
|
if not tab.set_path(path):
|
||||||
|
tab_label = None
|
||||||
|
notebook = None
|
||||||
|
wid, tid = None, None
|
||||||
|
path = None
|
||||||
|
tab = None
|
||||||
return
|
return
|
||||||
|
|
||||||
icon_grid = self.get_icon_grid_from_notebook(notebook, f"{wid}|{tid}")
|
icon_grid = self.get_icon_grid_from_notebook(notebook, f"{wid}|{tid}")
|
||||||
icon_grid.clear_and_set_new_store()
|
icon_grid.clear_and_set_new_store()
|
||||||
self.update_tab(tab_label, tab, store, wid, tid)
|
self.update_tab(tab_label, tab, store, wid, tid)
|
||||||
|
|
||||||
|
action = None
|
||||||
|
wid, tid = None, None
|
||||||
|
notebook = None
|
||||||
|
store, tab_label = None, None
|
||||||
|
path = None
|
||||||
|
tab = None
|
||||||
|
icon_grid = None
|
||||||
|
|
||||||
|
|
||||||
def process_path_menu(self, gtk_entry, tab, dir):
|
def process_path_menu(self, gtk_entry, tab, dir):
|
||||||
path_menu_buttons = self.builder.get_object("path_menu_buttons")
|
path_menu_buttons = self.builder.get_object("path_menu_buttons")
|
||||||
query = gtk_entry.get_text().replace(dir, "")
|
query = gtk_entry.get_text().replace(dir, "")
|
||||||
@@ -207,6 +264,10 @@ class TabMixin(GridMixin):
|
|||||||
path_menu_buttons.add(button)
|
path_menu_buttons.add(button)
|
||||||
show_path_menu = True
|
show_path_menu = True
|
||||||
|
|
||||||
|
path_menu_buttons = None
|
||||||
|
query = None
|
||||||
|
files = None
|
||||||
|
|
||||||
if not show_path_menu:
|
if not show_path_menu:
|
||||||
event_system.emit("hide_path_menu")
|
event_system.emit("hide_path_menu")
|
||||||
else:
|
else:
|
||||||
@@ -225,6 +286,8 @@ class TabMixin(GridMixin):
|
|||||||
button.grab_focus()
|
button.grab_focus()
|
||||||
button.clicked()
|
button.clicked()
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def set_path_entry(self, button = None, eve = None):
|
def set_path_entry(self, button = None, eve = None):
|
||||||
self.path_auto_filled = True
|
self.path_auto_filled = True
|
||||||
state = self.get_current_state()
|
state = self.get_current_state()
|
||||||
@@ -236,9 +299,16 @@ class TabMixin(GridMixin):
|
|||||||
path_entry.set_position(-1)
|
path_entry.set_position(-1)
|
||||||
event_system.emit("hide_path_menu")
|
event_system.emit("hide_path_menu")
|
||||||
|
|
||||||
|
state = None
|
||||||
|
path = None
|
||||||
|
path_entry = None
|
||||||
|
|
||||||
def show_hide_hidden_files(self):
|
def show_hide_hidden_files(self):
|
||||||
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
||||||
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
||||||
tab.set_hiding_hidden(not tab.is_hiding_hidden())
|
tab.set_hiding_hidden(not tab.is_hiding_hidden())
|
||||||
tab.load_directory()
|
tab.load_directory()
|
||||||
self.builder.get_object("refresh_tab").released()
|
self.builder.get_object("refresh_tab").released()
|
||||||
|
|
||||||
|
wid, tid = None, None
|
||||||
|
tab = None
|
||||||
|
@@ -42,10 +42,17 @@ class WindowMixin(TabMixin):
|
|||||||
event_system.emit("set_window_title", (dir,))
|
event_system.emit("set_window_title", (dir,))
|
||||||
self.set_bottom_labels(tab)
|
self.set_bottom_labels(tab)
|
||||||
|
|
||||||
|
wid, tid = None, None
|
||||||
|
notebook = None
|
||||||
|
tab = None
|
||||||
|
dir = None
|
||||||
|
|
||||||
def set_path_text(self, wid, tid):
|
def set_path_text(self, wid, tid):
|
||||||
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
||||||
event_system.emit("go_to_path", (tab.get_current_directory(),))
|
event_system.emit("go_to_path", (tab.get_current_directory(),))
|
||||||
|
|
||||||
|
tab = None
|
||||||
|
|
||||||
def grid_set_selected_items(self, icons_grid):
|
def grid_set_selected_items(self, icons_grid):
|
||||||
new_items = icons_grid.get_selected_items()
|
new_items = icons_grid.get_selected_items()
|
||||||
items_size = len(new_items)
|
items_size = len(new_items)
|
||||||
@@ -160,6 +167,12 @@ class WindowMixin(TabMixin):
|
|||||||
if target not in current:
|
if target not in current:
|
||||||
self.fm_controller.set_wid_and_tid(wid, tid)
|
self.fm_controller.set_wid_and_tid(wid, tid)
|
||||||
|
|
||||||
|
current = None
|
||||||
|
target = None
|
||||||
|
wid, tid = None, None
|
||||||
|
store = None
|
||||||
|
path_at_loc = None
|
||||||
|
|
||||||
|
|
||||||
def grid_on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
|
def grid_on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
|
||||||
if info == 80:
|
if info == 80:
|
||||||
@@ -173,6 +186,10 @@ class WindowMixin(TabMixin):
|
|||||||
if from_uri != dest:
|
if from_uri != dest:
|
||||||
event_system.emit("move_files", (uris, dest))
|
event_system.emit("move_files", (uris, dest))
|
||||||
|
|
||||||
|
Gtk.drag_finish(drag_context, True, False, time)
|
||||||
|
return
|
||||||
|
|
||||||
|
Gtk.drag_finish(drag_context, False, False, time)
|
||||||
|
|
||||||
def create_new_tab_notebook(self, widget=None, wid=None, path=None):
|
def create_new_tab_notebook(self, widget=None, wid=None, path=None):
|
||||||
self.create_tab(wid, None, path)
|
self.create_tab(wid, None, path)
|
||||||
|
@@ -75,6 +75,20 @@ class IconGridWidget(Gtk.IconView):
|
|||||||
return self.get_model()
|
return self.get_model()
|
||||||
|
|
||||||
def clear_and_set_new_store(self):
|
def clear_and_set_new_store(self):
|
||||||
|
store = self.get_model()
|
||||||
|
if store:
|
||||||
|
iter = store.get_iter_first()
|
||||||
|
while iter:
|
||||||
|
next_iter = store.iter_next(iter)
|
||||||
|
store.unref_node(iter)
|
||||||
|
iter = next_iter
|
||||||
|
|
||||||
|
store.clear()
|
||||||
|
store.run_dispose()
|
||||||
|
store = None
|
||||||
|
|
||||||
self.set_model(None)
|
self.set_model(None)
|
||||||
store = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str or None)
|
store = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str or None)
|
||||||
|
# store = Gtk.ListStore(Gtk.DirectoryList)
|
||||||
self.set_model(store)
|
self.set_model(store)
|
||||||
|
store = None
|
||||||
|
@@ -59,7 +59,7 @@ class IconTreeWidget(Gtk.TreeView):
|
|||||||
name = Gtk.CellRendererText()
|
name = Gtk.CellRendererText()
|
||||||
selec = self.get_selection()
|
selec = self.get_selection()
|
||||||
|
|
||||||
self.set_model(store)
|
self.set_model(self._store)
|
||||||
selec.set_mode(3)
|
selec.set_mode(3)
|
||||||
|
|
||||||
column.pack_start(icon, False)
|
column.pack_start(icon, False)
|
||||||
|
@@ -61,7 +61,6 @@ class MessagePopupWidget(Gtk.Popover):
|
|||||||
scroll_window.set_hexpand(True)
|
scroll_window.set_hexpand(True)
|
||||||
vbox.set_orientation(Gtk.Orientation.VERTICAL)
|
vbox.set_orientation(Gtk.Orientation.VERTICAL)
|
||||||
|
|
||||||
self.builder.expose_object(f"message_popup_widget", self)
|
|
||||||
self.builder.expose_object(f"message_text_view", message_text_view)
|
self.builder.expose_object(f"message_text_view", message_text_view)
|
||||||
|
|
||||||
scroll_window.add(message_text_view)
|
scroll_window.add(message_text_view)
|
||||||
@@ -103,7 +102,7 @@ class MessagePopupWidget(Gtk.Popover):
|
|||||||
self.popup()
|
self.popup()
|
||||||
self.hide_message_timeout(seconds)
|
self.hide_message_timeout(seconds)
|
||||||
|
|
||||||
@threaded
|
@daemon_threaded
|
||||||
def hide_message_timeout(self, seconds=3):
|
def hide_message_timeout(self, seconds=3):
|
||||||
time.sleep(seconds)
|
time.sleep(seconds)
|
||||||
GLib.idle_add(event_system.emit, ("hide_messages_popup"))
|
GLib.idle_add(event_system.emit, ("hide_messages_popup"))
|
||||||
|
@@ -9,14 +9,12 @@ from gi.repository import Gtk
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TabHeaderWidget(Gtk.Box):
|
class TabHeaderWidget(Gtk.Box):
|
||||||
"""docstring for TabHeaderWidget"""
|
"""docstring for TabHeaderWidget"""
|
||||||
|
|
||||||
def __init__(self, tab, close_tab):
|
def __init__(self, close_tab):
|
||||||
super(TabHeaderWidget, self).__init__()
|
super(TabHeaderWidget, self).__init__()
|
||||||
|
|
||||||
self._tab = tab
|
|
||||||
self._close_tab = close_tab # NOTE: Close method in tab_mixin
|
self._close_tab = close_tab # NOTE: Close method in tab_mixin
|
||||||
|
|
||||||
self._setup_styling()
|
self._setup_styling()
|
||||||
@@ -32,25 +30,19 @@ class TabHeaderWidget(Gtk.Box):
|
|||||||
...
|
...
|
||||||
|
|
||||||
def _load_widgets(self):
|
def _load_widgets(self):
|
||||||
label = Gtk.Label()
|
self.label = Gtk.Label()
|
||||||
tid = Gtk.Label()
|
|
||||||
close = Gtk.Button()
|
close = Gtk.Button()
|
||||||
icon = Gtk.Image(stock=Gtk.STOCK_CLOSE)
|
icon = Gtk.Image(stock=Gtk.STOCK_CLOSE)
|
||||||
|
|
||||||
label.set_label(f"{self._tab.get_end_of_path()}")
|
self.label.set_xalign(0.0)
|
||||||
label.set_width_chars(len(self._tab.get_end_of_path()))
|
self.label.set_margin_left(25)
|
||||||
label.set_xalign(0.0)
|
self.label.set_margin_right(25)
|
||||||
label.set_margin_left(25)
|
self.label.set_hexpand(True)
|
||||||
label.set_margin_right(25)
|
|
||||||
label.set_hexpand(True)
|
|
||||||
tid.set_label(f"{self._tab.get_id()}")
|
|
||||||
|
|
||||||
close.connect("released", self._close_tab)
|
close.connect("released", self._close_tab)
|
||||||
|
|
||||||
close.add(icon)
|
close.add(icon)
|
||||||
self.add(label)
|
self.add(self.label)
|
||||||
self.add(close)
|
self.add(close)
|
||||||
self.add(tid)
|
|
||||||
|
|
||||||
self.show_all()
|
self.show_all()
|
||||||
tid.hide()
|
|
||||||
|
@@ -10,6 +10,7 @@ gi.require_version('Gdk', '3.0')
|
|||||||
from gi.repository import Gtk
|
from gi.repository import Gtk
|
||||||
from gi.repository import Gdk
|
from gi.repository import Gdk
|
||||||
from gi.repository import GLib
|
from gi.repository import GLib
|
||||||
|
from gi.repository import GObject
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from core.controller import Controller
|
from core.controller import Controller
|
||||||
@@ -24,18 +25,20 @@ class Window(Gtk.ApplicationWindow):
|
|||||||
"""docstring for Window."""
|
"""docstring for Window."""
|
||||||
|
|
||||||
def __init__(self, args, unknownargs):
|
def __init__(self, args, unknownargs):
|
||||||
super(Window, self).__init__()
|
GObject.threads_init()
|
||||||
|
|
||||||
self._controller = None
|
super(Window, self).__init__()
|
||||||
settings_manager.set_main_window(self)
|
settings_manager.set_main_window(self)
|
||||||
|
|
||||||
self._set_window_data()
|
self._controller = None
|
||||||
|
|
||||||
self._setup_styling()
|
self._setup_styling()
|
||||||
self._setup_signals()
|
self._setup_signals()
|
||||||
self._subscribe_to_events()
|
self._subscribe_to_events()
|
||||||
|
|
||||||
self._load_widgets(args, unknownargs)
|
self._load_widgets(args, unknownargs)
|
||||||
|
|
||||||
|
self._set_window_data()
|
||||||
|
self._set_size_constraints()
|
||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
|
|
||||||
@@ -66,6 +69,18 @@ class Window(Gtk.ApplicationWindow):
|
|||||||
|
|
||||||
self.add( self._controller.get_core_widget() )
|
self.add( self._controller.get_core_widget() )
|
||||||
|
|
||||||
|
def _set_size_constraints(self):
|
||||||
|
_window_x = settings.config.main_window_x
|
||||||
|
_window_y = settings.config.main_window_y
|
||||||
|
_min_width = settings.config.main_window_min_width
|
||||||
|
_min_height = settings.config.main_window_min_height
|
||||||
|
_width = settings.config.main_window_width
|
||||||
|
_height = settings.config.main_window_height
|
||||||
|
|
||||||
|
self.move(_window_x, _window_y - 28)
|
||||||
|
self.set_size_request(_min_width, _min_height)
|
||||||
|
self.set_default_size(_width, _height)
|
||||||
|
|
||||||
def _set_window_data(self) -> None:
|
def _set_window_data(self) -> None:
|
||||||
screen = self.get_screen()
|
screen = self.get_screen()
|
||||||
visual = screen.get_rgba_visual()
|
visual = screen.get_rgba_visual()
|
||||||
@@ -73,7 +88,7 @@ class Window(Gtk.ApplicationWindow):
|
|||||||
if visual != None and screen.is_composited():
|
if visual != None and screen.is_composited():
|
||||||
self.set_visual(visual)
|
self.set_visual(visual)
|
||||||
self.set_app_paintable(True)
|
self.set_app_paintable(True)
|
||||||
self.connect("draw", self._area_draw)
|
# self.connect("draw", self._area_draw)
|
||||||
|
|
||||||
# bind css file
|
# bind css file
|
||||||
cssProvider = Gtk.CssProvider()
|
cssProvider = Gtk.CssProvider()
|
||||||
@@ -94,5 +109,18 @@ class Window(Gtk.ApplicationWindow):
|
|||||||
|
|
||||||
def _tear_down(self, widget = None, eve = None):
|
def _tear_down(self, widget = None, eve = None):
|
||||||
event_system.emit("shutting_down")
|
event_system.emit("shutting_down")
|
||||||
|
|
||||||
|
size = self.get_size()
|
||||||
|
pos = self.get_position()
|
||||||
|
|
||||||
|
settings_manager.set_main_window_width(size.width)
|
||||||
|
settings_manager.set_main_window_height(size.height)
|
||||||
|
settings_manager.set_main_window_x(pos.root_x)
|
||||||
|
settings_manager.set_main_window_y(pos.root_y)
|
||||||
|
settings_manager.save_settings()
|
||||||
|
|
||||||
settings_manager.clear_pid()
|
settings_manager.clear_pid()
|
||||||
Gtk.main_quit()
|
Gtk.main_quit()
|
||||||
|
|
||||||
|
def main(self):
|
||||||
|
Gtk.main()
|
@@ -15,32 +15,37 @@ class ManifestProcessorException(Exception):
|
|||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True)
|
@dataclass(slots = True)
|
||||||
class PluginInfo:
|
class PluginInfo:
|
||||||
path: str = None
|
path: str = None
|
||||||
name: str = None
|
name: str = None
|
||||||
author: str = None
|
author: str = None
|
||||||
version: str = None
|
version: str = None
|
||||||
support: str = None
|
support: str = None
|
||||||
requests:{} = None
|
requests:{} = None
|
||||||
reference: type = None
|
reference: type = None
|
||||||
|
pre_launch: bool = False
|
||||||
|
|
||||||
|
|
||||||
class ManifestProcessor:
|
class ManifestProcessor:
|
||||||
def __init__(self, path, builder):
|
def __init__(self, path, builder):
|
||||||
manifest = join(path, "manifest.json")
|
manifest_pth = join(path, "manifest.json")
|
||||||
if not os.path.exists(manifest):
|
if not os.path.exists(manifest_pth):
|
||||||
raise ManifestProcessorException("Invalid Plugin Structure: Plugin doesn't have 'manifest.json'. Aboarting load...")
|
raise ManifestProcessorException("Invalid Plugin Structure: Plugin doesn't have 'manifest.json'. Aboarting load...")
|
||||||
|
|
||||||
self._path = path
|
self._path = path
|
||||||
self._builder = builder
|
self._builder = builder
|
||||||
with open(manifest) as f:
|
with open(manifest_pth) as f:
|
||||||
data = json.load(f)
|
data = json.load(f)
|
||||||
self._manifest = data["manifest"]
|
self._manifest = data["manifest"]
|
||||||
self._plugin = self.collect_info()
|
self._plugin = self.collect_info()
|
||||||
|
|
||||||
|
def is_pre_launch(self) -> bool:
|
||||||
|
return self._plugin.pre_launch
|
||||||
|
|
||||||
def collect_info(self) -> PluginInfo:
|
def collect_info(self) -> PluginInfo:
|
||||||
plugin = PluginInfo()
|
plugin = PluginInfo()
|
||||||
|
|
||||||
plugin.path = self._path
|
plugin.path = self._path
|
||||||
plugin.name = self._manifest["name"]
|
plugin.name = self._manifest["name"]
|
||||||
plugin.author = self._manifest["author"]
|
plugin.author = self._manifest["author"]
|
||||||
@@ -48,14 +53,16 @@ class ManifestProcessor:
|
|||||||
plugin.support = self._manifest["support"]
|
plugin.support = self._manifest["support"]
|
||||||
plugin.requests = self._manifest["requests"]
|
plugin.requests = self._manifest["requests"]
|
||||||
|
|
||||||
|
if "pre_launch" in self._manifest.keys():
|
||||||
|
plugin.pre_launch = True if self._manifest["pre_launch"] == "true" else False
|
||||||
|
|
||||||
return plugin
|
return plugin
|
||||||
|
|
||||||
def get_loading_data(self):
|
def get_loading_data(self):
|
||||||
loading_data = {}
|
loading_data = {}
|
||||||
requests = self._plugin.requests
|
requests = self._plugin.requests
|
||||||
keys = requests.keys()
|
|
||||||
|
|
||||||
if "ui_target" in keys:
|
if "ui_target" in requests:
|
||||||
if requests["ui_target"] in [
|
if requests["ui_target"] in [
|
||||||
"none", "other", "main_Window", "main_menu_bar",
|
"none", "other", "main_Window", "main_menu_bar",
|
||||||
"main_menu_bttn_box_bar", "path_menu_bar", "plugin_control_list",
|
"main_menu_bttn_box_bar", "path_menu_bar", "plugin_control_list",
|
||||||
@@ -63,7 +70,7 @@ class ManifestProcessor:
|
|||||||
"window_2", "window_3", "window_4"
|
"window_2", "window_3", "window_4"
|
||||||
]:
|
]:
|
||||||
if requests["ui_target"] == "other":
|
if requests["ui_target"] == "other":
|
||||||
if "ui_target_id" in keys:
|
if "ui_target_id" in requests:
|
||||||
loading_data["ui_target"] = self._builder.get_object(requests["ui_target_id"])
|
loading_data["ui_target"] = self._builder.get_object(requests["ui_target_id"])
|
||||||
if loading_data["ui_target"] == None:
|
if loading_data["ui_target"] == None:
|
||||||
raise ManifestProcessorException('Invalid "ui_target_id" given in requests. Must have one if setting "ui_target" to "other"...')
|
raise ManifestProcessorException('Invalid "ui_target_id" given in requests. Must have one if setting "ui_target" to "other"...')
|
||||||
@@ -74,11 +81,11 @@ class ManifestProcessor:
|
|||||||
else:
|
else:
|
||||||
raise ManifestProcessorException('Unknown "ui_target" given in requests.')
|
raise ManifestProcessorException('Unknown "ui_target" given in requests.')
|
||||||
|
|
||||||
if "pass_fm_events" in keys:
|
if "pass_fm_events" in requests:
|
||||||
if requests["pass_fm_events"] in ["true"]:
|
if requests["pass_fm_events"] in ["true"]:
|
||||||
loading_data["pass_fm_events"] = True
|
loading_data["pass_fm_events"] = True
|
||||||
|
|
||||||
if "pass_ui_objects" in keys:
|
if "pass_ui_objects" in requests:
|
||||||
if len(requests["pass_ui_objects"]) > 0:
|
if len(requests["pass_ui_objects"]) > 0:
|
||||||
loading_data["pass_ui_objects"] = []
|
loading_data["pass_ui_objects"] = []
|
||||||
for ui_id in requests["pass_ui_objects"]:
|
for ui_id in requests["pass_ui_objects"]:
|
||||||
@@ -87,7 +94,7 @@ class ManifestProcessor:
|
|||||||
except ManifestProcessorException as e:
|
except ManifestProcessorException as e:
|
||||||
logger.error(repr(e))
|
logger.error(repr(e))
|
||||||
|
|
||||||
if "bind_keys" in keys:
|
if "bind_keys" in requests:
|
||||||
if isinstance(requests["bind_keys"], list):
|
if isinstance(requests["bind_keys"], list):
|
||||||
loading_data["bind_keys"] = requests["bind_keys"]
|
loading_data["bind_keys"] = requests["bind_keys"]
|
||||||
|
|
||||||
|
@@ -36,41 +36,76 @@ class PluginsController:
|
|||||||
|
|
||||||
self._plugins_dir_watcher = None
|
self._plugins_dir_watcher = None
|
||||||
self._plugin_collection = []
|
self._plugin_collection = []
|
||||||
|
self._plugin_manifests = {}
|
||||||
|
|
||||||
|
self._load_manifests()
|
||||||
|
|
||||||
|
|
||||||
def launch_plugins(self) -> None:
|
def _load_manifests(self):
|
||||||
|
logger.info(f"Loading manifests...")
|
||||||
|
|
||||||
|
for path, folder in [[join(self._plugins_path, item), item] if os.path.isdir(join(self._plugins_path, item)) else None for item in os.listdir(self._plugins_path)]:
|
||||||
|
manifest = ManifestProcessor(path, self._builder)
|
||||||
|
self._plugin_manifests[path] = {
|
||||||
|
"path": path,
|
||||||
|
"folder": folder,
|
||||||
|
"manifest": manifest
|
||||||
|
}
|
||||||
|
|
||||||
self._set_plugins_watcher()
|
self._set_plugins_watcher()
|
||||||
self.load_plugins()
|
|
||||||
|
|
||||||
def _set_plugins_watcher(self) -> None:
|
def _set_plugins_watcher(self) -> None:
|
||||||
self._plugins_dir_watcher = Gio.File.new_for_path(self._plugins_path) \
|
self._plugins_dir_watcher = Gio.File.new_for_path(self._plugins_path) \
|
||||||
.monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, Gio.Cancellable())
|
.monitor_directory(Gio.FileMonitorFlags.WATCH_MOVES, Gio.Cancellable())
|
||||||
self._plugins_dir_watcher.connect("changed", self._on_plugins_changed, ())
|
self._plugins_dir_watcher.connect("changed", self._on_plugins_changed, ())
|
||||||
|
|
||||||
def _on_plugins_changed(self, file_monitor, file, other_file=None, eve_type=None, data=None):
|
def _on_plugins_changed(self, file_monitor, file, other_file = None, eve_type = None, data = None):
|
||||||
if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED,
|
if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED,
|
||||||
Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN,
|
Gio.FileMonitorEvent.RENAMED, Gio.FileMonitorEvent.MOVED_IN,
|
||||||
Gio.FileMonitorEvent.MOVED_OUT]:
|
Gio.FileMonitorEvent.MOVED_OUT]:
|
||||||
self.reload_plugins(file)
|
self.reload_plugins(file)
|
||||||
|
|
||||||
@daemon_threaded
|
def pre_launch_plugins(self) -> None:
|
||||||
def load_plugins(self, file: str = None) -> None:
|
logger.info(f"Loading pre-launch plugins...")
|
||||||
logger.info(f"Loading plugins...")
|
plugin_manifests: {} = {}
|
||||||
|
|
||||||
|
for key in self._plugin_manifests:
|
||||||
|
target_manifest = self._plugin_manifests[key]["manifest"]
|
||||||
|
if target_manifest.is_pre_launch():
|
||||||
|
plugin_manifests[key] = self._plugin_manifests[key]
|
||||||
|
|
||||||
|
self._load_plugins(plugin_manifests, is_pre_launch = True)
|
||||||
|
|
||||||
|
def post_launch_plugins(self) -> None:
|
||||||
|
logger.info(f"Loading post-launch plugins...")
|
||||||
|
plugin_manifests: {} = {}
|
||||||
|
|
||||||
|
for key in self._plugin_manifests:
|
||||||
|
target_manifest = self._plugin_manifests[key]["manifest"]
|
||||||
|
if not target_manifest.is_pre_launch():
|
||||||
|
plugin_manifests[key] = self._plugin_manifests[key]
|
||||||
|
|
||||||
|
self._load_plugins(plugin_manifests)
|
||||||
|
|
||||||
|
def _load_plugins(self, plugin_manifests: {} = {}, is_pre_launch: bool = False) -> None:
|
||||||
parent_path = os.getcwd()
|
parent_path = os.getcwd()
|
||||||
|
|
||||||
for path, folder in [[join(self._plugins_path, item), item] if os.path.isdir(join(self._plugins_path, item)) else None for item in os.listdir(self._plugins_path)]:
|
for key in plugin_manifests:
|
||||||
try:
|
target_manifest = plugin_manifests[key]
|
||||||
target = join(path, "plugin.py")
|
path, folder, manifest = target_manifest["path"], target_manifest["folder"], target_manifest["manifest"]
|
||||||
manifest = ManifestProcessor(path, self._builder)
|
|
||||||
|
|
||||||
|
try:
|
||||||
|
target = join(path, "plugin.py")
|
||||||
if not os.path.exists(target):
|
if not os.path.exists(target):
|
||||||
raise FileNotFoundError("Invalid Plugin Structure: Plugin doesn't have 'plugin.py'. Aboarting load...")
|
raise FileNotFoundError("Invalid Plugin Structure: Plugin doesn't have 'plugin.py'. Aboarting load...")
|
||||||
|
|
||||||
plugin, loading_data = manifest.get_loading_data()
|
plugin, loading_data = manifest.get_loading_data()
|
||||||
module = self.load_plugin_module(path, folder, target)
|
module = self.load_plugin_module(path, folder, target)
|
||||||
|
|
||||||
GLib.idle_add(self.execute_plugin, *(module, plugin, loading_data))
|
if is_pre_launch:
|
||||||
# self.execute_plugin(module, plugin, loading_data)
|
self.execute_plugin(module, plugin, loading_data)
|
||||||
|
else:
|
||||||
|
GLib.idle_add(self.execute_plugin, *(module, plugin, loading_data))
|
||||||
except InvalidPluginException as e:
|
except InvalidPluginException as e:
|
||||||
logger.info(f"Malformed Plugin: Not loading -->: '{folder}' !")
|
logger.info(f"Malformed Plugin: Not loading -->: '{folder}' !")
|
||||||
logger.debug("Trace: ", traceback.print_exc())
|
logger.debug("Trace: ", traceback.print_exc())
|
||||||
@@ -100,25 +135,26 @@ class PluginsController:
|
|||||||
|
|
||||||
def execute_plugin(self, module: type, plugin: PluginInfo, loading_data: []):
|
def execute_plugin(self, module: type, plugin: PluginInfo, loading_data: []):
|
||||||
plugin.reference = module.Plugin()
|
plugin.reference = module.Plugin()
|
||||||
keys = loading_data.keys()
|
|
||||||
|
|
||||||
if "ui_target" in keys:
|
if "ui_target" in loading_data:
|
||||||
loading_data["ui_target"].add( plugin.reference.generate_reference_ui_element() )
|
loading_data["ui_target"].add( plugin.reference.generate_reference_ui_element() )
|
||||||
loading_data["ui_target"].show_all()
|
loading_data["ui_target"].show_all()
|
||||||
|
|
||||||
if "pass_ui_objects" in keys:
|
if "pass_ui_objects" in loading_data:
|
||||||
plugin.reference.set_ui_object_collection( loading_data["pass_ui_objects"] )
|
plugin.reference.set_ui_object_collection( loading_data["pass_ui_objects"] )
|
||||||
|
|
||||||
if "pass_fm_events" in keys:
|
if "pass_fm_events" in loading_data:
|
||||||
plugin.reference.set_fm_event_system(event_system)
|
plugin.reference.set_fm_event_system(event_system)
|
||||||
plugin.reference.subscribe_to_events()
|
plugin.reference.subscribe_to_events()
|
||||||
|
|
||||||
if "bind_keys" in keys:
|
if "bind_keys" in loading_data:
|
||||||
keybindings.append_bindings( loading_data["bind_keys"] )
|
keybindings.append_bindings( loading_data["bind_keys"] )
|
||||||
|
|
||||||
plugin.reference.run()
|
plugin.reference.run()
|
||||||
self._plugin_collection.append(plugin)
|
self._plugin_collection.append(plugin)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
def reload_plugins(self, file: str = None) -> None:
|
def reload_plugins(self, file: str = None) -> None:
|
||||||
logger.info(f"Reloading plugins...")
|
logger.info(f"Reloading plugins...")
|
||||||
parent_path = os.getcwd()
|
parent_path = os.getcwd()
|
||||||
|
@@ -14,7 +14,6 @@ from random import randint
|
|||||||
from .utils.settings import Settings
|
from .utils.settings import Settings
|
||||||
from .utils.launcher import Launcher
|
from .utils.launcher import Launcher
|
||||||
from .utils.filehandler import FileHandler
|
from .utils.filehandler import FileHandler
|
||||||
from .icons.icon import Icon
|
|
||||||
from .path import Path
|
from .path import Path
|
||||||
|
|
||||||
|
|
||||||
@@ -40,9 +39,8 @@ except Exception as e:
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
class Tab(Settings, FileHandler, Launcher, Icon, Path):
|
class Tab(Settings, FileHandler, Launcher, Path):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.logger = None
|
|
||||||
self._id_length: int = 10
|
self._id_length: int = 10
|
||||||
|
|
||||||
self._id: str = ""
|
self._id: str = ""
|
||||||
@@ -168,33 +166,6 @@ class Tab(Settings, FileHandler, Launcher, Icon, Path):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def get_video_icons(self) -> list:
|
|
||||||
data = []
|
|
||||||
dir = self.get_current_directory()
|
|
||||||
for file in self._vids:
|
|
||||||
img_hash, hash_img_path = self.create_video_thumbnail(full_path=f"{dir}/{file}", returnHashInstead=True)
|
|
||||||
data.append([img_hash, hash_img_path])
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
def get_pixbuf_icon_str_combo(self):
|
|
||||||
data = []
|
|
||||||
dir = self.get_current_directory()
|
|
||||||
for file in self._files:
|
|
||||||
icon = self.create_icon(dir, file).get_pixbuf()
|
|
||||||
data.append([icon, file])
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
def get_gtk_icon_str_combo(self) -> list:
|
|
||||||
data = []
|
|
||||||
dir = self.get_current_directory()
|
|
||||||
for file in self._files:
|
|
||||||
icon = self.create_icon(dir, file)
|
|
||||||
data.append([icon, file[0]])
|
|
||||||
|
|
||||||
return data
|
|
||||||
|
|
||||||
def get_current_directory(self) -> str:
|
def get_current_directory(self) -> str:
|
||||||
return self.get_path()
|
return self.get_path()
|
||||||
|
|
||||||
@@ -264,7 +235,7 @@ class Tab(Settings, FileHandler, Launcher, Icon, Path):
|
|||||||
return int(text) if text.isdigit() else text
|
return int(text) if text.isdigit() else text
|
||||||
|
|
||||||
def _natural_keys(self, text):
|
def _natural_keys(self, text):
|
||||||
return [ self._atoi(c) for c in re.split('(\d+)',text) ]
|
return [ self._atoi(c) for c in re.split(r'(\d+)', text) ]
|
||||||
|
|
||||||
def _hash_text(self, text) -> str:
|
def _hash_text(self, text) -> str:
|
||||||
return hashlib.sha256(str.encode(text)).hexdigest()[:18]
|
return hashlib.sha256(str.encode(text)).hexdigest()[:18]
|
||||||
@@ -289,3 +260,7 @@ class Tab(Settings, FileHandler, Launcher, Icon, Path):
|
|||||||
|
|
||||||
def _set_error_message(self, text: str):
|
def _set_error_message(self, text: str):
|
||||||
self.error_message = text
|
self.error_message = text
|
||||||
|
|
||||||
|
|
||||||
|
def create_icon(self, dir, file):
|
||||||
|
return event_system.emit_and_await("create-thumbnail", (dir, file,))
|
||||||
|
@@ -41,11 +41,11 @@ class Launcher:
|
|||||||
|
|
||||||
def execute(self, command, start_dir=os.getenv("HOME"), use_shell=False):
|
def execute(self, command, start_dir=os.getenv("HOME"), use_shell=False):
|
||||||
try:
|
try:
|
||||||
self.logger.debug(command)
|
logger.debug(command)
|
||||||
subprocess.Popen(command, cwd=start_dir, shell=use_shell, start_new_session=True, stdout=None, stderr=None, close_fds=True)
|
subprocess.Popen(command, cwd=start_dir, shell=use_shell, start_new_session=True, stdout=None, stderr=None, close_fds=True)
|
||||||
except ShellFMLauncherException as e:
|
except ShellFMLauncherException as e:
|
||||||
self.logger.error(f"Couldn't execute: {command}")
|
logger.error(f"Couldn't execute: {command}")
|
||||||
self.logger.error(e)
|
logger.error(e)
|
||||||
|
|
||||||
# TODO: Return std(out/in/err) handlers along with subprocess instead of sinking to null
|
# TODO: Return std(out/in/err) handlers along with subprocess instead of sinking to null
|
||||||
def execute_and_return_thread_handler(self, command, start_dir=os.getenv("HOME"), use_shell=False):
|
def execute_and_return_thread_handler(self, command, start_dir=os.getenv("HOME"), use_shell=False):
|
||||||
@@ -53,8 +53,8 @@ class Launcher:
|
|||||||
DEVNULL = open(os.devnull, 'w')
|
DEVNULL = open(os.devnull, 'w')
|
||||||
return subprocess.Popen(command, cwd=start_dir, shell=use_shell, start_new_session=False, stdout=DEVNULL, stderr=DEVNULL, close_fds=False)
|
return subprocess.Popen(command, cwd=start_dir, shell=use_shell, start_new_session=False, stdout=DEVNULL, stderr=DEVNULL, close_fds=False)
|
||||||
except ShellFMLauncherException as e:
|
except ShellFMLauncherException as e:
|
||||||
self.logger.error(f"Couldn't execute and return thread: {command}")
|
logger.error(f"Couldn't execute and return thread: {command}")
|
||||||
self.logger.error(e)
|
logger.error(e)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@threaded
|
@threaded
|
||||||
@@ -63,7 +63,7 @@ class Launcher:
|
|||||||
|
|
||||||
def remux_video(self, hash, file):
|
def remux_video(self, hash, file):
|
||||||
remux_vid_pth = "{self.REMUX_FOLDER}/{hash}.mp4"
|
remux_vid_pth = "{self.REMUX_FOLDER}/{hash}.mp4"
|
||||||
self.logger.debug(remux_vid_pth)
|
logger.debug(remux_vid_pth)
|
||||||
|
|
||||||
if not os.path.isfile(remux_vid_pth):
|
if not os.path.isfile(remux_vid_pth):
|
||||||
self.check_remux_space()
|
self.check_remux_space()
|
||||||
@@ -83,8 +83,8 @@ class Launcher:
|
|||||||
proc = subprocess.Popen(command)
|
proc = subprocess.Popen(command)
|
||||||
proc.wait()
|
proc.wait()
|
||||||
except ShellFMLauncherException as e:
|
except ShellFMLauncherException as e:
|
||||||
self.logger.error(message)
|
logger.error(message)
|
||||||
self.logger.error(e)
|
logger.error(e)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@@ -94,7 +94,7 @@ class Launcher:
|
|||||||
try:
|
try:
|
||||||
limit = int(limit)
|
limit = int(limit)
|
||||||
except ShellFMLauncherException as e:
|
except ShellFMLauncherException as e:
|
||||||
self.logger.debug(e)
|
logger.debug(e)
|
||||||
return
|
return
|
||||||
|
|
||||||
usage = self.get_remux_folder_usage(self.REMUX_FOLDER)
|
usage = self.get_remux_folder_usage(self.REMUX_FOLDER)
|
||||||
|
@@ -14,8 +14,6 @@ class ShellFMSettingsException(Exception):
|
|||||||
|
|
||||||
|
|
||||||
class Settings:
|
class Settings:
|
||||||
logger = None
|
|
||||||
|
|
||||||
# NOTE: app_name should be defined using python 'builtins'
|
# NOTE: app_name should be defined using python 'builtins'
|
||||||
app_name_exists = False
|
app_name_exists = False
|
||||||
try:
|
try:
|
||||||
@@ -31,45 +29,13 @@ class Settings:
|
|||||||
CONFIG_FILE = f"{CONFIG_PATH}/settings.json"
|
CONFIG_FILE = f"{CONFIG_PATH}/settings.json"
|
||||||
HIDE_HIDDEN_FILES = True
|
HIDE_HIDDEN_FILES = True
|
||||||
|
|
||||||
DEFAULT_ICONS = f"{CONFIG_PATH}/icons"
|
|
||||||
DEFAULT_ICON = f"{DEFAULT_ICONS}/text.png"
|
|
||||||
FFMPG_THUMBNLR = f"{CONFIG_PATH}/ffmpegthumbnailer" # Thumbnail generator binary
|
|
||||||
BLENDER_THUMBNLR = f"{CONFIG_PATH}/blender-thumbnailer" # Blender thumbnail generator binary
|
|
||||||
REMUX_FOLDER = f"{USER_HOME}/.remuxs" # Remuxed files folder
|
REMUX_FOLDER = f"{USER_HOME}/.remuxs" # Remuxed files folder
|
||||||
|
|
||||||
ICON_DIRS = ["/usr/share/icons", f"{USER_HOME}/.icons" "/usr/share/pixmaps"]
|
|
||||||
BASE_THUMBS_PTH = f"{USER_HOME}/.thumbnails"
|
|
||||||
ABS_THUMBS_PTH = f"{BASE_THUMBS_PTH}/normal"
|
|
||||||
STEAM_ICONS_PTH = f"{BASE_THUMBS_PTH}/steam_icons"
|
|
||||||
|
|
||||||
if not os.path.exists(CONFIG_PATH) or not os.path.exists(CONFIG_FILE):
|
|
||||||
msg = f"No config file located! Aborting loading ShellFM library...\nExpected: {CONFIG_FILE}"
|
|
||||||
raise ShellFMSettingsException(msg)
|
|
||||||
|
|
||||||
if not path.isdir(REMUX_FOLDER):
|
|
||||||
os.mkdir(REMUX_FOLDER)
|
|
||||||
|
|
||||||
if not path.isdir(BASE_THUMBS_PTH):
|
|
||||||
os.mkdir(BASE_THUMBS_PTH)
|
|
||||||
|
|
||||||
if not path.isdir(ABS_THUMBS_PTH):
|
|
||||||
os.mkdir(ABS_THUMBS_PTH)
|
|
||||||
|
|
||||||
if not path.isdir(STEAM_ICONS_PTH):
|
|
||||||
os.mkdir(STEAM_ICONS_PTH)
|
|
||||||
|
|
||||||
if not os.path.exists(DEFAULT_ICONS):
|
|
||||||
DEFAULT_ICONS = f"{USR_APP_CONTEXT}/icons"
|
|
||||||
DEFAULT_ICON = f"{DEFAULT_ICONS}/text.png"
|
|
||||||
|
|
||||||
with open(CONFIG_FILE) as f:
|
with open(CONFIG_FILE) as f:
|
||||||
settings = json.load(f)
|
settings = json.load(f)
|
||||||
config = settings["config"]
|
config = settings["config"]
|
||||||
|
|
||||||
subpath = config["base_of_home"]
|
subpath = config["base_of_home"]
|
||||||
STEAM_CDN_URL = config["steam_cdn_url"]
|
|
||||||
FFMPG_THUMBNLR = FFMPG_THUMBNLR if config["thumbnailer_path"] == "" else config["thumbnailer_path"]
|
|
||||||
BLENDER_THUMBNLR = BLENDER_THUMBNLR if config["blender_thumbnailer_path"] == "" else config["blender_thumbnailer_path"]
|
|
||||||
HIDE_HIDDEN_FILES = True if config["hide_hidden_files"] in ["true", ""] else False
|
HIDE_HIDDEN_FILES = True if config["hide_hidden_files"] in ["true", ""] else False
|
||||||
go_past_home = True if config["go_past_home"] in ["true", ""] else False
|
go_past_home = True if config["go_past_home"] in ["true", ""] else False
|
||||||
lock_folder = False if config["lock_folder"] in ["false", ""] else True
|
lock_folder = False if config["lock_folder"] in ["false", ""] else True
|
||||||
@@ -83,9 +49,6 @@ class Settings:
|
|||||||
code_app = config["code_app"]
|
code_app = config["code_app"]
|
||||||
text_app = config["text_app"]
|
text_app = config["text_app"]
|
||||||
terminal_app = config["terminal_app"]
|
terminal_app = config["terminal_app"]
|
||||||
container_icon_wh = config["container_icon_wh"]
|
|
||||||
video_icon_wh = config["video_icon_wh"]
|
|
||||||
sys_icon_wh = config["sys_icon_wh"]
|
|
||||||
file_manager_app = config["file_manager_app"]
|
file_manager_app = config["file_manager_app"]
|
||||||
remux_folder_max_disk_usage = config["remux_folder_max_disk_usage"]
|
remux_folder_max_disk_usage = config["remux_folder_max_disk_usage"]
|
||||||
|
|
||||||
|
@@ -18,7 +18,7 @@ def debug_signal_handler(signal, frame):
|
|||||||
rpdb2.start_embedded_debugger("foobar", True, True)
|
rpdb2.start_embedded_debugger("foobar", True, True)
|
||||||
rpdb2.setbreak(depth=1)
|
rpdb2.setbreak(depth=1)
|
||||||
return
|
return
|
||||||
except StandardError:
|
except Exception:
|
||||||
...
|
...
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -26,7 +26,7 @@ def debug_signal_handler(signal, frame):
|
|||||||
logger.debug("\n\nStarting embedded rconsole debugger...\n\n")
|
logger.debug("\n\nStarting embedded rconsole debugger...\n\n")
|
||||||
rconsole.spawn_server()
|
rconsole.spawn_server()
|
||||||
return
|
return
|
||||||
except StandardError as ex:
|
except Exception as ex:
|
||||||
...
|
...
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -34,7 +34,15 @@ def debug_signal_handler(signal, frame):
|
|||||||
logger.debug("\n\nStarting PuDB debugger...\n\n")
|
logger.debug("\n\nStarting PuDB debugger...\n\n")
|
||||||
set_trace(paused = True)
|
set_trace(paused = True)
|
||||||
return
|
return
|
||||||
except StandardError as ex:
|
except Exception as ex:
|
||||||
|
...
|
||||||
|
|
||||||
|
try:
|
||||||
|
import ipdb
|
||||||
|
logger.debug("\n\nStarting IPDB debugger...\n\n")
|
||||||
|
ipdb.set_trace()
|
||||||
|
return
|
||||||
|
except Exception as ex:
|
||||||
...
|
...
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -42,11 +50,11 @@ def debug_signal_handler(signal, frame):
|
|||||||
logger.debug("\n\nStarting embedded PDB debugger...\n\n")
|
logger.debug("\n\nStarting embedded PDB debugger...\n\n")
|
||||||
pdb.Pdb(skip=['gi.*']).set_trace()
|
pdb.Pdb(skip=['gi.*']).set_trace()
|
||||||
return
|
return
|
||||||
except StandardError as ex:
|
except Exception as ex:
|
||||||
...
|
...
|
||||||
|
|
||||||
try:
|
try:
|
||||||
import code
|
import code
|
||||||
code.interact()
|
code.interact()
|
||||||
except StandardError as ex:
|
except Exception as ex:
|
||||||
logger.debug(f"{ex}, returning to normal program flow...")
|
logger.debug(f"{ex}, returning to normal program flow...")
|
@@ -51,15 +51,20 @@ class IPCServer(Singleton):
|
|||||||
listener = Listener((self._ipc_address, self._ipc_port))
|
listener = Listener((self._ipc_address, self._ipc_port))
|
||||||
|
|
||||||
self.is_ipc_alive = True
|
self.is_ipc_alive = True
|
||||||
self._run_ipc_loop(listener)
|
# self._run_ipc_loop(listener)
|
||||||
|
GLib.Thread("", self._run_ipc_loop, listener)
|
||||||
|
|
||||||
@daemon_threaded
|
# @daemon_threaded
|
||||||
def _run_ipc_loop(self, listener) -> None:
|
def _run_ipc_loop(self, listener) -> None:
|
||||||
while True:
|
while True:
|
||||||
try:
|
try:
|
||||||
conn = listener.accept()
|
conn = listener.accept()
|
||||||
start_time = time.perf_counter()
|
start_time = time.perf_counter()
|
||||||
|
|
||||||
GLib.idle_add(self._handle_ipc_message, *(conn, start_time,))
|
GLib.idle_add(self._handle_ipc_message, *(conn, start_time,))
|
||||||
|
|
||||||
|
conn = None
|
||||||
|
start_time = None
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug( repr(e) )
|
logger.debug( repr(e) )
|
||||||
|
|
||||||
@@ -73,22 +78,29 @@ class IPCServer(Singleton):
|
|||||||
if "FILE|" in msg:
|
if "FILE|" in msg:
|
||||||
file = msg.split("FILE|")[1].strip()
|
file = msg.split("FILE|")[1].strip()
|
||||||
if file:
|
if file:
|
||||||
event_system.emit("handle_file_from_ipc", file)
|
event_system.emit_and_await("handle_file_from_ipc", file)
|
||||||
|
|
||||||
|
msg = None
|
||||||
|
file = None
|
||||||
conn.close()
|
conn.close()
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
if msg in ['close connection', 'close server']:
|
if msg in ['close connection', 'close server']:
|
||||||
|
msg = None
|
||||||
conn.close()
|
conn.close()
|
||||||
break
|
break
|
||||||
|
|
||||||
# NOTE: Not perfect but insures we don't lock up the connection for too long.
|
# NOTE: Not perfect but insures we don't lock up the connection for too long.
|
||||||
end_time = time.perf_counter()
|
end_time = time.perf_counter()
|
||||||
if (end_time - start_time) > self._ipc_timeout:
|
if (end_time - start_time) > self._ipc_timeout:
|
||||||
|
msg = None
|
||||||
|
end_time = None
|
||||||
conn.close()
|
conn.close()
|
||||||
break
|
break
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def send_ipc_message(self, message: str = "Empty Data...") -> None:
|
def send_ipc_message(self, message: str = "Empty Data...") -> None:
|
||||||
try:
|
try:
|
||||||
|
@@ -146,6 +146,13 @@ class SettingsManager(StartCheckMixin, Singleton):
|
|||||||
def is_trace_debug(self) -> bool: return self._trace_debug
|
def is_trace_debug(self) -> bool: return self._trace_debug
|
||||||
def is_debug(self) -> bool: return self._debug
|
def is_debug(self) -> bool: return self._debug
|
||||||
|
|
||||||
|
def set_main_window_x(self, x = 0): self.settings.config.main_window_x = x
|
||||||
|
def set_main_window_y(self, y = 0): self.settings.config.main_window_y = y
|
||||||
|
def set_main_window_width(self, width = 800): self.settings.config.main_window_width = width
|
||||||
|
def set_main_window_height(self, height = 600): self.settings.config.main_window_height = height
|
||||||
|
def set_main_window_min_width(self, width = 720): self.settings.config.main_window_min_width = width
|
||||||
|
def set_main_window_min_height(self, height = 480): self.settings.config.main_window_min_height = height
|
||||||
|
|
||||||
def set_trace_debug(self, trace_debug: bool):
|
def set_trace_debug(self, trace_debug: bool):
|
||||||
self._trace_debug = trace_debug
|
self._trace_debug = trace_debug
|
||||||
|
|
||||||
|
@@ -30,6 +30,13 @@ class Config:
|
|||||||
sys_icon_wh: list = field(default_factory=lambda: [56, 56])
|
sys_icon_wh: list = field(default_factory=lambda: [56, 56])
|
||||||
steam_cdn_url: str = "https://steamcdn-a.akamaihd.net/steam/apps/"
|
steam_cdn_url: str = "https://steamcdn-a.akamaihd.net/steam/apps/"
|
||||||
remux_folder_max_disk_usage: str = "8589934592"
|
remux_folder_max_disk_usage: str = "8589934592"
|
||||||
|
make_transparent: int = 0
|
||||||
|
main_window_x: int = 721
|
||||||
|
main_window_y: int = 465
|
||||||
|
main_window_min_width: int = 720
|
||||||
|
main_window_min_height: int = 480
|
||||||
|
main_window_width: int = 800
|
||||||
|
main_window_height: int = 600
|
||||||
application_dirs: list = field(default_factory=lambda: [
|
application_dirs: list = field(default_factory=lambda: [
|
||||||
"/usr/share/applications",
|
"/usr/share/applications",
|
||||||
f"{settings_manager.get_home_path()}/.local/share/applications"
|
f"{settings_manager.get_home_path()}/.local/share/applications"
|
||||||
|
@@ -11,36 +11,48 @@ import inspect
|
|||||||
|
|
||||||
|
|
||||||
class StartCheckMixin:
|
class StartCheckMixin:
|
||||||
def is_dirty_start(self) -> bool: return self._dirty_start
|
def is_dirty_start(self) -> bool:
|
||||||
def clear_pid(self): self._clean_pid()
|
return self._dirty_start
|
||||||
|
|
||||||
|
def clear_pid(self):
|
||||||
|
if not self.is_trace_debug():
|
||||||
|
self._clean_pid()
|
||||||
|
|
||||||
def do_dirty_start_check(self):
|
def do_dirty_start_check(self):
|
||||||
if not os.path.exists(self._PID_FILE):
|
if self.is_trace_debug():
|
||||||
self._write_new_pid()
|
pid = os.getpid()
|
||||||
else:
|
self._print_pid(pid)
|
||||||
with open(self._PID_FILE, "r") as _pid:
|
return
|
||||||
pid = _pid.readline().strip()
|
|
||||||
|
if os.path.exists(self._PID_FILE):
|
||||||
|
with open(self._PID_FILE, "r") as f:
|
||||||
|
pid = f.readline().strip()
|
||||||
if pid not in ("", None):
|
if pid not in ("", None):
|
||||||
self._check_alive_status(int(pid))
|
if self.is_pid_alive( int(pid) ):
|
||||||
else:
|
print("PID file exists and PID is alive... Letting downstream errors (sans debug args) handle app closure propigation.")
|
||||||
self._write_new_pid()
|
return
|
||||||
|
|
||||||
|
self._write_new_pid()
|
||||||
|
|
||||||
""" Check For the existence of a unix pid. """
|
""" Check For the existence of a unix pid. """
|
||||||
def _check_alive_status(self, pid):
|
def is_pid_alive(self, pid):
|
||||||
print(f"PID Found: {pid}")
|
print(f"PID Found: {pid}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
os.kill(pid, 0)
|
os.kill(pid, 0)
|
||||||
except OSError:
|
except OSError:
|
||||||
print(f"{app_name} is starting dirty...")
|
print(f"{app_name} PID file exists but PID is irrelevant; starting dirty...")
|
||||||
self._dirty_start = True
|
self._dirty_start = True
|
||||||
self._write_new_pid()
|
return False
|
||||||
return
|
|
||||||
|
|
||||||
print("PID is alive... Let downstream errors (sans debug args) handle app closure propigation.")
|
return True
|
||||||
|
|
||||||
def _write_new_pid(self):
|
def _write_new_pid(self):
|
||||||
pid = os.getpid()
|
pid = os.getpid()
|
||||||
self._write_pid(pid)
|
self._write_pid(pid)
|
||||||
|
self._print_pid(pid)
|
||||||
|
|
||||||
|
def _print_pid(self, pid):
|
||||||
print(f"{app_name} PID: {pid}")
|
print(f"{app_name} PID: {pid}")
|
||||||
|
|
||||||
def _clean_pid(self):
|
def _clean_pid(self):
|
||||||
|
@@ -20,6 +20,9 @@ function main() {
|
|||||||
files[$size]="${target}"
|
files[$size]="${target}"
|
||||||
done
|
done
|
||||||
|
|
||||||
|
G_SLICE=always-malloc
|
||||||
|
G_DEBUG=gc-friendly
|
||||||
|
GOBJECT_DEBUG=instance-count
|
||||||
python /opt/solarfm.zip "${files[@]}"
|
python /opt/solarfm.zip "${files[@]}"
|
||||||
}
|
}
|
||||||
main "$@";
|
main "$@";
|
||||||
|
@@ -1,8 +1,9 @@
|
|||||||
{
|
{
|
||||||
"Open Actions": {
|
"Open Actions": {
|
||||||
"Open": ["STOCK_OPEN", "open"],
|
"Open": ["STOCK_OPEN", "open"],
|
||||||
"Open With": ["STOCK_OPEN", "open_with"],
|
"Open With": ["STOCK_OPEN", "open_with"],
|
||||||
"Execute": ["STOCK_EXECUTE", "execute"],
|
"Open 2 Tab": ["STOCK_OPEN", "open_2_new_tab"],
|
||||||
|
"Execute": ["STOCK_EXECUTE", "execute"],
|
||||||
"Execute in Terminal": ["STOCK_EXECUTE", "execute_in_terminal"]
|
"Execute in Terminal": ["STOCK_EXECUTE", "execute_in_terminal"]
|
||||||
},
|
},
|
||||||
"File Actions": {
|
"File Actions": {
|
||||||
|
@@ -2,8 +2,6 @@
|
|||||||
"config": {
|
"config": {
|
||||||
"base_of_home": "",
|
"base_of_home": "",
|
||||||
"hide_hidden_files": "true",
|
"hide_hidden_files": "true",
|
||||||
"thumbnailer_path": "ffmpegthumbnailer",
|
|
||||||
"blender_thumbnailer_path": "",
|
|
||||||
"go_past_home": "true",
|
"go_past_home": "true",
|
||||||
"lock_folder": "false",
|
"lock_folder": "false",
|
||||||
"locked_folders": "venv::::flasks",
|
"locked_folders": "venv::::flasks",
|
||||||
@@ -16,12 +14,18 @@
|
|||||||
"code_app": "newton",
|
"code_app": "newton",
|
||||||
"text_app": "mousepad",
|
"text_app": "mousepad",
|
||||||
"terminal_app": "terminator",
|
"terminal_app": "terminator",
|
||||||
"container_icon_wh": [128, 128],
|
|
||||||
"video_icon_wh": [128, 64],
|
|
||||||
"sys_icon_wh": [56, 56],
|
|
||||||
"file_manager_app": "solarfm",
|
"file_manager_app": "solarfm",
|
||||||
"steam_cdn_url": "https://steamcdn-a.akamaihd.net/steam/apps/",
|
"remux_folder_max_disk_usage": "8589934592",
|
||||||
"remux_folder_max_disk_usage": "8589934592"
|
"make_transparent":0,
|
||||||
|
"main_window_x":721,
|
||||||
|
"main_window_y":465,
|
||||||
|
"main_window_min_width":720,
|
||||||
|
"main_window_min_height":480,
|
||||||
|
"main_window_width":800,
|
||||||
|
"main_window_height":600,
|
||||||
|
"application_dirs":[
|
||||||
|
"/usr/share/applications"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters": {
|
||||||
"meshs": [".dae", ".fbx", ".gltf", ".obj", ".stl"],
|
"meshs": [".dae", ".fbx", ".gltf", ".obj", ".stl"],
|
||||||
|
@@ -8,7 +8,7 @@
|
|||||||
<property name="can-focus">False</property>
|
<property name="can-focus">False</property>
|
||||||
<property name="border-width">5</property>
|
<property name="border-width">5</property>
|
||||||
<property name="window-position">center-on-parent</property>
|
<property name="window-position">center-on-parent</property>
|
||||||
<property name="icon">../icons/solarfm.png</property>
|
<property name="icon">../icons/solarfm-64x64.png</property>
|
||||||
<property name="type-hint">dialog</property>
|
<property name="type-hint">dialog</property>
|
||||||
<property name="skip-taskbar-hint">True</property>
|
<property name="skip-taskbar-hint">True</property>
|
||||||
<property name="skip-pager-hint">True</property>
|
<property name="skip-pager-hint">True</property>
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
<property name="copyright" translatable="yes">Copyright (C) 2021 GPL2</property>
|
<property name="copyright" translatable="yes">Copyright (C) 2021 GPL2</property>
|
||||||
<property name="comments" translatable="yes">by ITDominator</property>
|
<property name="comments" translatable="yes">by ITDominator</property>
|
||||||
<property name="website">https://code.itdominator.com/itdominator/SolarFM</property>
|
<property name="website">https://code.itdominator.com/itdominator/SolarFM</property>
|
||||||
|
<property name="website-label" translatable="yes">ITDominator</property>
|
||||||
<property name="license" translatable="yes">SolarFM - Copyright (C) 2021 ITDominator GPL2
|
<property name="license" translatable="yes">SolarFM - Copyright (C) 2021 ITDominator GPL2
|
||||||
|
|
||||||
|
|
||||||
@@ -367,7 +368,9 @@ Public License instead of this License.
|
|||||||
|
|
||||||
|
|
||||||
SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspection.</property>
|
SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspection.</property>
|
||||||
<property name="translator-credits" translatable="yes" comments="Please replace this line with your own names, one name per line. ">translator-credits</property>
|
<property name="documenters">...</property>
|
||||||
|
<property name="translator-credits" translatable="yes" comments="Please replace this line with your own names, one name per line. ">...</property>
|
||||||
|
<property name="artists">...</property>
|
||||||
<property name="logo">../icons/solarfm-64x64.png</property>
|
<property name="logo">../icons/solarfm-64x64.png</property>
|
||||||
<property name="wrap-license">True</property>
|
<property name="wrap-license">True</property>
|
||||||
<property name="license-type">custom</property>
|
<property name="license-type">custom</property>
|
||||||
|
Reference in New Issue
Block a user