Fixing vod thumbnailer; handling signal removal on tab close

+ more attempts at leak fixes; thumbnailer plugin updates and corrections
This commit is contained in:
2025-08-15 22:23:51 -05:00
parent e0723e7b9e
commit d96ace1504
22 changed files with 225 additions and 860 deletions

2
.gitignore vendored
View File

@@ -1,3 +1,5 @@
cookies.txt
docs/ docs/
.idea/ .idea/
*.zip *.zip

View File

@@ -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...")
@@ -202,4 +205,4 @@ class Icon(DesktopIconMixin, VideoIconMixin, MeshsIconMixin):
pixbuf = GdkPixbuf.Pixbuf.new_from_bytes(data, GdkPixbuf.Colorspace.RGB, pixbuf = GdkPixbuf.Pixbuf.new_from_bytes(data, GdkPixbuf.Colorspace.RGB,
False, 8, w, h, w * 3) False, 8, w, h, w * 3)
return pixbuf.scale_simple(wxh[0], wxh[1], 2) # BILINEAR = 2 return pixbuf.scale_simple(wxh[0], wxh[1], 2) # BILINEAR = 2

View File

@@ -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 = []

View File

@@ -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)

View File

@@ -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)

View File

@@ -44,7 +44,8 @@ class Application:
def ipc_realization_check(self, ipc_server): def ipc_realization_check(self, ipc_server):
try: try:
ipc_server.create_ipc_listener() ipc_server.create_ipc_listener()
except Exception: except Exception as e:
print(e)
ipc_server.send_test_ipc_message() ipc_server.send_test_ipc_message()
try: try:

View File

@@ -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

View File

@@ -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,
@@ -46,7 +49,6 @@ class FileActionSignalsMixin:
GLib.source_remove(timeout_id) GLib.source_remove(timeout_id)
timeout_id = GLib.timeout_add(0, self.update_on_soft_lock_end, 600, *(tab_widget_id,)) timeout_id = GLib.timeout_add(0, self.update_on_soft_lock_end, 600, *(tab_widget_id,))
self.soft_update_lock[tab_widget_id] = { "timeout_id": timeout_id }
def update_on_soft_lock_end(self, timout_ms, tab_widget_id): def update_on_soft_lock_end(self, timout_ms, tab_widget_id):
@@ -68,14 +70,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 return False
def do_file_search(self, widget, eve = None): def do_file_search(self, widget, eve = None):

View File

@@ -34,7 +34,7 @@ class KeyboardSignalsMixin:
self.alt_down = False self.alt_down = False
def on_global_key_press_controller(self, eve, user_data): def on_global_key_press_controller(self, eve, user_data):
keyname = Gdk.keyval_name(user_data.keyval).lower() keyname = Gdk.keyval_name(user_data.keyval).lower()
modifiers = Gdk.ModifierType(user_data.get_state() & ~Gdk.ModifierType.LOCK_MASK) modifiers = Gdk.ModifierType(user_data.get_state() & ~Gdk.ModifierType.LOCK_MASK)
self.was_midified_key = True if modifiers != 0 else False self.was_midified_key = True if modifiers != 0 else False

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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)
itr = store.get_iter(i)
GLib.idle_add(self.insert_store, store, itr, icon)
itr = None
@daemon_threaded def update_store(self, store, icons):
def make_and_load_icon(self, i, store, tab, dir, file): for i, icon in enumerate(icons):
icon = tab.create_icon(dir, file) try:
self.update_store(i, store, icon) itr = store.get_iter(i)
icon = None store.set_value(itr, 0, icon)
icon.run_dispose()
def get_icon(self, tab, dir, file): except:
tab.create_icon(dir, file) icon.run_dispose()
continue
# @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):
@@ -171,4 +133,4 @@ class GridMixin:
icon_grid = obj.get_children()[0] icon_grid = obj.get_children()[0]
name = icon_grid.get_name() name = icon_grid.get_name()
if name == _name: if name == _name:
return icon_grid return icon_grid

View File

@@ -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,57 @@ 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()
Gtk.main_iteration_do(False)
gc.collect()
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__}")
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(): 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 +126,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 +135,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 +223,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 +250,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 +272,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 +289,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)
@@ -311,4 +298,4 @@ class TabMixin(GridMixin):
self.builder.get_object("refresh_tab").released() self.builder.get_object("refresh_tab").released()
wid, tid = None, None wid, tid = None, None
tab = None tab = None

View File

@@ -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,10 +52,12 @@ class WindowMixin(TabMixin):
dir = None dir = None
def set_path_text(self, wid, tid): def set_path_text(self, wid, tid):
tab = self.get_fm_window(wid).get_tab_by_id(tid) path_entry = self.builder.get_object("path_entry")
event_system.emit("go_to_path", (tab.get_current_directory(),)) tab = self.get_fm_window(wid).get_tab_by_id(tid)
path_entry.set_text(tab.get_current_directory())
tab = None path_entry = None
tab = None
def grid_set_selected_items(self, icons_grid): def grid_set_selected_items(self, icons_grid):
new_items = icons_grid.get_selected_items() new_items = icons_grid.get_selected_items()
@@ -110,7 +116,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 +130,10 @@ class WindowMixin(TabMixin):
self.update_tab(tab_label, state.tab, state.icon_grid.get_store(), state.wid, state.tid) self.update_tab(tab_label, state.tab, state.icon_grid.get_store(), state.wid, state.tid)
else: else:
event_system.emit("open_files") event_system.emit("open_files")
state = None
notebook = None
tab_label = None
except WindowException as e: except WindowException as e:
traceback.print_exc() traceback.print_exc()
self.display_message(settings.theming.error_color, f"{repr(e)}") self.display_message(settings.theming.error_color, f"{repr(e)}")
@@ -192,4 +201,4 @@ class WindowMixin(TabMixin):
Gtk.drag_finish(drag_context, False, False, time) Gtk.drag_finish(drag_context, False, False, time)
def create_new_tab_notebook(self, widget=None, wid=None, path=None): def create_new_tab_notebook(self, widget=None, wid=None, path=None):
self.create_tab(wid, None, path) self.create_tab(wid, None, path)

View File

@@ -1,4 +1,5 @@
# Python imports # Python imports
import gc
# Lib imports # Lib imports
import gi import gi
@@ -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()
@@ -52,12 +55,12 @@ class IconGridWidget(Gtk.IconView):
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.append(self.connect("button_release_event", grid_icon_single_click))
self.connect("item-activated", grid_icon_double_click) self._handler_ids.append(self.connect("item-activated", grid_icon_double_click))
self.connect("selection-changed", grid_set_selected_items) self._handler_ids.append(self.connect("selection-changed", grid_set_selected_items))
self.connect("drag-data-get", grid_on_drag_set) self._handler_ids.append(self.connect("drag-data-get", grid_on_drag_set))
self.connect("drag-data-received", grid_on_drag_data_received) self._handler_ids.append(self.connect("drag-data-received", grid_on_drag_data_received))
self.connect("drag-motion", grid_on_drag_motion) self._handler_ids.append(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 +78,45 @@ 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:
iter = store.get_iter_first()
while iter:
next_iter = store.iter_next(iter)
store.unref_node(iter)
iter = next_iter
store.clear()
store.run_dispose()
store = None
self.set_model(None) self.set_model(None)
store = Gtk.ListStore(GdkPixbuf.Pixbuf or GdkPixbuf.PixbufAnimation or None, str or None)
# store = Gtk.ListStore(Gtk.DirectoryList) if not store: return
self.set_model(store)
store = None iter = store.get_iter_first()
while iter:
icon = store.get_value(iter, 0)
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()
del icon
store.clear()
store.run_dispose()
del store
gc.collect()

View File

@@ -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()
@@ -30,19 +30,28 @@ 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()

View File

@@ -17,22 +17,25 @@ class Launcher:
lowerName = file.lower() lowerName = file.lower()
command = [] command = []
if lowerName.endswith(self.fvideos): if self.use_defined_launchers:
command = [self.media_app, file] if lowerName.endswith(self.fvideos):
elif lowerName.endswith(self.fimages): command = [self.media_app, file]
command = [self.image_app, file] elif lowerName.endswith(self.fimages):
elif lowerName.endswith(self.fmusic): command = [self.image_app, file]
command = [self.music_app, file] elif lowerName.endswith(self.fmusic):
elif lowerName.endswith(self.foffice): command = [self.music_app, file]
command = [self.office_app, file] elif lowerName.endswith(self.foffice):
elif lowerName.endswith(self.fcode): command = [self.office_app, file]
command = [self.code_app, file] elif lowerName.endswith(self.fcode):
elif lowerName.endswith(self.ftext): command = [self.code_app, file]
command = [self.text_app, file] elif lowerName.endswith(self.ftext):
elif lowerName.endswith(self.fpdf): command = [self.text_app, file]
command = [self.pdf_app, file] elif lowerName.endswith(self.fpdf):
elif lowerName.endswith("placeholder-until-i-can-get-a-use-pref-fm-flag"): command = [self.pdf_app, file]
command = [self.file_manager_app, file] elif lowerName.endswith("placeholder-until-i-can-get-a-use-pref-fm-flag"):
command = [self.file_manager_app, file]
else:
command = ["xdg-open", file]
else: else:
command = ["xdg-open", file] command = ["xdg-open", file]
@@ -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)

View File

@@ -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"]

View File

@@ -14,9 +14,9 @@ from .singleton import Singleton
class IPCServer(Singleton): 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 = "local_network_unsecured"):
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')

View File

@@ -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"