Compare commits

..

13 Commits

Author SHA1 Message Date
itdominator 2f47614eaf Merge pull request 'develop' (#13) from develop into master
Reviewed-on: #13
2023-11-12 06:51:23 +00:00
itdominator a5f864b802 Merge pull request 'develop' (#12) from develop into master
Reviewed-on: #12
2023-09-30 19:36:35 +00:00
itdominator 5e9fe86cd6 Merge pull request 'develop' (#11) from develop into master
Reviewed-on: #11
2023-09-19 01:11:23 +00:00
itdominator da70244a54 Merge pull request 'Externalized items to new glade files and classes' (#10) from develop into master
Reviewed-on: #10
2022-12-04 08:49:44 +00:00
itdominator c01e81af27 Merge pull request 'Merge Stable Changesto Master' (#9) from develop into master
Reviewed-on: #9
2022-11-29 04:58:09 +00:00
itdominator 41f39ba8cc Merge pull request 'Plugin rework/fixes' (#8) from develop into master
Reviewed-on: #8
2022-09-06 02:24:35 +00:00
itdominator bebe0c7cba Merge pull request 'plugin work' (#7) from develop into master
Reviewed-on: #7
2022-09-05 06:09:39 +00:00
itdominator 74d53690e2 Merge pull request 'Updated VOD Thumbnailer, added icon gen logic' (#6) from develop into master
Reviewed-on: #6
2022-09-03 22:03:24 +00:00
itdominator 061dbf19ad Merge pull request 'develop' (#5) from develop into master
Reviewed-on: #5
2022-09-03 05:46:08 +00:00
itdominator 1798213bfc Merge pull request 'develop' (#4) from develop into master
Reviewed-on: #4
2022-09-02 03:25:44 +00:00
itdominator 6bd4d97db2 Merge pull request 'Bringing to latest changes' (#3) from develop into master
Reviewed-on: #3
2022-07-16 19:14:29 +00:00
itdominator 7737e3ad6d Merge pull request 'develop' (#2) from develop into master
Reviewed-on: #2
2022-01-31 02:34:18 +00:00
itdominator 3c914e64dd Merge pull request 'develop' (#1) from develop into master
Reviewed-on: #1
2022-01-30 00:22:36 +00:00
77 changed files with 577 additions and 1180 deletions

View File

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

View File

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

View File

@ -122,6 +122,7 @@ class Plugin(PluginBase):
uri = state.uris[0]
path = state.tab.get_current_directory()
properties = self._set_ui_data(uri, path)
response = self._properties_dialog.run()
if response in [Gtk.ResponseType.CANCEL, Gtk.ResponseType.DELETE_EVENT]:
@ -173,7 +174,7 @@ class Plugin(PluginBase):
is_symlink = file_info.get_attribute_as_string("standard::is-symlink")
properties.file_uri = uri
properties.file_target = file_info.get_attribute_as_string("standard::symlink-target") if is_symlink in [True, "TRUE"] else ""
properties.file_target = file_info.get_attribute_as_string("standard::symlink-target") if is_symlink else ""
properties.file_name = file_info.get_display_name()
properties.file_location = path
properties.mime_type = file_info.get_content_type()

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,7 +7,6 @@ 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
@ -20,17 +19,6 @@ class GridMixin:
"""docstring for GridMixin"""
def load_store(self, tab, store, save_state = False, use_generator = False):
# dir = tab.get_current_directory()
# file = Gio.File.new_for_path(dir)
# dir_list = Gtk.DirectoryList.new("standard::*", file)
# store.set(dir_list)
# file = Gio.File.new_for_path(dir)
# for file in file.enumerate_children("standard::*", Gio.FILE_ATTRIBUTE_STANDARD_NAME, None):
# store.append(file)
# return
dir = tab.get_current_directory()
files = tab.get_files()
@ -38,77 +26,61 @@ class GridMixin:
store.append([None, file[0]])
Gtk.main_iteration()
self.generate_icons(tab, store, dir, files)
# GLib.Thread("", self.generate_icons, tab, store, dir, files)
# for i, file in enumerate(files):
# self.create_icon(i, tab, store, dir, file[0])
if use_generator:
# NOTE: tab > icon > _get_system_thumbnail_gtk_thread must not be used
# as the attempted promotion back to gtk threading stalls the generator. (We're already in main gtk thread)
for i, icon in enumerate( self.create_icons_generator(tab, dir, files) ):
self.load_icon(i, store, icon)
else:
try:
loop = asyncio.get_running_loop()
except RuntimeError:
loop = None
if loop and loop.is_running():
loop.create_task( self.create_icons(tab, store, dir, files) )
else:
asyncio.run( self.create_icons(tab, store, dir, files) )
# 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
async def create_icons(self, tab, store, dir, files):
tasks = [self.update_store(i, store, dir, tab, file[0]) for i, file in enumerate(files)]
await asyncio.gather(*tasks)
@daemon_threaded
def generate_icons(self, tab, store, dir, files):
for i, file in enumerate(files):
# GLib.Thread(f"{i}", self.make_and_load_icon, i, store, tab, dir, file[0])
self.make_and_load_icon( i, store, tab, dir, file[0])
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)
async def load_icon(self, i, store, icon):
self.update_store(i, store, icon)
icon = None
def get_icon(self, tab, dir, file):
tab.create_icon(dir, file)
# @daemon_threaded
# def generate_icons(self, tab, store, dir, files):
# try:
# loop = asyncio.get_running_loop()
# except RuntimeError:
# loop = None
# if loop and loop.is_running():
# loop = asyncio.get_event_loop()
# loop.create_task( self.create_icons(tab, store, dir, files) )
# else:
# asyncio.run( self.create_icons(tab, store, dir, files) )
# async def create_icons(self, tab, store, dir, files):
# icons = [self.get_icon(tab, dir, file[0]) for file in files]
# data = await asyncio.gather(*icons)
# tasks = [self.update_store(i, store, icon) for i, icon in enumerate(data)]
# asyncio.gather(*tasks)
# async def update_store(self, i, store, icon):
# itr = store.get_iter(i)
# GLib.idle_add(self.insert_store, store, itr, icon)
# async def get_icon(self, tab, dir, file):
# return tab.create_icon(dir, file)
def insert_store(self, store, itr, icon):
async def update_store(self, i, store, dir, tab, file):
icon = tab.create_icon(dir, file)
itr = store.get_iter(i)
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 create_icons_generator(self, tab, dir, files):
for file in files:
icon = tab.create_icon(dir, file[0])
yield icon
def do_ui_update(self):
Gtk.main_iteration()
return False
# @daemon_threaded
# def create_icon(self, i, tab, store, dir, file):
# icon = tab.create_icon(dir, file)
# GLib.idle_add(self.update_store, *(i, store, icon,))
#
# @daemon_threaded
# def load_icon(self, i, store, icon):
# GLib.idle_add(self.update_store, *(i, store, icon,))
def create_tab_widget(self):
return TabHeaderWidget(self.close_tab)
# def update_store(self, i, store, icon):
# itr = store.get_iter(i)
# store.set_value(itr, 0, icon)
def create_tab_widget(self, tab):
return TabHeaderWidget(tab, self.close_tab)
def create_scroll_and_store(self, tab, wid, use_tree_view = False):
scroll = Gtk.ScrolledWindow()
@ -165,7 +137,6 @@ class GridMixin:
store = icon_grid.get_model()
tab_label = notebook.get_tab_label(obj).get_children()[0]
icon_grid = None
return store, tab_label
def get_icon_grid_from_notebook(self, notebook, _name):

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -138,7 +138,6 @@ class Icon(DesktopIconMixin, VideoIconMixin, MeshsIconMixin):
def _call_gtk_thread(event, result):
result.append( self.get_system_thumbnail(full_path, size) )
event.set()
return False
result = []
event = threading.Event()
@ -156,7 +155,7 @@ class Icon(DesktopIconMixin, VideoIconMixin, MeshsIconMixin):
if data:
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(icon_path)
raise IconException("No system icon found...")
except IconException:

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

17
user_config/usr/bin/solarfm Executable file
View File

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

View File

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

View File

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

View File

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