Compare commits
23 Commits
d6de2a8a4a
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 53b604e299 | |||
| 5e9354ba58 | |||
| 6a3bfbeb13 | |||
| ad06ea50ad | |||
| 9932f34782 | |||
| 76a4b2e0e2 | |||
| 0942af249b | |||
| 28fefafa6f | |||
| d96ace1504 | |||
| e0723e7b9e | |||
| 2f47614eaf | |||
| a5f864b802 | |||
| 5e9fe86cd6 | |||
| da70244a54 | |||
| c01e81af27 | |||
| 41f39ba8cc | |||
| bebe0c7cba | |||
| 74d53690e2 | |||
| 061dbf19ad | |||
| 1798213bfc | |||
| 6bd4d97db2 | |||
| 7737e3ad6d | |||
| 3c914e64dd |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1,5 @@
|
|||||||
|
cookies.txt
|
||||||
|
|
||||||
docs/
|
docs/
|
||||||
.idea/
|
.idea/
|
||||||
*.zip
|
*.zip
|
||||||
|
|||||||
@@ -2,9 +2,7 @@
|
|||||||
SolarFM is a Gtk+ Python file manager.
|
SolarFM is a Gtk+ Python file manager.
|
||||||
|
|
||||||
# Notes
|
# Notes
|
||||||
<b>Still Work in progress! Use at own risk!</b>
|
If not building a .deb then just move the contents of user_config to their respective folders.
|
||||||
|
|
||||||
Additionally, if not building a .deb then just move the contents of user_config to their respective folders.
|
|
||||||
Copy the share/solarfm folder to your user .config/ directory too.
|
Copy the share/solarfm folder to your user .config/ directory too.
|
||||||
|
|
||||||
`pyrightconfig.json`
|
`pyrightconfig.json`
|
||||||
@@ -17,7 +15,7 @@ sudo apt-get install xclip python3.8 python3-setproctitle python3-gi wget ffmpeg
|
|||||||
|
|
||||||
# Known Issues
|
# Known Issues
|
||||||
<ul>
|
<ul>
|
||||||
<li>The memory leak has now been mostly mitigated in that it doesn't balloon to 2GB'. Still working out where the smaller one(s) is/are.</li>
|
<li>There is a memory leak that has been slowed down but can get to 2GB over a long enough time period OR active accessing image based dirs.</li>
|
||||||
<li>Doing Ctrl+D when in Terminator (maybe other terminals too) somehow propagates the signal to SolarFM too.
|
<li>Doing Ctrl+D when in Terminator (maybe other terminals too) somehow propagates the signal to SolarFM too.
|
||||||
A selected file in the active quad-pane will move to trash since it is the default key-binding for that action.</li>
|
A selected file in the active quad-pane will move to trash since it is the default key-binding for that action.</li>
|
||||||
</ul>
|
</ul>
|
||||||
@@ -25,7 +23,6 @@ A selected file in the active quad-pane will move to trash since it is the defau
|
|||||||
# TODO
|
# TODO
|
||||||
<ul>
|
<ul>
|
||||||
<li>Add simpleish preview plugin for various file types.</li>
|
<li>Add simpleish preview plugin for various file types.</li>
|
||||||
<li>Add simpleish bulk-renamer.</li>
|
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
# Images
|
# Images
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ class IconController(Icon):
|
|||||||
self.BASE_THUMBS_PTH = f"{USER_HOME}/.thumbnails"
|
self.BASE_THUMBS_PTH = f"{USER_HOME}/.thumbnails"
|
||||||
self.ABS_THUMBS_PTH = f"{self.BASE_THUMBS_PTH}/normal"
|
self.ABS_THUMBS_PTH = f"{self.BASE_THUMBS_PTH}/normal"
|
||||||
self.STEAM_ICONS_PTH = f"{self.BASE_THUMBS_PTH}/steam_icons"
|
self.STEAM_ICONS_PTH = f"{self.BASE_THUMBS_PTH}/steam_icons"
|
||||||
|
self.STEAM_CDN_URL = ""
|
||||||
|
|
||||||
if not path.isdir(self.BASE_THUMBS_PTH):
|
if not path.isdir(self.BASE_THUMBS_PTH):
|
||||||
os.mkdir(self.BASE_THUMBS_PTH)
|
os.mkdir(self.BASE_THUMBS_PTH)
|
||||||
@@ -60,6 +61,7 @@ class IconController(Icon):
|
|||||||
self.container_icon_wh = config["container_icon_wh"]
|
self.container_icon_wh = config["container_icon_wh"]
|
||||||
self.video_icon_wh = config["video_icon_wh"]
|
self.video_icon_wh = config["video_icon_wh"]
|
||||||
self.sys_icon_wh = config["sys_icon_wh"]
|
self.sys_icon_wh = config["sys_icon_wh"]
|
||||||
|
self.STEAM_CDN_URL = config["steam_cdn_url"]
|
||||||
|
|
||||||
# Filters
|
# Filters
|
||||||
filters = settings["filters"]
|
filters = settings["filters"]
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ class Icon(DesktopIconMixin, VideoIconMixin, MeshsIconMixin):
|
|||||||
|
|
||||||
if not thumbnl:
|
if not thumbnl:
|
||||||
# TODO: Detect if not in a thread and use directly for speed get_system_thumbnail
|
# TODO: Detect if not in a thread and use directly for speed get_system_thumbnail
|
||||||
thumbnl = self.get_system_thumbnail(full_path, self.sys_icon_wh[0])
|
# thumbnl = self.get_system_thumbnail(full_path, self.sys_icon_wh[0])
|
||||||
# thumbnl = self._get_system_thumbnail_gtk_thread(full_path, self.sys_icon_wh[0])
|
thumbnl = self._get_system_thumbnail_gtk_thread(full_path, self.sys_icon_wh[0])
|
||||||
if not thumbnl:
|
if not thumbnl:
|
||||||
raise IconException("No known icons found.")
|
raise IconException("No known icons found.")
|
||||||
|
|
||||||
@@ -144,6 +144,8 @@ class Icon(DesktopIconMixin, VideoIconMixin, MeshsIconMixin):
|
|||||||
event = threading.Event()
|
event = threading.Event()
|
||||||
GLib.idle_add(_call_gtk_thread, event, result)
|
GLib.idle_add(_call_gtk_thread, event, result)
|
||||||
event.wait()
|
event.wait()
|
||||||
|
|
||||||
|
event = None
|
||||||
return result[0]
|
return result[0]
|
||||||
|
|
||||||
|
|
||||||
@@ -156,6 +158,7 @@ class Icon(DesktopIconMixin, VideoIconMixin, MeshsIconMixin):
|
|||||||
|
|
||||||
if data:
|
if data:
|
||||||
icon_path = data.get_filename()
|
icon_path = data.get_filename()
|
||||||
|
|
||||||
return GdkPixbuf.Pixbuf.new_from_file_at_size(icon_path, width = size, height = size)
|
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...")
|
||||||
|
|||||||
@@ -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:
|
||||||
logger.info(repr(e))
|
logger.info(e)
|
||||||
self.ffprobe_generate_video_thumbnail(full_path, hash_img_path)
|
self.ffprobe_generate_video_thumbnail(full_path, hash_img_path)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,10 @@ class Plugin(PluginBase):
|
|||||||
def run(self):
|
def run(self):
|
||||||
self.icon_controller = IconController()
|
self.icon_controller = IconController()
|
||||||
self._event_system.subscribe("create-thumbnail", self.create_thumbnail)
|
self._event_system.subscribe("create-thumbnail", self.create_thumbnail)
|
||||||
|
self._event_system.subscribe("create-video-thumbnail", self.create_video_thumbnail)
|
||||||
|
self._event_system.subscribe("create-scaled-image", self.create_scaled_image)
|
||||||
|
self._event_system.subscribe("get-thumbnail-hash", self.get_thumbnail_hash)
|
||||||
|
self._event_system.subscribe("get-thumbnails-path", self.get_thumbnails_path)
|
||||||
|
|
||||||
def generate_reference_ui_element(self):
|
def generate_reference_ui_element(self):
|
||||||
...
|
...
|
||||||
@@ -28,6 +32,18 @@ class Plugin(PluginBase):
|
|||||||
def create_thumbnail(self, dir, file) -> str:
|
def create_thumbnail(self, dir, file) -> str:
|
||||||
return self.icon_controller.create_icon(dir, file)
|
return self.icon_controller.create_icon(dir, file)
|
||||||
|
|
||||||
|
def create_video_thumbnail(self, file, scrub_percent, replace):
|
||||||
|
return self.icon_controller.create_video_thumbnail(file, scrub_percent, replace)
|
||||||
|
|
||||||
|
def create_scaled_image(self, hash_img_pth):
|
||||||
|
return self.icon_controller.create_scaled_image(hash_img_pth)
|
||||||
|
|
||||||
|
def get_thumbnail_hash(self, file):
|
||||||
|
return self.icon_controller.generate_hash_and_path(file)
|
||||||
|
|
||||||
|
def get_thumbnails_path(self) -> str:
|
||||||
|
return self.icon_controller.ABS_THUMBS_PTH
|
||||||
|
|
||||||
def get_video_icons(self, dir) -> list:
|
def get_video_icons(self, dir) -> list:
|
||||||
data = []
|
data = []
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ def threaded(fn):
|
|||||||
return wrapper
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
class VODThumbnailerException(Exception):
|
||||||
|
...
|
||||||
|
|
||||||
|
|
||||||
class Plugin(PluginBase):
|
class Plugin(PluginBase):
|
||||||
@@ -94,32 +96,39 @@ class Plugin(PluginBase):
|
|||||||
file = self._file_name.get_text()
|
file = self._file_name.get_text()
|
||||||
dir = self._file_location.get_text()
|
dir = self._file_location.get_text()
|
||||||
file_hash = self._file_hash.get_text()
|
file_hash = self._file_hash.get_text()
|
||||||
hash_img_pth = f"{self._fm_state.tab.ABS_THUMBS_PTH}/{file_hash}.jpg"
|
hash_img_pth = f"{self.ABS_THUMBS_PTH}/{file_hash}.jpg"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self._fm_state.tab.create_video_thumbnail(f"{dir}/{file}", f"{scrub_percent}%", True)
|
self._event_system.emit_and_await("create-video-thumbnail", (f"{dir}/{file}", f"{scrub_percent}%", True,))
|
||||||
preview_pixbuf = GdkPixbuf.Pixbuf.new_from_file(hash_img_pth)
|
preview_pixbuf = GdkPixbuf.Pixbuf.new_from_file(hash_img_pth)
|
||||||
self._thumbnail_preview_img.set_from_pixbuf(preview_pixbuf)
|
self._thumbnail_preview_img.set_from_pixbuf(preview_pixbuf)
|
||||||
|
|
||||||
img_pixbuf = self._fm_state.tab.create_scaled_image(hash_img_pth)
|
img_pixbuf = self._event_system.emit_and_await("create-scaled-image", (hash_img_pth,))
|
||||||
tree_pth = self._fm_state.icon_grid.get_selected_items()[0]
|
tree_pth = self._fm_state.icon_grid.get_selected_items()[0]
|
||||||
itr = self._fm_state.store.get_iter(tree_pth)
|
itr = self._fm_state.store.get_iter(tree_pth)
|
||||||
pixbuff = self._fm_state.store.get(itr, 0)[0]
|
pixbuff = self._fm_state.store.get(itr, 0)[0]
|
||||||
self._fm_state.store.set(itr, 0, img_pixbuf)
|
self._fm_state.store.set(itr, 0, img_pixbuf)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(repr(e))
|
|
||||||
print("Couldn't regenerate thumbnail!")
|
print("Couldn't regenerate thumbnail!")
|
||||||
|
print(repr(e))
|
||||||
|
|
||||||
|
|
||||||
def _set_ui_data(self):
|
def _set_ui_data(self):
|
||||||
uri = self._fm_state.uris[0]
|
uri = self._fm_state.uris[0]
|
||||||
path = self._fm_state.tab.get_current_directory()
|
path = self._fm_state.tab.get_current_directory()
|
||||||
parts = uri.split("/")
|
parts = uri.split("/")
|
||||||
file_hash = self._fm_state.tab.fast_hash(uri)
|
path_exists, \
|
||||||
hash_img_pth = f"{self._fm_state.tab.ABS_THUMBS_PTH}/{file_hash}.jpg"
|
img_hash, \
|
||||||
|
hash_img_pth = self._event_system.emit_and_await("get-thumbnail-hash", (uri,))
|
||||||
|
|
||||||
|
if not path_exists:
|
||||||
|
raise VODThumbnailerException(f"Could not generate file_hash from: {uri}")
|
||||||
|
|
||||||
|
|
||||||
|
self.ABS_THUMBS_PTH = self._event_system.emit_and_await("get-thumbnails-path")
|
||||||
preview_pixbuf = GdkPixbuf.Pixbuf.new_from_file(hash_img_pth)
|
preview_pixbuf = GdkPixbuf.Pixbuf.new_from_file(hash_img_pth)
|
||||||
|
|
||||||
self._thumbnail_preview_img.set_from_pixbuf(preview_pixbuf)
|
self._thumbnail_preview_img.set_from_pixbuf(preview_pixbuf)
|
||||||
self._file_name.set_text(parts[ len(parts) - 1 ])
|
self._file_name.set_text(parts[ len(parts) - 1 ])
|
||||||
self._file_location.set_text(path)
|
self._file_location.set_text(path)
|
||||||
self._file_hash.set_text(file_hash)
|
self._file_hash.set_text(img_hash)
|
||||||
|
|||||||
@@ -47,4 +47,4 @@ class Plugin(PluginBase):
|
|||||||
|
|
||||||
@threaded
|
@threaded
|
||||||
def _download(self, dir):
|
def _download(self, dir):
|
||||||
subprocess.Popen([f'{self.path}/download.sh', dir])
|
subprocess.Popen([f'{self.path}/download.sh', dir], start_new_session=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, close_fds=True)
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ def limit_memory(maxsize):
|
|||||||
|
|
||||||
def main(args, unknownargs):
|
def main(args, unknownargs):
|
||||||
setproctitle(f'{app_name}')
|
setproctitle(f'{app_name}')
|
||||||
# limit_memory(248 * (1024 * 1024 * 42))
|
|
||||||
|
|
||||||
if args.debug == "true":
|
if args.debug == "true":
|
||||||
settings_manager.set_debug(True)
|
settings_manager.set_debug(True)
|
||||||
@@ -34,7 +33,9 @@ def main(args, unknownargs):
|
|||||||
settings_manager.set_trace_debug(True)
|
settings_manager.set_trace_debug(True)
|
||||||
|
|
||||||
settings_manager.do_dirty_start_check()
|
settings_manager.do_dirty_start_check()
|
||||||
Application(args, unknownargs)
|
|
||||||
|
app = Application()
|
||||||
|
app.run()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -52,6 +53,7 @@ if __name__ == "__main__":
|
|||||||
|
|
||||||
# Read arguments (If any...)
|
# Read arguments (If any...)
|
||||||
args, unknownargs = parser.parse_known_args()
|
args, unknownargs = parser.parse_known_args()
|
||||||
|
settings_manager.set_starting_args( args, unknownargs )
|
||||||
|
|
||||||
try:
|
try:
|
||||||
faulthandler.enable() # For better debug info
|
faulthandler.enable() # For better debug info
|
||||||
|
|||||||
@@ -19,38 +19,47 @@ class AppLaunchException(Exception):
|
|||||||
class Application:
|
class Application:
|
||||||
""" docstring for Application. """
|
""" docstring for Application. """
|
||||||
|
|
||||||
def __init__(self, args, unknownargs):
|
def __init__(self):
|
||||||
super(Application, self).__init__()
|
super(Application, self).__init__()
|
||||||
|
|
||||||
if not settings_manager.is_trace_debug():
|
|
||||||
self.load_ipc(args, unknownargs)
|
|
||||||
|
|
||||||
self.setup_debug_hook()
|
self.setup_debug_hook()
|
||||||
Window(args, unknownargs).main()
|
|
||||||
|
|
||||||
|
|
||||||
def load_ipc(self, args, unknownargs):
|
def run(self):
|
||||||
|
if not settings_manager.is_trace_debug():
|
||||||
|
if not self.load_ipc():
|
||||||
|
return
|
||||||
|
|
||||||
|
win = Window()
|
||||||
|
win.start()
|
||||||
|
|
||||||
|
def load_ipc(self):
|
||||||
|
args, \
|
||||||
|
unknownargs = settings_manager.get_starting_args()
|
||||||
ipc_server = IPCServer()
|
ipc_server = IPCServer()
|
||||||
self.ipc_realization_check(ipc_server)
|
|
||||||
|
|
||||||
if not ipc_server.is_ipc_alive:
|
self.ipc_realization_check(ipc_server)
|
||||||
|
if ipc_server.is_ipc_alive:
|
||||||
|
return True
|
||||||
|
|
||||||
|
logger.warning(f"{app_name} IPC Server Exists: Have sent path(s) to it and closing...")
|
||||||
for arg in unknownargs + [args.new_tab,]:
|
for arg in unknownargs + [args.new_tab,]:
|
||||||
if os.path.isfile(arg):
|
if os.path.isfile(arg):
|
||||||
message = f"FILE|{arg}"
|
message = f"FILE|{arg}"
|
||||||
ipc_server.send_ipc_message(message)
|
ipc_server.send_ipc_message(message)
|
||||||
|
|
||||||
raise AppLaunchException(f"{app_name} IPC Server Exists: Have sent path(s) to it and closing...")
|
if os.path.isdir(arg):
|
||||||
|
message = f"DIR|{arg}"
|
||||||
|
ipc_server.send_ipc_message(message)
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def ipc_realization_check(self, ipc_server):
|
def ipc_realization_check(self, ipc_server):
|
||||||
try:
|
|
||||||
ipc_server.create_ipc_listener()
|
|
||||||
except Exception:
|
|
||||||
ipc_server.send_test_ipc_message()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
ipc_server.create_ipc_listener()
|
ipc_server.create_ipc_listener()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
...
|
ipc_server.send_test_ipc_message()
|
||||||
|
|
||||||
def setup_debug_hook(self):
|
def setup_debug_hook(self):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -36,26 +36,14 @@ from .ui_mixin import UIMixin
|
|||||||
class Controller(UIMixin, SignalsMixins, Controller_Data):
|
class Controller(UIMixin, SignalsMixins, Controller_Data):
|
||||||
""" Controller coordinates the mixins and is somewhat the root hub of it all. """
|
""" Controller coordinates the mixins and is somewhat the root hub of it all. """
|
||||||
|
|
||||||
def __init__(self, args, unknownargs):
|
def __init__(self):
|
||||||
self._setup_controller_data()
|
self._setup_controller_data()
|
||||||
|
|
||||||
self._setup_styling()
|
self._setup_styling()
|
||||||
self._setup_signals()
|
self._setup_signals()
|
||||||
self._subscribe_to_events()
|
self._subscribe_to_events()
|
||||||
self._load_widgets()
|
self._load_widgets()
|
||||||
|
self._load_plugins_and_files()
|
||||||
if args.no_plugins == "false":
|
|
||||||
self.plugins_controller.pre_launch_plugins()
|
|
||||||
|
|
||||||
self._generate_file_views(self.fm_controller_data)
|
|
||||||
|
|
||||||
if args.no_plugins == "false":
|
|
||||||
self.plugins_controller.post_launch_plugins()
|
|
||||||
|
|
||||||
for arg in unknownargs + [args.new_tab,]:
|
|
||||||
if os.path.isdir(arg):
|
|
||||||
message = f"FILE|{arg}"
|
|
||||||
event_system.emit("post_file_to_ipc", message)
|
|
||||||
|
|
||||||
|
|
||||||
def _setup_styling(self):
|
def _setup_styling(self):
|
||||||
@@ -69,7 +57,7 @@ class Controller(UIMixin, SignalsMixins, Controller_Data):
|
|||||||
|
|
||||||
def _subscribe_to_events(self):
|
def _subscribe_to_events(self):
|
||||||
event_system.subscribe("shutting_down", self._shutting_down)
|
event_system.subscribe("shutting_down", self._shutting_down)
|
||||||
event_system.subscribe("handle_file_from_ipc", self.handle_file_from_ipc)
|
event_system.subscribe("handle_dir_from_ipc", self.handle_dir_from_ipc)
|
||||||
event_system.subscribe("generate_file_views", self._generate_file_views)
|
event_system.subscribe("generate_file_views", self._generate_file_views)
|
||||||
event_system.subscribe("clear_notebooks", self.clear_notebooks)
|
event_system.subscribe("clear_notebooks", self.clear_notebooks)
|
||||||
|
|
||||||
@@ -113,6 +101,22 @@ class Controller(UIMixin, SignalsMixins, Controller_Data):
|
|||||||
FileExistsWidget()
|
FileExistsWidget()
|
||||||
SaveLoadWidget()
|
SaveLoadWidget()
|
||||||
|
|
||||||
|
def _load_plugins_and_files(self):
|
||||||
|
args, unknownargs = settings_manager.get_starting_args()
|
||||||
|
|
||||||
|
if args.no_plugins == "false":
|
||||||
|
self.plugins_controller.pre_launch_plugins()
|
||||||
|
|
||||||
|
self._generate_file_views(self.fm_controller_data)
|
||||||
|
|
||||||
|
if args.no_plugins == "false":
|
||||||
|
self.plugins_controller.post_launch_plugins()
|
||||||
|
|
||||||
|
for arg in unknownargs + [args.new_tab,]:
|
||||||
|
if os.path.isdir(arg):
|
||||||
|
message = f"FILE|{arg}"
|
||||||
|
event_system.emit("post_file_to_ipc", message)
|
||||||
|
|
||||||
def _shutting_down(self):
|
def _shutting_down(self):
|
||||||
if not settings_manager.is_trace_debug():
|
if not settings_manager.is_trace_debug():
|
||||||
self.fm_controller.save_state()
|
self.fm_controller.save_state()
|
||||||
|
|||||||
@@ -54,6 +54,7 @@ class Controller_Data:
|
|||||||
self.ctrl_down = False
|
self.ctrl_down = False
|
||||||
self.shift_down = False
|
self.shift_down = False
|
||||||
self.alt_down = False
|
self.alt_down = False
|
||||||
|
self.was_midified_key = None
|
||||||
|
|
||||||
self._state = State()
|
self._state = State()
|
||||||
self.message_dialog = MessageWidget()
|
self.message_dialog = MessageWidget()
|
||||||
@@ -88,12 +89,6 @@ class Controller_Data:
|
|||||||
|
|
||||||
state.selected_files = event_system.emit_and_await("get_selected_files")
|
state.selected_files = event_system.emit_and_await("get_selected_files")
|
||||||
|
|
||||||
# if self.to_copy_files:
|
|
||||||
# state.to_copy_files = self.format_to_uris(state.store, state.wid, state.tid, self.to_copy_files, True)
|
|
||||||
#
|
|
||||||
# if self.to_cut_files:
|
|
||||||
# state.to_cut_files = self.format_to_uris(state.store, state.wid, state.tid, self.to_cut_files, True)
|
|
||||||
|
|
||||||
event_system.emit("update_state_info_plugins", state) # NOTE: Need to remove after we convert plugins to use emit_and_await
|
event_system.emit("update_state_info_plugins", state) # NOTE: Need to remove after we convert plugins to use emit_and_await
|
||||||
return state
|
return state
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ class FileActionSignalsMixin:
|
|||||||
if tab.get_dir_watcher():
|
if tab.get_dir_watcher():
|
||||||
watcher = tab.get_dir_watcher()
|
watcher = tab.get_dir_watcher()
|
||||||
watcher.cancel()
|
watcher.cancel()
|
||||||
|
watcher.disconnect(watcher.watch_id)
|
||||||
|
watcher.run_dispose()
|
||||||
if settings_manager.is_debug():
|
if settings_manager.is_debug():
|
||||||
logger.debug(f"Watcher Is Cancelled: {watcher.is_cancelled()}")
|
logger.debug(f"Watcher Is Cancelled: {watcher.is_cancelled()}")
|
||||||
|
|
||||||
@@ -30,8 +32,9 @@ 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}",))
|
watch_id = dir_watcher.connect("changed", self.dir_watch_updates, *(f"{wid}|{tid}",))
|
||||||
tab.set_dir_watcher(dir_watcher)
|
tab.set_dir_watcher(dir_watcher)
|
||||||
|
dir_watcher.watch_id = watch_id
|
||||||
|
|
||||||
def dir_watch_updates(self, file_monitor, file, other_file = None, eve_type = None, tab_widget_id = None):
|
def dir_watch_updates(self, file_monitor, file, other_file = None, eve_type = None, tab_widget_id = None):
|
||||||
if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED,
|
if eve_type in [Gio.FileMonitorEvent.CREATED, Gio.FileMonitorEvent.DELETED,
|
||||||
@@ -44,14 +47,26 @@ class FileActionSignalsMixin:
|
|||||||
if tab_widget_id in self.soft_update_lock:
|
if tab_widget_id in self.soft_update_lock:
|
||||||
timeout_id = self.soft_update_lock[tab_widget_id]["timeout_id"]
|
timeout_id = self.soft_update_lock[tab_widget_id]["timeout_id"]
|
||||||
GLib.source_remove(timeout_id)
|
GLib.source_remove(timeout_id)
|
||||||
|
self.soft_update_lock[tab_widget_id]["call_count"] += 1
|
||||||
|
else:
|
||||||
|
self.soft_update_lock[tab_widget_id] = {}
|
||||||
|
self.soft_update_lock[tab_widget_id]["call_count"] = 0
|
||||||
|
|
||||||
timeout_id = GLib.timeout_add(0, self.update_on_soft_lock_end, 600, *(tab_widget_id,))
|
timeout_id = GLib.timeout_add(
|
||||||
self.soft_update_lock[tab_widget_id] = { "timeout_id": timeout_id }
|
500 if self.soft_update_lock[tab_widget_id]["call_count"] <= 5 else 1000,
|
||||||
|
self.update_on_soft_lock_end,
|
||||||
|
tab_widget_id
|
||||||
|
)
|
||||||
|
|
||||||
|
self.soft_update_lock[tab_widget_id]["timeout_id"] = timeout_id
|
||||||
|
|
||||||
|
|
||||||
def update_on_soft_lock_end(self, timout_ms, tab_widget_id):
|
def update_on_soft_lock_end(self, tab_widget_id):
|
||||||
|
GLib.idle_add(self._update_interface, tab_widget_id)
|
||||||
self.soft_update_lock.pop(tab_widget_id, None)
|
self.soft_update_lock.pop(tab_widget_id, None)
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _update_interface(self, tab_widget_id):
|
||||||
wid, tid = tab_widget_id.split("|")
|
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)
|
||||||
@@ -68,16 +83,6 @@ class FileActionSignalsMixin:
|
|||||||
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:
|
||||||
target = widget.get_name()
|
target = widget.get_name()
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ class IPCSignalsMixin:
|
|||||||
def print_to_console(self, message=None):
|
def print_to_console(self, message=None):
|
||||||
print(message)
|
print(message)
|
||||||
|
|
||||||
def handle_file_from_ipc(self, path):
|
def handle_dir_from_ipc(self, path):
|
||||||
window = self.builder.get_object("main_window")
|
window = self.builder.get_object("main_window")
|
||||||
window.deiconify()
|
window.deiconify()
|
||||||
window.show()
|
window.show()
|
||||||
|
|||||||
@@ -1,135 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
import asyncio
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
|
|
||||||
gi.require_version("Gtk", "3.0")
|
|
||||||
from gi.repository import Gtk
|
|
||||||
from gi.repository import GLib
|
|
||||||
from gi.repository import Gio
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from ...widgets.tab_header_widget import TabHeaderWidget
|
|
||||||
from ...widgets.icon_grid_widget import IconGridWidget
|
|
||||||
from ...widgets.icon_tree_widget import IconTreeWidget
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class GridMixin:
|
|
||||||
"""docstring for GridMixin"""
|
|
||||||
|
|
||||||
def load_store(self, tab, store, save_state = False, use_generator = False):
|
|
||||||
dir = tab.get_current_directory()
|
|
||||||
files = tab.get_files()
|
|
||||||
|
|
||||||
for file in files:
|
|
||||||
store.append([None, file[0]])
|
|
||||||
|
|
||||||
Gtk.main_iteration()
|
|
||||||
self.generate_icons(tab, store, dir, files)
|
|
||||||
|
|
||||||
# NOTE: Not likely called often from here but it could be useful
|
|
||||||
if save_state and not trace_debug:
|
|
||||||
self.fm_controller.save_state()
|
|
||||||
|
|
||||||
dir = None
|
|
||||||
files = None
|
|
||||||
|
|
||||||
def generate_icons(self, tab, store, dir, files):
|
|
||||||
for i, file in enumerate(files):
|
|
||||||
self.make_and_load_icon( i, store, tab, dir, file[0])
|
|
||||||
|
|
||||||
def update_store(self, i, store, icon):
|
|
||||||
itr = store.get_iter(i)
|
|
||||||
GLib.idle_add(self.insert_store, store, itr, icon.copy())
|
|
||||||
itr = None
|
|
||||||
del icon
|
|
||||||
|
|
||||||
@daemon_threaded
|
|
||||||
def make_and_load_icon(self, i, store, tab, dir, file):
|
|
||||||
icon = tab.create_icon(dir, file)
|
|
||||||
GLib.idle_add(self.update_store, i, store, icon)
|
|
||||||
icon = None
|
|
||||||
|
|
||||||
def get_icon(self, tab, dir, file):
|
|
||||||
tab.create_icon(dir, file)
|
|
||||||
|
|
||||||
def insert_store(self, store, itr, icon):
|
|
||||||
store.set_value(itr, 0, icon)
|
|
||||||
|
|
||||||
# Note: If the function returns GLib.SOURCE_REMOVE or False it is automatically removed from the list of event sources and will not be called again.
|
|
||||||
return False
|
|
||||||
|
|
||||||
def do_ui_update(self):
|
|
||||||
Gtk.main_iteration()
|
|
||||||
# Note: If the function returns GLib.SOURCE_REMOVE or False it is automatically removed from the list of event sources and will not be called again.
|
|
||||||
return False
|
|
||||||
|
|
||||||
def create_tab_widget(self):
|
|
||||||
return TabHeaderWidget(self.close_tab)
|
|
||||||
|
|
||||||
def create_scroll_and_store(self, tab, wid, use_tree_view = False):
|
|
||||||
scroll = Gtk.ScrolledWindow()
|
|
||||||
|
|
||||||
if not use_tree_view:
|
|
||||||
grid = self.create_icon_grid_widget()
|
|
||||||
else:
|
|
||||||
# TODO: Fix global logic to make the below work too
|
|
||||||
grid = self.create_icon_tree_widget()
|
|
||||||
|
|
||||||
scroll.add(grid)
|
|
||||||
scroll.set_name(f"{wid}|{tab.get_id()}")
|
|
||||||
grid.set_name(f"{wid}|{tab.get_id()}")
|
|
||||||
self.builder.expose_object(f"{wid}|{tab.get_id()}|icon_grid", grid, use_gtk = False)
|
|
||||||
self.builder.expose_object(f"{wid}|{tab.get_id()}", scroll, use_gtk = False)
|
|
||||||
|
|
||||||
return scroll, grid.get_store()
|
|
||||||
|
|
||||||
def create_icon_grid_widget(self):
|
|
||||||
grid = IconGridWidget()
|
|
||||||
grid._setup_additional_signals(
|
|
||||||
self.grid_icon_single_click,
|
|
||||||
self.grid_icon_double_click,
|
|
||||||
self.grid_set_selected_items,
|
|
||||||
self.grid_on_drag_set,
|
|
||||||
self.grid_on_drag_data_received,
|
|
||||||
self.grid_on_drag_motion
|
|
||||||
)
|
|
||||||
|
|
||||||
return grid
|
|
||||||
|
|
||||||
def create_icon_tree_widget(self):
|
|
||||||
grid = IconTreeWidget()
|
|
||||||
grid._setup_additional_signals(
|
|
||||||
self.grid_icon_single_click,
|
|
||||||
self.grid_icon_double_click,
|
|
||||||
self.grid_on_drag_set,
|
|
||||||
self.grid_on_drag_data_received,
|
|
||||||
self.grid_on_drag_motion
|
|
||||||
)
|
|
||||||
|
|
||||||
grid.columns_autosize()
|
|
||||||
return grid
|
|
||||||
|
|
||||||
def get_store_and_label_from_notebook(self, notebook, _name):
|
|
||||||
icon_grid = None
|
|
||||||
tab_label = None
|
|
||||||
store = None
|
|
||||||
|
|
||||||
for obj in notebook.get_children():
|
|
||||||
icon_grid = obj.get_children()[0]
|
|
||||||
name = icon_grid.get_name()
|
|
||||||
if name == _name:
|
|
||||||
store = icon_grid.get_model()
|
|
||||||
tab_label = notebook.get_tab_label(obj).get_children()[0]
|
|
||||||
|
|
||||||
icon_grid = None
|
|
||||||
return store, tab_label
|
|
||||||
|
|
||||||
def get_icon_grid_from_notebook(self, notebook, _name):
|
|
||||||
for obj in notebook.get_children():
|
|
||||||
icon_grid = obj.get_children()[0]
|
|
||||||
name = icon_grid.get_name()
|
|
||||||
if name == _name:
|
|
||||||
return icon_grid
|
|
||||||
@@ -1,315 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
import os
|
|
||||||
import gc
|
|
||||||
import time
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
gi.require_version('Gtk', '3.0')
|
|
||||||
from gi.repository import Gtk
|
|
||||||
from gi.repository import GLib
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from .grid_mixin import GridMixin
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TabMixin(GridMixin):
|
|
||||||
"""docstring for TabMixin"""
|
|
||||||
|
|
||||||
def create_tab(self, wid: int = None, tid: int = None, path: str = None):
|
|
||||||
if not wid:
|
|
||||||
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
|
||||||
|
|
||||||
notebook = self.builder.get_object(f"window_{wid}")
|
|
||||||
path_entry = self.builder.get_object(f"path_entry")
|
|
||||||
tab = self.fm_controller.add_tab_for_window_by_nickname(f"window_{wid}")
|
|
||||||
tab.logger = logger
|
|
||||||
|
|
||||||
tab.set_wid(wid)
|
|
||||||
if not path:
|
|
||||||
if wid and tid:
|
|
||||||
_tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
|
||||||
tab.set_path(_tab.get_current_directory())
|
|
||||||
else:
|
|
||||||
tab.set_path(path)
|
|
||||||
|
|
||||||
tab_widget = self.get_tab_widget(tab)
|
|
||||||
scroll, store = self.create_scroll_and_store(tab, wid)
|
|
||||||
index = notebook.append_page(scroll, tab_widget)
|
|
||||||
notebook.set_tab_detachable(scroll, True)
|
|
||||||
notebook.set_tab_reorderable(scroll, True)
|
|
||||||
|
|
||||||
self.fm_controller.set_wid_and_tid(wid, tab.get_id())
|
|
||||||
# path_entry.set_text(tab.get_current_directory())
|
|
||||||
event_system.emit("go_to_path", (tab.get_current_directory(),)) # NOTE: Not efficent if I understand how
|
|
||||||
notebook.show_all()
|
|
||||||
notebook.set_current_page(index)
|
|
||||||
|
|
||||||
ctx = notebook.get_style_context()
|
|
||||||
ctx.add_class("notebook-unselected-focus")
|
|
||||||
self.load_store(tab, store)
|
|
||||||
event_system.emit("set_window_title", (tab.get_current_directory(),))
|
|
||||||
self.set_file_watcher(tab)
|
|
||||||
|
|
||||||
tab_widget = None
|
|
||||||
scroll, store = None, None
|
|
||||||
index = None
|
|
||||||
notebook = None
|
|
||||||
path_entry = None
|
|
||||||
tab = None
|
|
||||||
ctx = None
|
|
||||||
|
|
||||||
|
|
||||||
def get_tab_widget(self, tab):
|
|
||||||
tab_widget = self.create_tab_widget()
|
|
||||||
tab_widget.tab = tab
|
|
||||||
|
|
||||||
tab_widget.label.set_label(f"{tab.get_end_of_path()}")
|
|
||||||
tab_widget.label.set_width_chars(len(tab.get_end_of_path()))
|
|
||||||
|
|
||||||
return tab_widget
|
|
||||||
|
|
||||||
def close_tab(self, button, eve = None):
|
|
||||||
notebook = button.get_parent().get_parent()
|
|
||||||
if notebook.get_n_pages() == 1:
|
|
||||||
notebook = None
|
|
||||||
return
|
|
||||||
|
|
||||||
tab_box = button.get_parent()
|
|
||||||
wid = int(notebook.get_name()[-1])
|
|
||||||
tid = self.get_id_from_tab_box(tab_box)
|
|
||||||
scroll = self.builder.get_object(f"{wid}|{tid}", use_gtk = False)
|
|
||||||
icon_grid = scroll.get_children()[0]
|
|
||||||
store = icon_grid.get_store()
|
|
||||||
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)
|
|
||||||
|
|
||||||
self.builder.dereference_object(f"{wid}|{tid}|icon_grid")
|
|
||||||
self.builder.dereference_object(f"{wid}|{tid}")
|
|
||||||
|
|
||||||
iter = store.get_iter_first()
|
|
||||||
while iter:
|
|
||||||
next_iter = store.iter_next(iter)
|
|
||||||
store.unref_node(iter)
|
|
||||||
iter = next_iter
|
|
||||||
|
|
||||||
store.clear()
|
|
||||||
store.run_dispose()
|
|
||||||
|
|
||||||
icon_grid.set_model(None)
|
|
||||||
icon_grid.run_dispose()
|
|
||||||
scroll.run_dispose()
|
|
||||||
tab_box.run_dispose()
|
|
||||||
|
|
||||||
iter = None
|
|
||||||
wid, tid = None, None
|
|
||||||
store = None
|
|
||||||
icon_grid = None
|
|
||||||
scroll = None
|
|
||||||
tab_box = None
|
|
||||||
watcher = None
|
|
||||||
tab = None
|
|
||||||
notebook = None
|
|
||||||
|
|
||||||
if not settings_manager.is_trace_debug():
|
|
||||||
self.fm_controller.save_state()
|
|
||||||
|
|
||||||
self.set_window_title()
|
|
||||||
gc.collect()
|
|
||||||
|
|
||||||
# NOTE: Not actually getting called even tho set in the glade file...
|
|
||||||
def on_tab_dnded(self, notebook, page, x, y):
|
|
||||||
...
|
|
||||||
|
|
||||||
def on_tab_reorder(self, child, page_num, new_index):
|
|
||||||
wid, tid = page_num.get_name().split("|")
|
|
||||||
window = self.get_fm_window(wid)
|
|
||||||
tab = None
|
|
||||||
|
|
||||||
for i, tab in enumerate(window.get_all_tabs()):
|
|
||||||
if tab.get_id() == tid:
|
|
||||||
_tab = window.get_tab_by_id(tid)
|
|
||||||
watcher = _tab.get_dir_watcher()
|
|
||||||
watcher.cancel()
|
|
||||||
window.get_all_tabs().insert(new_index, window.get_all_tabs().pop(i))
|
|
||||||
|
|
||||||
tab = window.get_tab_by_id(tid)
|
|
||||||
self.set_file_watcher(tab)
|
|
||||||
if not settings_manager.is_trace_debug():
|
|
||||||
self.fm_controller.save_state()
|
|
||||||
|
|
||||||
wid, tid = None, None
|
|
||||||
window = None
|
|
||||||
tab = None
|
|
||||||
|
|
||||||
|
|
||||||
def on_tab_switch_update(self, notebook, content = None, index = None):
|
|
||||||
self.selected_files.clear()
|
|
||||||
wid, tid = content.get_children()[0].get_name().split("|")
|
|
||||||
|
|
||||||
self.fm_controller.set_wid_and_tid(wid, tid)
|
|
||||||
self.set_path_text(wid, tid)
|
|
||||||
self.set_window_title()
|
|
||||||
|
|
||||||
wid, tid = None, None
|
|
||||||
|
|
||||||
def get_id_from_tab_box(self, tab_box):
|
|
||||||
return tab_box.tab.get_id()
|
|
||||||
|
|
||||||
def get_tab_label(self, notebook, icon_grid):
|
|
||||||
return notebook.get_tab_label(icon_grid.get_parent()).get_children()[0]
|
|
||||||
|
|
||||||
def get_tab_close(self, notebook, icon_grid):
|
|
||||||
return notebook.get_tab_label(icon_grid.get_parent()).get_children()[1]
|
|
||||||
|
|
||||||
def get_tab_icon_grid_from_notebook(self, notebook):
|
|
||||||
return notebook.get_children()[1].get_children()[0]
|
|
||||||
|
|
||||||
def refresh_tab(data = None):
|
|
||||||
state = self.get_current_state()
|
|
||||||
state.tab.load_directory()
|
|
||||||
self.load_store(state.tab, state.store)
|
|
||||||
|
|
||||||
state = None
|
|
||||||
|
|
||||||
def update_tab(self, tab_label, tab, store, wid, tid):
|
|
||||||
self.load_store(tab, store)
|
|
||||||
self.set_path_text(wid, tid)
|
|
||||||
|
|
||||||
char_width = len(tab.get_end_of_path())
|
|
||||||
tab_label.set_width_chars(char_width)
|
|
||||||
tab_label.set_label(tab.get_end_of_path())
|
|
||||||
self.set_window_title()
|
|
||||||
self.set_file_watcher(tab)
|
|
||||||
if not settings_manager.is_trace_debug():
|
|
||||||
self.fm_controller.save_state()
|
|
||||||
|
|
||||||
def do_action_from_bar_controls(self, widget, eve = None):
|
|
||||||
action = widget.get_name()
|
|
||||||
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
|
||||||
notebook = self.builder.get_object(f"window_{wid}")
|
|
||||||
store, tab_label = self.get_store_and_label_from_notebook(notebook, f"{wid}|{tid}")
|
|
||||||
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
|
||||||
|
|
||||||
if action == "create_tab":
|
|
||||||
dir = tab.get_current_directory()
|
|
||||||
self.create_tab(wid, None, dir)
|
|
||||||
if not settings_manager.is_trace_debug():
|
|
||||||
self.fm_controller.save_state()
|
|
||||||
|
|
||||||
return
|
|
||||||
if action == "go_up":
|
|
||||||
tab.pop_from_path()
|
|
||||||
if action == "go_home":
|
|
||||||
tab.set_to_home()
|
|
||||||
if action == "refresh_tab":
|
|
||||||
tab.load_directory()
|
|
||||||
if action == "path_entry":
|
|
||||||
focused_obj = self.window.get_focus()
|
|
||||||
dir = f"{tab.get_current_directory()}/"
|
|
||||||
path = widget.get_text()
|
|
||||||
|
|
||||||
if isinstance(focused_obj, Gtk.Entry):
|
|
||||||
self.process_path_menu(widget, tab, dir)
|
|
||||||
|
|
||||||
action = None
|
|
||||||
store = None
|
|
||||||
|
|
||||||
if path.endswith(".") or path == dir:
|
|
||||||
tab_label = None
|
|
||||||
notebook = None
|
|
||||||
wid, tid = None, None
|
|
||||||
path = None
|
|
||||||
tab = None
|
|
||||||
return
|
|
||||||
|
|
||||||
if not tab.set_path(path):
|
|
||||||
tab_label = None
|
|
||||||
notebook = None
|
|
||||||
wid, tid = None, None
|
|
||||||
path = None
|
|
||||||
tab = None
|
|
||||||
return
|
|
||||||
|
|
||||||
icon_grid = self.get_icon_grid_from_notebook(notebook, f"{wid}|{tid}")
|
|
||||||
icon_grid.clear_and_set_new_store()
|
|
||||||
self.update_tab(tab_label, tab, icon_grid.get_store(), wid, tid)
|
|
||||||
|
|
||||||
action = None
|
|
||||||
wid, tid = None, None
|
|
||||||
notebook = None
|
|
||||||
store, tab_label = None, None
|
|
||||||
path = None
|
|
||||||
tab = None
|
|
||||||
icon_grid = None
|
|
||||||
|
|
||||||
|
|
||||||
def process_path_menu(self, gtk_entry, tab, dir):
|
|
||||||
path_menu_buttons = self.builder.get_object("path_menu_buttons")
|
|
||||||
query = gtk_entry.get_text().replace(dir, "")
|
|
||||||
files = tab.get_files() + tab.get_hidden()
|
|
||||||
|
|
||||||
self.clear_children(path_menu_buttons)
|
|
||||||
show_path_menu = False
|
|
||||||
for file, hash, size in files:
|
|
||||||
if os.path.isdir(f"{dir}{file}"):
|
|
||||||
if query.lower() in file.lower():
|
|
||||||
button = Gtk.Button(label=file)
|
|
||||||
button.show()
|
|
||||||
button.connect("clicked", self.set_path_entry)
|
|
||||||
path_menu_buttons.add(button)
|
|
||||||
show_path_menu = True
|
|
||||||
|
|
||||||
query = None
|
|
||||||
files = None
|
|
||||||
|
|
||||||
if not show_path_menu:
|
|
||||||
path_menu_buttons = None
|
|
||||||
event_system.emit("hide_path_menu")
|
|
||||||
else:
|
|
||||||
event_system.emit("show_path_menu")
|
|
||||||
buttons = path_menu_buttons.get_children()
|
|
||||||
path_menu_buttons = None
|
|
||||||
|
|
||||||
if len(buttons) == 1:
|
|
||||||
self.slowed_focus(buttons[0])
|
|
||||||
|
|
||||||
@daemon_threaded
|
|
||||||
def slowed_focus(self, button):
|
|
||||||
time.sleep(0.05)
|
|
||||||
GLib.idle_add(self.do_focused_click, *(button,))
|
|
||||||
|
|
||||||
def do_focused_click(self, button):
|
|
||||||
button.grab_focus()
|
|
||||||
button.clicked()
|
|
||||||
return False
|
|
||||||
|
|
||||||
def set_path_entry(self, button = None, eve = None):
|
|
||||||
self.path_auto_filled = True
|
|
||||||
state = self.get_current_state()
|
|
||||||
path = f"{state.tab.get_current_directory()}/{button.get_label()}"
|
|
||||||
path_entry = self.builder.get_object("path_entry")
|
|
||||||
|
|
||||||
path_entry.set_text(path)
|
|
||||||
path_entry.grab_focus_without_selecting()
|
|
||||||
path_entry.set_position(-1)
|
|
||||||
event_system.emit("hide_path_menu")
|
|
||||||
|
|
||||||
state = None
|
|
||||||
path = None
|
|
||||||
path_entry = None
|
|
||||||
|
|
||||||
|
|
||||||
def show_hide_hidden_files(self):
|
|
||||||
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
|
||||||
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
|
||||||
tab.set_hiding_hidden(not tab.is_hiding_hidden())
|
|
||||||
tab.load_directory()
|
|
||||||
self.builder.get_object("refresh_tab").released()
|
|
||||||
|
|
||||||
wid, tid = None, None
|
|
||||||
tab = None
|
|
||||||
@@ -1,204 +0,0 @@
|
|||||||
# Python imports
|
|
||||||
import copy
|
|
||||||
import traceback
|
|
||||||
from os.path import isdir
|
|
||||||
|
|
||||||
# Lib imports
|
|
||||||
import gi
|
|
||||||
gi.require_version('Gtk', '3.0')
|
|
||||||
gi.require_version('Gdk', '3.0')
|
|
||||||
from gi.repository import Gtk
|
|
||||||
from gi.repository import Gdk
|
|
||||||
from gi.repository import Gio
|
|
||||||
|
|
||||||
# Application imports
|
|
||||||
from .tab_mixin import TabMixin
|
|
||||||
|
|
||||||
|
|
||||||
class WindowException(Exception):
|
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
class WindowMixin(TabMixin):
|
|
||||||
"""docstring for WindowMixin"""
|
|
||||||
|
|
||||||
def get_fm_window(self, wid):
|
|
||||||
return self.fm_controller.get_window_by_nickname(f"window_{wid}")
|
|
||||||
|
|
||||||
def set_bottom_labels(self, tab):
|
|
||||||
event_system.emit("set_bottom_labels", (tab,))
|
|
||||||
|
|
||||||
def set_window_title(self):
|
|
||||||
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
|
||||||
notebook = self.builder.get_object(f"window_{wid}")
|
|
||||||
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
|
||||||
dir = tab.get_current_directory()
|
|
||||||
|
|
||||||
for _notebook in self.notebooks:
|
|
||||||
ctx = _notebook.get_style_context()
|
|
||||||
ctx.remove_class("notebook-selected-focus")
|
|
||||||
ctx.add_class("notebook-unselected-focus")
|
|
||||||
|
|
||||||
ctx = notebook.get_style_context()
|
|
||||||
ctx.remove_class("notebook-unselected-focus")
|
|
||||||
ctx.add_class("notebook-selected-focus")
|
|
||||||
|
|
||||||
self.window.set_title(f"{app_name} ~ {dir}")
|
|
||||||
self.set_bottom_labels(tab)
|
|
||||||
|
|
||||||
wid, tid = None, None
|
|
||||||
notebook = None
|
|
||||||
tab = None
|
|
||||||
dir = None
|
|
||||||
|
|
||||||
def set_path_text(self, wid, tid):
|
|
||||||
path_entry = self.builder.get_object("path_entry")
|
|
||||||
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
|
||||||
path_entry.set_text(tab.get_current_directory())
|
|
||||||
|
|
||||||
path_entry = None
|
|
||||||
tab = None
|
|
||||||
|
|
||||||
def grid_set_selected_items(self, icons_grid):
|
|
||||||
new_items = icons_grid.get_selected_items()
|
|
||||||
items_size = len(new_items)
|
|
||||||
selected_items = event_system.emit_and_await("get_selected_files")
|
|
||||||
|
|
||||||
if items_size == 1:
|
|
||||||
# NOTE: If already in selection, likely dnd else not so wont readd
|
|
||||||
if new_items[0] in selected_items:
|
|
||||||
self.dnd_left_primed += 1
|
|
||||||
# NOTE: If in selection but trying to just select an already selected item.
|
|
||||||
if self.dnd_left_primed > 1:
|
|
||||||
self.dnd_left_primed = 0
|
|
||||||
selected_items.clear()
|
|
||||||
|
|
||||||
# NOTE: Likely trying dnd, just readd to selection the former set.
|
|
||||||
# Prevents losing highlighting of grid selected.
|
|
||||||
for path in selected_items:
|
|
||||||
icons_grid.select_path(path)
|
|
||||||
|
|
||||||
if items_size > 0:
|
|
||||||
event_system.emit("set_selected_files", (new_items,))
|
|
||||||
else:
|
|
||||||
self.dnd_left_primed = 0
|
|
||||||
selected_items.clear()
|
|
||||||
|
|
||||||
def grid_icon_single_click(self, icons_grid, eve):
|
|
||||||
try:
|
|
||||||
event_system.emit("hide_path_menu")
|
|
||||||
wid, tid = icons_grid.get_name().split("|")
|
|
||||||
self.fm_controller.set_wid_and_tid(wid, tid)
|
|
||||||
self.set_path_text(wid, tid)
|
|
||||||
self.set_window_title()
|
|
||||||
|
|
||||||
if eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 1: # l-click
|
|
||||||
if eve.state & Gdk.ModifierType.CONTROL_MASK:
|
|
||||||
self.dnd_left_primed = 0
|
|
||||||
|
|
||||||
if self.single_click_open: # FIXME: need to find a way to pass the model index
|
|
||||||
self.grid_icon_double_click(icons_grid)
|
|
||||||
elif eve.type == Gdk.EventType.BUTTON_RELEASE and eve.button == 3: # r-click
|
|
||||||
event_system.emit("show_context_menu")
|
|
||||||
|
|
||||||
except WindowException as e:
|
|
||||||
logger.info(repr(e))
|
|
||||||
self.display_message(settings.theming.error_color, f"{repr(e)}")
|
|
||||||
|
|
||||||
def grid_icon_double_click(self, icons_grid, item, data=None):
|
|
||||||
try:
|
|
||||||
if self.ctrl_down and self.shift_down:
|
|
||||||
self.unset_keys_and_data()
|
|
||||||
self.execute_files(in_terminal=True)
|
|
||||||
return
|
|
||||||
elif self.ctrl_down:
|
|
||||||
self.unset_keys_and_data()
|
|
||||||
self.execute_files()
|
|
||||||
return
|
|
||||||
|
|
||||||
state = self.get_current_state()
|
|
||||||
notebook = self.builder.get_object(f"window_{state.wid}")
|
|
||||||
tab_label = self.get_tab_label(notebook, state.icon_grid)
|
|
||||||
|
|
||||||
fileName = state.store[item][1]
|
|
||||||
dir = state.tab.get_current_directory()
|
|
||||||
file = f"{dir}/{fileName}"
|
|
||||||
|
|
||||||
if isdir(file):
|
|
||||||
state.tab.set_path(file)
|
|
||||||
state.icon_grid.clear_and_set_new_store()
|
|
||||||
self.update_tab(tab_label, state.tab, state.icon_grid.get_store(), state.wid, state.tid)
|
|
||||||
else:
|
|
||||||
event_system.emit("open_files")
|
|
||||||
|
|
||||||
state = None
|
|
||||||
notebook = None
|
|
||||||
tab_label = None
|
|
||||||
except WindowException as e:
|
|
||||||
traceback.print_exc()
|
|
||||||
self.display_message(settings.theming.error_color, f"{repr(e)}")
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def grid_on_drag_set(self, icons_grid, drag_context, data, info, time):
|
|
||||||
action = icons_grid.get_name()
|
|
||||||
wid, tid = action.split("|")
|
|
||||||
store = icons_grid.get_model()
|
|
||||||
treePaths = icons_grid.get_selected_items()
|
|
||||||
# NOTE: Need URIs as URI format for DnD to work. Will strip 'file://'
|
|
||||||
# further down call chain when doing internal fm stuff.
|
|
||||||
uris = self.format_to_uris(store, wid, tid, treePaths)
|
|
||||||
uris_text = '\n'.join(uris)
|
|
||||||
|
|
||||||
data.set_uris(uris)
|
|
||||||
data.set_text(uris_text, -1)
|
|
||||||
|
|
||||||
def grid_on_drag_motion(self, icons_grid, drag_context, x, y, data):
|
|
||||||
current = '|'.join(self.fm_controller.get_active_wid_and_tid())
|
|
||||||
target = icons_grid.get_name()
|
|
||||||
wid, tid = target.split("|")
|
|
||||||
store = icons_grid.get_model()
|
|
||||||
path_at_loc = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
data = icons_grid.get_dest_item_at_pos(x, y)
|
|
||||||
path_at_loc = data[0]
|
|
||||||
drop_position = data[1]
|
|
||||||
highlighted_item_path = icons_grid.get_drag_dest_item().path
|
|
||||||
if path_at_loc and path_at_loc == highlighted_item_path and drop_position == Gtk.IconViewDropPosition.DROP_INTO:
|
|
||||||
uri = self.format_to_uris(store, wid, tid, highlighted_item_path)[0].replace("file://", "")
|
|
||||||
self.override_drop_dest = uri if isdir(uri) else None
|
|
||||||
else:
|
|
||||||
self.override_drop_dest = None
|
|
||||||
except Exception as e:
|
|
||||||
self.override_drop_dest = None
|
|
||||||
|
|
||||||
if target not in current:
|
|
||||||
self.fm_controller.set_wid_and_tid(wid, tid)
|
|
||||||
|
|
||||||
current = None
|
|
||||||
target = None
|
|
||||||
wid, tid = None, None
|
|
||||||
store = None
|
|
||||||
path_at_loc = None
|
|
||||||
|
|
||||||
|
|
||||||
def grid_on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
|
|
||||||
if info == 80:
|
|
||||||
uris = data.get_uris()
|
|
||||||
state = event_system.emit_and_await("get_current_state")
|
|
||||||
dest = f"{state.tab.get_current_directory()}" if not self.override_drop_dest else self.override_drop_dest
|
|
||||||
if len(uris) == 0:
|
|
||||||
uris = data.get_text().split("\n")
|
|
||||||
|
|
||||||
from_uri = '/'.join(uris[0].replace("file://", "").split("/")[:-1])
|
|
||||||
if from_uri != dest:
|
|
||||||
event_system.emit("move_files", (uris, dest))
|
|
||||||
|
|
||||||
Gtk.drag_finish(drag_context, True, False, time)
|
|
||||||
return
|
|
||||||
|
|
||||||
Gtk.drag_finish(drag_context, False, False, time)
|
|
||||||
|
|
||||||
def create_new_tab_notebook(self, widget=None, wid=None, path=None):
|
|
||||||
self.create_tab(wid, None, path)
|
|
||||||
@@ -10,7 +10,7 @@ from gi.repository import GLib
|
|||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from .mixins.ui.pane_mixin import PaneMixin
|
from .mixins.ui.pane_mixin import PaneMixin
|
||||||
from .mixins.ui.window_mixin import WindowMixin
|
from .widgets.files_view.window_mixin import WindowMixin
|
||||||
|
|
||||||
from .widgets.files_view.files_widget import FilesWidget
|
from .widgets.files_view.files_widget import FilesWidget
|
||||||
|
|
||||||
|
|||||||
@@ -64,16 +64,56 @@ class RenameWidget:
|
|||||||
|
|
||||||
|
|
||||||
def set_to_title_case(self, widget, eve=None):
|
def set_to_title_case(self, widget, eve=None):
|
||||||
self._rename_fname.set_text( self._rename_fname.get_text().title() )
|
bounds = self._rename_fname.get_selection_bounds()
|
||||||
|
fname = self._rename_fname.get_text()
|
||||||
|
|
||||||
|
if bounds:
|
||||||
|
start, end = bounds
|
||||||
|
replace = fname[start:end]
|
||||||
|
fname = fname.replace(replace, replace.title())
|
||||||
|
else:
|
||||||
|
fname = fname.title()
|
||||||
|
|
||||||
|
self._rename_fname.set_text(fname)
|
||||||
|
|
||||||
def set_to_upper_case(self, widget, eve=None):
|
def set_to_upper_case(self, widget, eve=None):
|
||||||
self._rename_fname.set_text( self._rename_fname.get_text().upper() )
|
bounds = self._rename_fname.get_selection_bounds()
|
||||||
|
fname = self._rename_fname.get_text()
|
||||||
|
|
||||||
|
if bounds:
|
||||||
|
start, end = bounds
|
||||||
|
replace = fname[start:end]
|
||||||
|
fname = fname.replace(replace, replace.upper())
|
||||||
|
else:
|
||||||
|
fname = fname.upper()
|
||||||
|
|
||||||
|
self._rename_fname.set_text(fname)
|
||||||
|
|
||||||
def set_to_lower_case(self, widget, eve=None):
|
def set_to_lower_case(self, widget, eve=None):
|
||||||
self._rename_fname.set_text( self._rename_fname.get_text().lower() )
|
bounds = self._rename_fname.get_selection_bounds()
|
||||||
|
fname = self._rename_fname.get_text()
|
||||||
|
|
||||||
|
if bounds:
|
||||||
|
start, end = bounds
|
||||||
|
replace = fname[start:end]
|
||||||
|
fname = fname.replace(replace, replace.lower())
|
||||||
|
else:
|
||||||
|
fname = fname.lower()
|
||||||
|
|
||||||
|
self._rename_fname.set_text(fname)
|
||||||
|
|
||||||
def set_to_invert_case(self, widget, eve=None):
|
def set_to_invert_case(self, widget, eve=None):
|
||||||
self._rename_fname.set_text( self._rename_fname.get_text().swapcase() )
|
bounds = self._rename_fname.get_selection_bounds()
|
||||||
|
fname = self._rename_fname.get_text()
|
||||||
|
|
||||||
|
if bounds:
|
||||||
|
start, end = bounds
|
||||||
|
replace = fname[start:end]
|
||||||
|
fname = fname.replace(replace, replace.swapcase())
|
||||||
|
else:
|
||||||
|
fname = fname.swapcase()
|
||||||
|
|
||||||
|
self._rename_fname.set_text(fname)
|
||||||
|
|
||||||
def hide_rename_file_menu(self, widget=None, eve=None):
|
def hide_rename_file_menu(self, widget=None, eve=None):
|
||||||
self._rename_file_menu.hide()
|
self._rename_file_menu.hide()
|
||||||
|
|||||||
@@ -20,81 +20,41 @@ 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()
|
||||||
|
|
||||||
for file in files:
|
for file in files:
|
||||||
store.append([None, file[0]])
|
store.append([None, file[0]])
|
||||||
|
|
||||||
Gtk.main_iteration()
|
self.load_icons(tab, store, dir, files, self.update_store)
|
||||||
self.generate_icons(tab, store, dir, files)
|
|
||||||
# GLib.Thread("", self.generate_icons, tab, store, dir, files)
|
|
||||||
|
|
||||||
# NOTE: Not likely called often from here but it could be useful
|
# 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()
|
||||||
|
|
||||||
dir = None
|
|
||||||
files = None
|
|
||||||
|
|
||||||
@daemon_threaded
|
@daemon_threaded
|
||||||
def generate_icons(self, tab, store, dir, files):
|
def load_icons(self, tab, store, dir, files, callback):
|
||||||
for i, file in enumerate(files):
|
icons = []
|
||||||
# GLib.Thread(f"{i}", self.make_and_load_icon, i, store, tab, dir, file[0])
|
for file in files:
|
||||||
self.make_and_load_icon( i, store, tab, dir, file[0])
|
icons.append(
|
||||||
|
tab.create_icon(dir, file[0])
|
||||||
|
)
|
||||||
|
|
||||||
def update_store(self, i, store, icon):
|
GLib.idle_add(callback, store, icons)
|
||||||
|
|
||||||
|
def update_store(self, store, icons):
|
||||||
|
for i, icon in enumerate(icons):
|
||||||
|
try:
|
||||||
itr = store.get_iter(i)
|
itr = store.get_iter(i)
|
||||||
GLib.idle_add(self.insert_store, store, itr, icon)
|
store.set_value(itr, 0, icon)
|
||||||
itr = None
|
icon.run_dispose()
|
||||||
|
except:
|
||||||
@daemon_threaded
|
icon.run_dispose()
|
||||||
def make_and_load_icon(self, i, store, tab, dir, file):
|
continue
|
||||||
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)
|
|
||||||
|
|
||||||
|
store.run_dispose()
|
||||||
|
del icons
|
||||||
|
del store
|
||||||
|
|
||||||
def insert_store(self, store, itr, icon):
|
def insert_store(self, store, itr, icon):
|
||||||
store.set_value(itr, 0, icon)
|
store.set_value(itr, 0, icon)
|
||||||
@@ -104,6 +64,7 @@ class GridMixin:
|
|||||||
|
|
||||||
def do_ui_update(self):
|
def do_ui_update(self):
|
||||||
Gtk.main_iteration()
|
Gtk.main_iteration()
|
||||||
|
# Note: If the function returns GLib.SOURCE_REMOVE or False it is automatically removed from the list of event sources and will not be called again.
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def create_tab_widget(self):
|
def create_tab_widget(self):
|
||||||
@@ -164,6 +125,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):
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
# Python imports
|
# Python imports
|
||||||
import os
|
import os
|
||||||
import gc
|
|
||||||
import time
|
import time
|
||||||
|
import gc
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
import gi
|
import gi
|
||||||
@@ -22,9 +22,9 @@ class TabMixin(GridMixin):
|
|||||||
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
wid, tid = self.fm_controller.get_active_wid_and_tid()
|
||||||
|
|
||||||
notebook = self.builder.get_object(f"window_{wid}")
|
notebook = self.builder.get_object(f"window_{wid}")
|
||||||
# path_entry = self.builder.get_object(f"path_entry")
|
path_entry = self.builder.get_object(f"path_entry")
|
||||||
tab = self.fm_controller.add_tab_for_window_by_nickname(f"window_{wid}")
|
tab = self.fm_controller.add_tab_for_window_by_nickname(f"window_{wid}")
|
||||||
# tab.logger = logger
|
tab.logger = logger
|
||||||
|
|
||||||
tab.set_wid(wid)
|
tab.set_wid(wid)
|
||||||
if not path:
|
if not path:
|
||||||
@@ -41,8 +41,8 @@ class TabMixin(GridMixin):
|
|||||||
notebook.set_tab_reorderable(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
|
# 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)
|
||||||
|
|
||||||
@@ -52,18 +52,10 @@ class TabMixin(GridMixin):
|
|||||||
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):
|
def get_tab_widget(self, tab):
|
||||||
tab_widget = self.create_tab_widget()
|
tab_widget = self.create_tab_widget()
|
||||||
tab_widget.tab_id = tab.get_id()
|
tab_widget.tab = tab
|
||||||
|
|
||||||
tab_widget.label.set_label(f"{tab.get_end_of_path()}")
|
tab_widget.label.set_label(f"{tab.get_end_of_path()}")
|
||||||
tab_widget.label.set_width_chars(len(tab.get_end_of_path()))
|
tab_widget.label.set_width_chars(len(tab.get_end_of_path()))
|
||||||
@@ -72,59 +64,59 @@ class TabMixin(GridMixin):
|
|||||||
|
|
||||||
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: return
|
||||||
notebook = None
|
|
||||||
return
|
|
||||||
|
|
||||||
tab_box = button.get_parent()
|
tab_box = button.get_parent()
|
||||||
wid = int(notebook.get_name()[-1])
|
wid = int(notebook.get_name()[-1])
|
||||||
tid = self.get_id_from_tab_box(tab_box)
|
tid = self.get_id_from_tab_box(tab_box)
|
||||||
scroll = self.builder.get_object(f"{wid}|{tid}", use_gtk = False)
|
scroll = self.builder.get_object(f"{wid}|{tid}", use_gtk = False)
|
||||||
icon_grid = scroll.get_children()[0]
|
icon_grid = scroll.get_children()[0]
|
||||||
store = icon_grid.get_model()
|
|
||||||
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
||||||
watcher = tab.get_dir_watcher()
|
watcher = tab.get_dir_watcher()
|
||||||
|
|
||||||
watcher.cancel()
|
watcher.cancel()
|
||||||
|
watcher.disconnect(watcher.watch_id)
|
||||||
|
watcher.run_dispose()
|
||||||
self.get_fm_window(wid).delete_tab_by_id(tid)
|
self.get_fm_window(wid).delete_tab_by_id(tid)
|
||||||
|
|
||||||
|
logger.debug(f"Reference count for watcher is: {watcher.__grefcount__}")
|
||||||
|
logger.debug(f"Reference count for tab_box is: {tab_box.__grefcount__}")
|
||||||
|
logger.debug(f"Reference count for icon_grid is: {icon_grid.__grefcount__}")
|
||||||
|
logger.debug(f"Reference count for scroll is: {scroll.__grefcount__}")
|
||||||
|
|
||||||
|
tab_box.clear_signals_and_data()
|
||||||
|
icon_grid.clear_signals_and_data()
|
||||||
|
|
||||||
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()
|
notebook.remove_page( notebook.page_num(scroll) )
|
||||||
while iter:
|
|
||||||
next_iter = store.iter_next(iter)
|
|
||||||
store.unref_node(iter)
|
|
||||||
iter = next_iter
|
|
||||||
|
|
||||||
store.clear()
|
tab_box.tab = None
|
||||||
store.run_dispose()
|
tab_box.unparent()
|
||||||
|
tab_box.run_dispose()
|
||||||
icon_grid.set_model(None)
|
|
||||||
icon_grid.run_dispose()
|
icon_grid.run_dispose()
|
||||||
scroll.run_dispose()
|
scroll.run_dispose()
|
||||||
tab_box.run_dispose()
|
icon_grid.unparent()
|
||||||
|
scroll.unparent()
|
||||||
|
|
||||||
iter = None
|
logger.debug(f"Reference count for tab_box is: {tab_box.__grefcount__}")
|
||||||
wid, tid = None, None
|
logger.debug(f"Reference count for icon_grid is: {icon_grid.__grefcount__}")
|
||||||
store = None
|
logger.debug(f"Reference count for scroll is: {scroll.__grefcount__}")
|
||||||
icon_grid = None
|
|
||||||
scroll = None
|
del tab_box
|
||||||
tab_box = None
|
del icon_grid
|
||||||
watcher = None
|
del scroll
|
||||||
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...
|
|
||||||
def on_tab_dnded(self, notebook, page, x, y):
|
def on_tab_dnded(self, notebook, page, x, y):
|
||||||
...
|
logger.info("Create new window on tab dnd outside stub...")
|
||||||
|
|
||||||
def on_tab_reorder(self, child, page_num, new_index):
|
def on_tab_reorder(self, child, page_num, new_index):
|
||||||
wid, tid = page_num.get_name().split("|")
|
wid, tid = page_num.get_name().split("|")
|
||||||
@@ -136,6 +128,8 @@ class TabMixin(GridMixin):
|
|||||||
_tab = window.get_tab_by_id(tid)
|
_tab = window.get_tab_by_id(tid)
|
||||||
watcher = _tab.get_dir_watcher()
|
watcher = _tab.get_dir_watcher()
|
||||||
watcher.cancel()
|
watcher.cancel()
|
||||||
|
watcher.disconnect(watcher.watch_id)
|
||||||
|
watcher.run_dispose()
|
||||||
window.get_all_tabs().insert(new_index, window.get_all_tabs().pop(i))
|
window.get_all_tabs().insert(new_index, window.get_all_tabs().pop(i))
|
||||||
|
|
||||||
tab = window.get_tab_by_id(tid)
|
tab = window.get_tab_by_id(tid)
|
||||||
@@ -143,20 +137,14 @@ 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].tab.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.tab.get_id()
|
return tab_box.tab.get_id()
|
||||||
|
|
||||||
@@ -237,7 +225,7 @@ class TabMixin(GridMixin):
|
|||||||
|
|
||||||
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, icon_grid.get_store(), wid, tid)
|
||||||
|
|
||||||
action = None
|
action = None
|
||||||
wid, tid = None, None
|
wid, tid = None, None
|
||||||
@@ -264,15 +252,16 @@ 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
|
query = None
|
||||||
files = None
|
files = None
|
||||||
|
|
||||||
if not show_path_menu:
|
if not show_path_menu:
|
||||||
|
path_menu_buttons = None
|
||||||
event_system.emit("hide_path_menu")
|
event_system.emit("hide_path_menu")
|
||||||
else:
|
else:
|
||||||
event_system.emit("show_path_menu")
|
event_system.emit("show_path_menu")
|
||||||
buttons = path_menu_buttons.get_children()
|
buttons = path_menu_buttons.get_children()
|
||||||
|
path_menu_buttons = None
|
||||||
|
|
||||||
if len(buttons) == 1:
|
if len(buttons) == 1:
|
||||||
self.slowed_focus(buttons[0])
|
self.slowed_focus(buttons[0])
|
||||||
@@ -285,7 +274,6 @@ 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
|
return False
|
||||||
|
|
||||||
def set_path_entry(self, button = None, eve = None):
|
def set_path_entry(self, button = None, eve = None):
|
||||||
@@ -303,6 +291,7 @@ class TabMixin(GridMixin):
|
|||||||
path = None
|
path = None
|
||||||
path_entry = 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)
|
||||||
|
|||||||
@@ -34,12 +34,16 @@ class WindowMixin(TabMixin):
|
|||||||
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
tab = self.get_fm_window(wid).get_tab_by_id(tid)
|
||||||
dir = tab.get_current_directory()
|
dir = tab.get_current_directory()
|
||||||
|
|
||||||
event_system.emit("unset_selected_files_views")
|
for _notebook in self.notebooks:
|
||||||
ctx = self.files_view.get_style_context()
|
ctx = _notebook.get_style_context()
|
||||||
|
ctx.remove_class("notebook-selected-focus")
|
||||||
|
ctx.add_class("notebook-unselected-focus")
|
||||||
|
|
||||||
|
ctx = notebook.get_style_context()
|
||||||
ctx.remove_class("notebook-unselected-focus")
|
ctx.remove_class("notebook-unselected-focus")
|
||||||
ctx.add_class("notebook-selected-focus")
|
ctx.add_class("notebook-selected-focus")
|
||||||
|
|
||||||
event_system.emit("set_window_title", (dir,))
|
self.window.set_title(f"{app_name} ~ {dir}")
|
||||||
self.set_bottom_labels(tab)
|
self.set_bottom_labels(tab)
|
||||||
|
|
||||||
wid, tid = None, None
|
wid, tid = None, None
|
||||||
@@ -48,9 +52,12 @@ class WindowMixin(TabMixin):
|
|||||||
dir = 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")
|
||||||
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(),))
|
path_entry.set_text(tab.get_current_directory())
|
||||||
|
path_entry.set_position(-1)
|
||||||
|
|
||||||
|
path_entry = None
|
||||||
tab = None
|
tab = None
|
||||||
|
|
||||||
def grid_set_selected_items(self, icons_grid):
|
def grid_set_selected_items(self, icons_grid):
|
||||||
@@ -110,7 +117,6 @@ class WindowMixin(TabMixin):
|
|||||||
self.execute_files()
|
self.execute_files()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
state = self.get_current_state()
|
state = self.get_current_state()
|
||||||
notebook = self.builder.get_object(f"window_{state.wid}")
|
notebook = self.builder.get_object(f"window_{state.wid}")
|
||||||
tab_label = self.get_tab_label(notebook, state.icon_grid)
|
tab_label = self.get_tab_label(notebook, state.icon_grid)
|
||||||
@@ -125,6 +131,10 @@ class WindowMixin(TabMixin):
|
|||||||
self.update_tab(tab_label, state.tab, state.icon_grid.get_store(), state.wid, state.tid)
|
self.update_tab(tab_label, state.tab, state.icon_grid.get_store(), state.wid, state.tid)
|
||||||
else:
|
else:
|
||||||
event_system.emit("open_files")
|
event_system.emit("open_files")
|
||||||
|
|
||||||
|
state = None
|
||||||
|
notebook = None
|
||||||
|
tab_label = None
|
||||||
except WindowException as e:
|
except WindowException as e:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
self.display_message(settings.theming.error_color, f"{repr(e)}")
|
self.display_message(settings.theming.error_color, f"{repr(e)}")
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
# Python imports
|
# Python imports
|
||||||
|
import gc
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
import gi
|
import gi
|
||||||
@@ -8,8 +9,8 @@ from gi.repository import Gtk
|
|||||||
from gi.repository import Gdk
|
from gi.repository import Gdk
|
||||||
from gi.repository import GdkPixbuf
|
from gi.repository import GdkPixbuf
|
||||||
|
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
|
# from utils.cbindings import gtkmemreaper
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -20,6 +21,8 @@ class IconGridWidget(Gtk.IconView):
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
super(IconGridWidget, self).__init__()
|
super(IconGridWidget, self).__init__()
|
||||||
|
|
||||||
|
self._handler_ids = []
|
||||||
|
|
||||||
self._setup_styling()
|
self._setup_styling()
|
||||||
self._setup_signals()
|
self._setup_signals()
|
||||||
self._set_up_dnd()
|
self._set_up_dnd()
|
||||||
@@ -45,19 +48,23 @@ class IconGridWidget(Gtk.IconView):
|
|||||||
def _setup_signals(self):
|
def _setup_signals(self):
|
||||||
...
|
...
|
||||||
|
|
||||||
def _setup_additional_signals(self, grid_icon_single_click,
|
def _setup_additional_signals(self,
|
||||||
|
grid_icon_single_click,
|
||||||
grid_icon_double_click,
|
grid_icon_double_click,
|
||||||
grid_set_selected_items,
|
grid_set_selected_items,
|
||||||
grid_on_drag_set,
|
grid_on_drag_set,
|
||||||
grid_on_drag_data_received,
|
grid_on_drag_data_received,
|
||||||
grid_on_drag_motion):
|
grid_on_drag_motion
|
||||||
|
):
|
||||||
|
|
||||||
self.connect("button_release_event", grid_icon_single_click)
|
self._handler_ids = [
|
||||||
self.connect("item-activated", grid_icon_double_click)
|
self.connect("button_release_event", grid_icon_single_click),
|
||||||
self.connect("selection-changed", grid_set_selected_items)
|
self.connect("item-activated", grid_icon_double_click),
|
||||||
self.connect("drag-data-get", grid_on_drag_set)
|
self.connect("selection-changed", grid_set_selected_items),
|
||||||
self.connect("drag-data-received", grid_on_drag_data_received)
|
self.connect("drag-data-get", grid_on_drag_set),
|
||||||
|
self.connect("drag-data-received", grid_on_drag_data_received),
|
||||||
self.connect("drag-motion", grid_on_drag_motion)
|
self.connect("drag-motion", grid_on_drag_motion)
|
||||||
|
]
|
||||||
|
|
||||||
def _load_widgets(self):
|
def _load_widgets(self):
|
||||||
self.clear_and_set_new_store()
|
self.clear_and_set_new_store()
|
||||||
@@ -75,20 +82,49 @@ 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):
|
||||||
|
self._clear_store()
|
||||||
|
self.set_model(
|
||||||
|
Gtk.ListStore(
|
||||||
|
GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str or None
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
def clear_signals_and_data(self):
|
||||||
|
self.unset_model_drag_dest()
|
||||||
|
self.unset_model_drag_source()
|
||||||
|
|
||||||
|
for handle_id in self._handler_ids:
|
||||||
|
self.disconnect(handle_id)
|
||||||
|
|
||||||
|
self._handler_ids.clear()
|
||||||
|
self._clear_store()
|
||||||
|
|
||||||
|
def _clear_store(self):
|
||||||
store = self.get_model()
|
store = self.get_model()
|
||||||
if store:
|
self.set_model(None)
|
||||||
|
|
||||||
|
if not store: return
|
||||||
|
|
||||||
iter = store.get_iter_first()
|
iter = store.get_iter_first()
|
||||||
while iter:
|
while iter:
|
||||||
next_iter = store.iter_next(iter)
|
icon = store.get_value(iter, 0)
|
||||||
store.unref_node(iter)
|
|
||||||
iter = next_iter
|
store.set_value(iter, 0, None)
|
||||||
|
store.set_value(iter, 1, None)
|
||||||
|
|
||||||
|
iter = store.iter_next(iter)
|
||||||
|
|
||||||
|
if icon:
|
||||||
|
logger.debug(f"Reference count for icon is: {icon.__grefcount__}")
|
||||||
|
icon.run_dispose()
|
||||||
|
# icon_ptr = int(hash(icon)) # WARNING: not stable across runs
|
||||||
|
# gtkmemreaper.free_pixbuf(icon_ptr)
|
||||||
|
del icon
|
||||||
|
|
||||||
store.clear()
|
store.clear()
|
||||||
store.run_dispose()
|
store.run_dispose()
|
||||||
store = None
|
# store_ptr = int(hash(store)) # WARNING: not stable across runs
|
||||||
|
# gtkmemreaper.free_list_store(store)
|
||||||
|
del store
|
||||||
|
|
||||||
self.set_model(None)
|
gc.collect()
|
||||||
store = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str or None)
|
|
||||||
# store = Gtk.ListStore(Gtk.DirectoryList)
|
|
||||||
self.set_model(store)
|
|
||||||
store = None
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ class TabHeaderWidget(Gtk.Box):
|
|||||||
def __init__(self, close_tab):
|
def __init__(self, close_tab):
|
||||||
super(TabHeaderWidget, self).__init__()
|
super(TabHeaderWidget, self).__init__()
|
||||||
|
|
||||||
self._close_tab = close_tab # NOTE: Close method in tab_mixin
|
self._close_tab = close_tab # NOTE: Close method is from tab_mixin
|
||||||
|
|
||||||
self._setup_styling()
|
self._setup_styling()
|
||||||
self._setup_signals()
|
self._setup_signals()
|
||||||
@@ -31,18 +31,27 @@ class TabHeaderWidget(Gtk.Box):
|
|||||||
|
|
||||||
def _load_widgets(self):
|
def _load_widgets(self):
|
||||||
self.label = Gtk.Label()
|
self.label = Gtk.Label()
|
||||||
close = Gtk.Button()
|
self.close_btn = Gtk.Button()
|
||||||
icon = Gtk.Image(stock=Gtk.STOCK_CLOSE)
|
icon = Gtk.Image(stock = Gtk.STOCK_CLOSE)
|
||||||
|
|
||||||
self.label.set_xalign(0.0)
|
self.label.set_xalign(0.0)
|
||||||
self.label.set_margin_left(25)
|
self.label.set_margin_left(25)
|
||||||
self.label.set_margin_right(25)
|
self.label.set_margin_right(25)
|
||||||
self.label.set_hexpand(True)
|
self.label.set_hexpand(True)
|
||||||
|
|
||||||
close.connect("released", self._close_tab)
|
self._handler_id = self.close_btn.connect("released", self._close_tab)
|
||||||
|
|
||||||
close.add(icon)
|
self.close_btn.add(icon)
|
||||||
self.add(self.label)
|
self.add(self.label)
|
||||||
self.add(close)
|
self.add(self.close_btn)
|
||||||
|
|
||||||
self.show_all()
|
self.show_all()
|
||||||
|
|
||||||
|
def clear_signals_and_data(self):
|
||||||
|
self.close_btn.disconnect(self._handler_id)
|
||||||
|
self._close_tab = None
|
||||||
|
self._handler_id = None
|
||||||
|
|
||||||
|
for child in self.get_children():
|
||||||
|
child.unparent()
|
||||||
|
child.run_dispose()
|
||||||
|
|||||||
@@ -22,9 +22,8 @@ class ControllerStartException(Exception):
|
|||||||
class Window(Gtk.ApplicationWindow):
|
class Window(Gtk.ApplicationWindow):
|
||||||
"""docstring for Window."""
|
"""docstring for Window."""
|
||||||
|
|
||||||
def __init__(self, args, unknownargs):
|
def __init__(self):
|
||||||
Gtk.ApplicationWindow.__init__(self)
|
Gtk.ApplicationWindow.__init__(self)
|
||||||
# super(Window, self).__init__()
|
|
||||||
|
|
||||||
settings_manager.set_main_window(self)
|
settings_manager.set_main_window(self)
|
||||||
|
|
||||||
@@ -33,7 +32,7 @@ class Window(Gtk.ApplicationWindow):
|
|||||||
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()
|
||||||
|
|
||||||
self._set_window_data()
|
self._set_window_data()
|
||||||
self._set_size_constraints()
|
self._set_size_constraints()
|
||||||
@@ -56,11 +55,11 @@ class Window(Gtk.ApplicationWindow):
|
|||||||
event_system.subscribe("tear_down", self._tear_down)
|
event_system.subscribe("tear_down", self._tear_down)
|
||||||
event_system.subscribe("load_interactive_debug", self._load_interactive_debug)
|
event_system.subscribe("load_interactive_debug", self._load_interactive_debug)
|
||||||
|
|
||||||
def _load_widgets(self, args, unknownargs):
|
def _load_widgets(self):
|
||||||
if settings_manager.is_debug():
|
if settings_manager.is_debug():
|
||||||
self.set_interactive_debugging(True)
|
self.set_interactive_debugging(True)
|
||||||
|
|
||||||
self._controller = Controller(args, unknownargs)
|
self._controller = Controller()
|
||||||
|
|
||||||
if not self._controller:
|
if not self._controller:
|
||||||
raise ControllerStartException("Controller exited and doesn't exist...")
|
raise ControllerStartException("Controller exited and doesn't exist...")
|
||||||
@@ -120,5 +119,5 @@ class Window(Gtk.ApplicationWindow):
|
|||||||
settings_manager.clear_pid()
|
settings_manager.clear_pid()
|
||||||
Gtk.main_quit()
|
Gtk.main_quit()
|
||||||
|
|
||||||
def main(self):
|
def start(self):
|
||||||
Gtk.main()
|
Gtk.main()
|
||||||
@@ -17,6 +17,7 @@ class Launcher:
|
|||||||
lowerName = file.lower()
|
lowerName = file.lower()
|
||||||
command = []
|
command = []
|
||||||
|
|
||||||
|
if self.use_defined_launchers:
|
||||||
if lowerName.endswith(self.fvideos):
|
if lowerName.endswith(self.fvideos):
|
||||||
command = [self.media_app, file]
|
command = [self.media_app, file]
|
||||||
elif lowerName.endswith(self.fimages):
|
elif lowerName.endswith(self.fimages):
|
||||||
@@ -35,6 +36,8 @@ class Launcher:
|
|||||||
command = [self.file_manager_app, file]
|
command = [self.file_manager_app, file]
|
||||||
else:
|
else:
|
||||||
command = ["xdg-open", file]
|
command = ["xdg-open", file]
|
||||||
|
else:
|
||||||
|
command = ["xdg-open", file]
|
||||||
|
|
||||||
self.execute(command)
|
self.execute(command)
|
||||||
|
|
||||||
@@ -42,7 +45,7 @@ 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:
|
||||||
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=subprocess.DEVNULL, stderr=None, close_fds=True)
|
||||||
except ShellFMLauncherException as e:
|
except ShellFMLauncherException as e:
|
||||||
logger.error(f"Couldn't execute: {command}")
|
logger.error(f"Couldn't execute: {command}")
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
@@ -50,8 +53,7 @@ class Launcher:
|
|||||||
# 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):
|
||||||
try:
|
try:
|
||||||
DEVNULL = open(os.devnull, 'w')
|
return subprocess.Popen(command, cwd=start_dir, shell=use_shell, start_new_session=False, stdout=subprocess.DEVNULL, stderr=None, 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:
|
||||||
logger.error(f"Couldn't execute and return thread: {command}")
|
logger.error(f"Couldn't execute and return thread: {command}")
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
@@ -80,7 +82,7 @@ class Launcher:
|
|||||||
|
|
||||||
command += [remux_vid_pth]
|
command += [remux_vid_pth]
|
||||||
try:
|
try:
|
||||||
proc = subprocess.Popen(command)
|
proc = subprocess.Popen(command, stdout=subprocess.DEVNULL, stderr=None)
|
||||||
proc.wait()
|
proc.wait()
|
||||||
except ShellFMLauncherException as e:
|
except ShellFMLauncherException as e:
|
||||||
logger.error(message)
|
logger.error(message)
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ class Settings:
|
|||||||
lock_folder = False if config["lock_folder"] in ["false", ""] else True
|
lock_folder = False if config["lock_folder"] in ["false", ""] else True
|
||||||
locked_folders = config["locked_folders"].split("::::")
|
locked_folders = config["locked_folders"].split("::::")
|
||||||
mplayer_options = config["mplayer_options"].split()
|
mplayer_options = config["mplayer_options"].split()
|
||||||
|
use_defined_launchers = config["use_defined_launchers"]
|
||||||
music_app = config["music_app"]
|
music_app = config["music_app"]
|
||||||
media_app = config["media_app"]
|
media_app = config["media_app"]
|
||||||
image_app = config["image_app"]
|
image_app = config["image_app"]
|
||||||
|
|||||||
0
src/solarfm/utils/cbindings/__init__.py
Normal file
0
src/solarfm/utils/cbindings/__init__.py
Normal file
20
src/solarfm/utils/cbindings/gtkmemreaper.py
Normal file
20
src/solarfm/utils/cbindings/gtkmemreaper.py
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Python imports
|
||||||
|
import pkg_resources
|
||||||
|
import importlib.util
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def __bootstrap__():
|
||||||
|
__file__ = pkg_resources.resource_filename(__name__, 'gtkmemreaper.cpython-313-x86_64-linux-gnu.so')
|
||||||
|
spec = importlib.util.spec_from_file_location(
|
||||||
|
__name__,
|
||||||
|
__file__
|
||||||
|
)
|
||||||
|
mod = importlib.util.module_from_spec(spec)
|
||||||
|
spec.loader.exec_module(mod)
|
||||||
|
|
||||||
|
__bootstrap__()
|
||||||
15
src/solarfm/utils/cbindings/python_package_works/compile.sh
Executable file
15
src/solarfm/utils/cbindings/python_package_works/compile.sh
Executable file
@@ -0,0 +1,15 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# . CONFIG.sh
|
||||||
|
|
||||||
|
# set -o xtrace ## To debug scripts
|
||||||
|
# set -o errexit ## To exit on error
|
||||||
|
# set -o errunset ## To exit if a variable is referenced but not set
|
||||||
|
|
||||||
|
function main() {
|
||||||
|
cd "$(dirname "")"
|
||||||
|
echo "Working Dir: " $(pwd)
|
||||||
|
|
||||||
|
python3 setup.py build && python3 setup.py install --user
|
||||||
|
}
|
||||||
|
main "$@";
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
#include <Python.h>
|
||||||
|
#include <gtk/gtk.h>
|
||||||
|
#include <cairo.h>
|
||||||
|
#include <gdk-pixbuf/gdk-pixbuf.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
|
||||||
|
// static PyObject* free_pixbuf(PyObject* self, PyObject* args) {
|
||||||
|
static PyObject* free_pixbuf(PyObject* self, PyObject* args) {
|
||||||
|
PyObject *py_pixbuf;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args, "O", &py_pixbuf)) {
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
GdkPixbuf *pixbuf = (GdkPixbuf *) PyLong_AsVoidPtr(py_pixbuf);
|
||||||
|
if (!GDK_IS_PIXBUF(pixbuf)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "Invalid GdkPixbuf pointer.");
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_object_unref(pixbuf);
|
||||||
|
g_assert_null(pixbuf);
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static PyObject* free_list_store(PyObject* self, PyObject* args) {
|
||||||
|
PyObject *py_list_store;
|
||||||
|
|
||||||
|
if (!PyArg_ParseTuple(args, "O", &py_list_store)) {
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
GtkListStore *list_store = (GtkListStore *) PyLong_AsVoidPtr(py_list_store);
|
||||||
|
if (!GTK_IS_LIST_STORE(list_store)) {
|
||||||
|
PyErr_SetString(PyExc_TypeError, "Invalid Gtk.ListStore pointer.");
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
g_object_unref(list_store);
|
||||||
|
g_assert_null(list_store);
|
||||||
|
Py_RETURN_NONE;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static PyMethodDef Methods[] = {
|
||||||
|
{"free_pixbuf", free_pixbuf, METH_VARARGS, "Clear GdkPixbuf* ."},
|
||||||
|
{"free_list_store", free_list_store, METH_VARARGS, "Clear GtkListStore* ."},
|
||||||
|
{NULL, NULL, 0, NULL}
|
||||||
|
};
|
||||||
|
|
||||||
|
static struct PyModuleDef moduledef = {
|
||||||
|
PyModuleDef_HEAD_INIT,
|
||||||
|
"gtkmemreaper",
|
||||||
|
NULL,
|
||||||
|
-1,
|
||||||
|
Methods
|
||||||
|
};
|
||||||
|
|
||||||
|
PyMODINIT_FUNC PyInit_gtkmemreaper(void) {
|
||||||
|
return PyModule_Create(&moduledef);
|
||||||
|
}
|
||||||
28
src/solarfm/utils/cbindings/python_package_works/setup.py
Normal file
28
src/solarfm/utils/cbindings/python_package_works/setup.py
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Python imports
|
||||||
|
from setuptools import setup, Extension
|
||||||
|
from subprocess import check_output
|
||||||
|
|
||||||
|
# Lib imports
|
||||||
|
|
||||||
|
# Application imports
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
pkg_config_args = ["gdk-pixbuf-2.0", "cairo", "gtk+-3.0"]
|
||||||
|
|
||||||
|
def get_pkgconfig_flags(flag_type):
|
||||||
|
return check_output(["pkg-config", flag_type] + pkg_config_args).decode().split()
|
||||||
|
|
||||||
|
ext = Extension(
|
||||||
|
"gtkmemreaper",
|
||||||
|
sources = ["gtkmemreaper.c"],
|
||||||
|
include_dirs = [],
|
||||||
|
extra_compile_args = get_pkgconfig_flags("--cflags"),
|
||||||
|
extra_link_args = get_pkgconfig_flags("--libs")
|
||||||
|
)
|
||||||
|
|
||||||
|
setup(
|
||||||
|
name = "gtkmemreaper",
|
||||||
|
version = "0.1",
|
||||||
|
ext_modules = [ext]
|
||||||
|
)
|
||||||
@@ -5,7 +5,6 @@ from multiprocessing.connection import Client
|
|||||||
from multiprocessing.connection import Listener
|
from multiprocessing.connection import Listener
|
||||||
|
|
||||||
# Lib imports
|
# Lib imports
|
||||||
from gi.repository import GLib
|
|
||||||
|
|
||||||
# Application imports
|
# Application imports
|
||||||
from .singleton import Singleton
|
from .singleton import Singleton
|
||||||
@@ -16,7 +15,7 @@ class IPCServer(Singleton):
|
|||||||
""" Create a listener so that other SolarFM instances send requests back to existing instance. """
|
""" Create a listener so that other SolarFM instances send requests back to existing instance. """
|
||||||
def __init__(self, ipc_address: str = '127.0.0.1', conn_type: str = "socket"):
|
def __init__(self, ipc_address: str = '127.0.0.1', conn_type: str = "socket"):
|
||||||
self.is_ipc_alive = False
|
self.is_ipc_alive = False
|
||||||
self._ipc_port = 4848
|
self._ipc_port = 0 # Use 0 to let Listener chose port
|
||||||
self._ipc_address = ipc_address
|
self._ipc_address = ipc_address
|
||||||
self._conn_type = conn_type
|
self._conn_type = conn_type
|
||||||
self._ipc_authkey = b'' + bytes(f'{app_name}-ipc', 'utf-8')
|
self._ipc_authkey = b'' + bytes(f'{app_name}-ipc', 'utf-8')
|
||||||
@@ -43,30 +42,29 @@ class IPCServer(Singleton):
|
|||||||
if os.path.exists(self._ipc_address) and settings_manager.is_dirty_start():
|
if os.path.exists(self._ipc_address) and settings_manager.is_dirty_start():
|
||||||
os.unlink(self._ipc_address)
|
os.unlink(self._ipc_address)
|
||||||
|
|
||||||
listener = Listener(address = self._ipc_address, family = "AF_UNIX", authkey = self._ipc_authkey)
|
listener = Listener(self._ipc_address, family = "AF_UNIX", authkey = self._ipc_authkey)
|
||||||
|
|
||||||
elif "unsecured" not in self._conn_type:
|
elif "unsecured" not in self._conn_type:
|
||||||
listener = Listener((self._ipc_address, self._ipc_port), authkey = self._ipc_authkey)
|
listener = Listener((self._ipc_address, self._ipc_port), authkey = self._ipc_authkey)
|
||||||
else:
|
else:
|
||||||
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,))
|
self._handle_ipc_message(conn, start_time)
|
||||||
|
except EOFError as e:
|
||||||
conn = None
|
logger.debug( repr(e) )
|
||||||
start_time = None
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.debug( repr(e) )
|
logger.debug( repr(e) )
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
listener.close()
|
listener.close()
|
||||||
|
|
||||||
@@ -80,27 +78,28 @@ class IPCServer(Singleton):
|
|||||||
if file:
|
if file:
|
||||||
event_system.emit_and_await("handle_file_from_ipc", file)
|
event_system.emit_and_await("handle_file_from_ipc", file)
|
||||||
|
|
||||||
msg = None
|
conn.close()
|
||||||
file = None
|
break
|
||||||
|
|
||||||
|
if "DIR|" in msg:
|
||||||
|
file = msg.split("DIR|")[1].strip()
|
||||||
|
if file:
|
||||||
|
event_system.emit_and_await("handle_dir_from_ipc", file)
|
||||||
|
|
||||||
conn.close()
|
conn.close()
|
||||||
break
|
break
|
||||||
|
|
||||||
|
|
||||||
if msg in ['close connection', 'close server']:
|
if msg in ['close connection', 'close server', 'Empty Data...']:
|
||||||
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:
|
||||||
|
|||||||
@@ -97,7 +97,8 @@ class SettingsManager(StartCheckMixin, Singleton):
|
|||||||
self._trace_debug = False
|
self._trace_debug = False
|
||||||
self._debug = False
|
self._debug = False
|
||||||
self._dirty_start = False
|
self._dirty_start = False
|
||||||
|
self._passed_in_file: bool = False
|
||||||
|
self._starting_files: list = []
|
||||||
|
|
||||||
def register_signals_to_builder(self, classes=None, builder=None):
|
def register_signals_to_builder(self, classes=None, builder=None):
|
||||||
handlers = {}
|
handlers = {}
|
||||||
@@ -143,6 +144,12 @@ class SettingsManager(StartCheckMixin, Singleton):
|
|||||||
def get_trash_info_path(self) -> str: return self._TRASH_INFO_PATH
|
def get_trash_info_path(self) -> str: return self._TRASH_INFO_PATH
|
||||||
def get_plugins_path(self) -> str: return self._PLUGINS_PATH
|
def get_plugins_path(self) -> str: return self._PLUGINS_PATH
|
||||||
|
|
||||||
|
def get_starting_args(self):
|
||||||
|
return self.args, self.unknownargs
|
||||||
|
|
||||||
|
def set_is_starting_with_file(self, is_passed_in_file: bool = False):
|
||||||
|
self._passed_in_file = is_passed_in_file
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
@@ -153,6 +160,10 @@ class SettingsManager(StartCheckMixin, Singleton):
|
|||||||
def set_main_window_min_width(self, width = 720): self.settings.config.main_window_min_width = width
|
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_main_window_min_height(self, height = 480): self.settings.config.main_window_min_height = height
|
||||||
|
|
||||||
|
def set_starting_args(self, args, unknownargs):
|
||||||
|
self.args = args
|
||||||
|
self.unknownargs = unknownargs
|
||||||
|
|
||||||
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
|
||||||
|
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ class Config:
|
|||||||
lock_folder: str = "false"
|
lock_folder: str = "false"
|
||||||
locked_folders: list = field(default_factory=lambda: ["venv", "flasks"])
|
locked_folders: list = field(default_factory=lambda: ["venv", "flasks"])
|
||||||
mplayer_options: str = "-quiet -really-quiet -xy 1600 -geometry 50%:50%"
|
mplayer_options: str = "-quiet -really-quiet -xy 1600 -geometry 50%:50%"
|
||||||
|
use_defined_launchers: bool = False
|
||||||
music_app: str = "deadbeef"
|
music_app: str = "deadbeef"
|
||||||
media_app: str = "mpv"
|
media_app: str = "mpv"
|
||||||
image_app: str = "mirage"
|
image_app: str = "mirage"
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ class StartCheckMixin:
|
|||||||
pid = f.readline().strip()
|
pid = f.readline().strip()
|
||||||
if pid not in ("", None):
|
if pid not in ("", None):
|
||||||
if self.is_pid_alive( int(pid) ):
|
if self.is_pid_alive( int(pid) ):
|
||||||
print("PID file exists and PID is alive... Letting downstream errors (sans debug args) handle app closure propigation.")
|
print("PID file exists and PID is alive...")
|
||||||
return
|
return
|
||||||
|
|
||||||
self._write_new_pid()
|
self._write_new_pid()
|
||||||
|
|||||||
@@ -27,15 +27,90 @@
|
|||||||
"/usr/share/applications"
|
"/usr/share/applications"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"filters": {
|
"filters":{
|
||||||
"meshs": [".dae", ".fbx", ".gltf", ".obj", ".stl"],
|
"meshs":[
|
||||||
"code": [".cpp", ".css", ".c", ".go", ".html", ".htm", ".java", ".js", ".json", ".lua", ".md", ".py", ".rs", ".toml", ".xml", ".pom"],
|
".dae",
|
||||||
"videos": [".mkv", ".mp4", ".webm", ".avi", ".mov", ".m4v", ".mpg", ".mpeg", ".wmv", ".flv"],
|
".fbx",
|
||||||
"office": [".doc", ".docx", ".xls", ".xlsx", ".xlt", ".xltx", ".xlm", ".ppt", ".pptx", ".pps", ".ppsx", ".odt", ".rtf"],
|
".gltf",
|
||||||
"images": [".png", ".jpg", ".jpeg", ".gif", ".ico", ".tga", ".webp"],
|
".obj",
|
||||||
"text": [".txt", ".text", ".sh", ".cfg", ".conf", ".log"],
|
".stl"
|
||||||
"music": [".psf", ".mp3", ".ogg", ".flac", ".m4a"],
|
],
|
||||||
"pdf": [".pdf"]
|
"code":[
|
||||||
|
".cpp",
|
||||||
|
".css",
|
||||||
|
".c",
|
||||||
|
".go",
|
||||||
|
".html",
|
||||||
|
".htm",
|
||||||
|
".java",
|
||||||
|
".js",
|
||||||
|
".ts",
|
||||||
|
".json",
|
||||||
|
".lua",
|
||||||
|
".md",
|
||||||
|
".py",
|
||||||
|
".rs",
|
||||||
|
".toml",
|
||||||
|
".xml",
|
||||||
|
".pom"
|
||||||
|
],
|
||||||
|
"videos":[
|
||||||
|
".mkv",
|
||||||
|
".mp4",
|
||||||
|
".webm",
|
||||||
|
".avi",
|
||||||
|
".mov",
|
||||||
|
".m4v",
|
||||||
|
".mpg",
|
||||||
|
".mpeg",
|
||||||
|
".wmv",
|
||||||
|
".flv",
|
||||||
|
".m4b"
|
||||||
|
],
|
||||||
|
"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",
|
||||||
|
".yml",
|
||||||
|
".yaml"
|
||||||
|
],
|
||||||
|
"music":[
|
||||||
|
".psf",
|
||||||
|
".mp3",
|
||||||
|
".ogg",
|
||||||
|
".flac",
|
||||||
|
".m4a"
|
||||||
|
],
|
||||||
|
"pdf":[
|
||||||
|
".pdf"
|
||||||
|
]
|
||||||
},
|
},
|
||||||
"theming":{
|
"theming":{
|
||||||
"success_color": "#88cc27",
|
"success_color": "#88cc27",
|
||||||
|
|||||||
Reference in New Issue
Block a user