427 lines
13 KiB
Python
427 lines
13 KiB
Python
from talon import app, Module, Context, actions, ui, imgui, settings, app, registry
|
|
from os.path import expanduser
|
|
from subprocess import Popen
|
|
from pathlib import Path
|
|
from typing import List, Union
|
|
import os
|
|
import math
|
|
import re
|
|
from itertools import islice
|
|
|
|
mod = Module()
|
|
ctx = Context()
|
|
|
|
mod.tag("file_manager", desc="Tag for enabling generic file management commands")
|
|
mod.list("file_manager_directories", desc="List of subdirectories for the current path")
|
|
mod.list("file_manager_files", desc="List of files at the root of the current path")
|
|
|
|
|
|
setting_auto_show_pickers = mod.setting(
|
|
"file_manager_auto_show_pickers",
|
|
type=int,
|
|
default=0,
|
|
desc="Enable to show the file/directories pickers automatically",
|
|
)
|
|
setting_folder_limit = mod.setting(
|
|
"file_manager_folder_limit",
|
|
type=int,
|
|
default=1000,
|
|
desc="Maximum number of files/folders to iterate",
|
|
)
|
|
setting_file_limit = mod.setting(
|
|
"file_manager_file_limit",
|
|
type=int,
|
|
default=1000,
|
|
desc="Maximum number of files to iterate",
|
|
)
|
|
setting_imgui_limit = mod.setting(
|
|
"file_manager_imgui_limit",
|
|
type=int,
|
|
default=20,
|
|
desc="Maximum number of files/folders to display in the imgui",
|
|
)
|
|
setting_imgui_string_limit = mod.setting(
|
|
"file_manager_string_limit",
|
|
type=int,
|
|
default=20,
|
|
desc="Maximum like of string to display in the imgui",
|
|
)
|
|
cached_path = None
|
|
file_selections = folder_selections = []
|
|
current_file_page = current_folder_page = 1
|
|
|
|
ctx.lists["self.file_manager_directories"] = []
|
|
ctx.lists["self.file_manager_files"] = []
|
|
|
|
directories_to_remap = {}
|
|
user_path = os.path.expanduser("~")
|
|
if app.platform == "windows":
|
|
is_windows = True
|
|
import ctypes
|
|
|
|
GetUserNameEx = ctypes.windll.secur32.GetUserNameExW
|
|
NameDisplay = 3
|
|
|
|
size = ctypes.pointer(ctypes.c_ulong(0))
|
|
GetUserNameEx(NameDisplay, None, size)
|
|
|
|
nameBuffer = ctypes.create_unicode_buffer(size.contents.value)
|
|
GetUserNameEx(NameDisplay, nameBuffer, size)
|
|
one_drive_path = os.path.expanduser(os.path.join("~", "OneDrive"))
|
|
|
|
# this is probably not the correct way to check for onedrive, quick and dirty
|
|
if os.path.isdir(os.path.expanduser(os.path.join("~", r"OneDrive\Desktop"))):
|
|
default_folder = os.path.join("~", "Desktop")
|
|
|
|
directories_to_remap = {
|
|
"Desktop": os.path.join(one_drive_path, "Desktop"),
|
|
"Documents": os.path.join(one_drive_path, "Documents"),
|
|
"Downloads": os.path.join(user_path, "Downloads"),
|
|
"Music": os.path.join(user_path, "Music"),
|
|
"OneDrive": one_drive_path,
|
|
"Pictures": os.path.join(one_drive_path, "Pictures"),
|
|
"Videos": os.path.join(user_path, "Videos"),
|
|
}
|
|
else:
|
|
# todo use expanduser for cross platform support
|
|
directories_to_remap = {
|
|
"Desktop": os.path.join(user_path, "Desktop"),
|
|
"Documents": os.path.join(user_path, "Documents"),
|
|
"Downloads": os.path.join(user_path, "Downloads"),
|
|
"Music": os.path.join(user_path, "Music"),
|
|
"OneDrive": one_drive_path,
|
|
"Pictures": os.path.join(user_path, "Pictures"),
|
|
"Videos": os.path.join(user_path, "Videos"),
|
|
}
|
|
|
|
|
|
@mod.action_class
|
|
class Actions:
|
|
def file_manager_current_path() -> str:
|
|
"""Returns the current path for the active file manager."""
|
|
return ""
|
|
|
|
def file_manager_open_parent():
|
|
"""file_manager_open_parent"""
|
|
return
|
|
|
|
def file_manager_go_forward():
|
|
"""file_manager_go_forward_directory"""
|
|
|
|
def file_manager_go_back():
|
|
"""file_manager_go_forward_directory"""
|
|
|
|
def file_manager_open_volume(volume: str):
|
|
"""file_manager_open_volume"""
|
|
|
|
def file_manager_open_directory(path: str):
|
|
"""opens the directory that's already visible in the view"""
|
|
|
|
def file_manager_select_directory(path: str):
|
|
"""selects the directory"""
|
|
|
|
def file_manager_new_folder(name: str):
|
|
"""Creates a new folder in a gui filemanager or inserts the command to do so for terminals"""
|
|
|
|
def file_manager_show_properties():
|
|
"""Shows the properties for the file"""
|
|
|
|
def file_manager_terminal_here():
|
|
"""Opens terminal at current location"""
|
|
|
|
def file_manager_open_file(path: str):
|
|
"""opens the file"""
|
|
|
|
def file_manager_select_file(path: str):
|
|
"""selects the file"""
|
|
|
|
def file_manager_refresh_title():
|
|
"""Refreshes the title to match current directory. this is for e.g. windows command prompt that will need to do some magic. """
|
|
return
|
|
|
|
def file_manager_update_lists():
|
|
"""Forces an update of the lists (e.g., when file or folder created)"""
|
|
update_lists()
|
|
|
|
def file_manager_toggle_pickers():
|
|
"""Shows the pickers"""
|
|
if gui_files.showing:
|
|
gui_files.hide()
|
|
gui_folders.hide()
|
|
else:
|
|
gui_files.show()
|
|
gui_folders.show()
|
|
|
|
def file_manager_hide_pickers():
|
|
"""Hides the pickers"""
|
|
if gui_files.showing:
|
|
gui_files.hide()
|
|
gui_folders.hide()
|
|
|
|
def file_manager_open_user_directory(path: str):
|
|
"""expands and opens the user directory"""
|
|
# this functionality exists mostly for windows.
|
|
# since OneDrive does strange stuff...
|
|
if path in directories_to_remap:
|
|
path = directories_to_remap[path]
|
|
|
|
path = os.path.expanduser(os.path.join("~", path))
|
|
actions.user.file_manager_open_directory(path)
|
|
|
|
def file_manager_get_directory_by_index(index: int) -> str:
|
|
"""Returns the requested directory for the imgui display by index"""
|
|
index = (current_folder_page - 1) * setting_imgui_limit.get() + index
|
|
assert index < len(folder_selections)
|
|
return folder_selections[index]
|
|
|
|
def file_manager_get_file_by_index(index: int) -> str:
|
|
"""Returns the requested directory for the imgui display by index"""
|
|
index = (current_file_page - 1) * setting_imgui_limit.get() + index
|
|
assert index < len(file_selections)
|
|
return file_selections[index]
|
|
|
|
def file_manager_next_file_page():
|
|
"""next_file_page"""
|
|
global current_file_page
|
|
if gui_files.showing:
|
|
if current_file_page != total_file_pages:
|
|
current_file_page += 1
|
|
else:
|
|
current_file_page = 1
|
|
gui_files.show()
|
|
|
|
def file_manager_previous_file_page():
|
|
"""previous_file_page"""
|
|
global current_file_page
|
|
if gui_files.showing:
|
|
if current_file_page != 1:
|
|
current_file_page -= 1
|
|
else:
|
|
current_file_page = total_file_pages
|
|
|
|
gui_files.show()
|
|
|
|
def file_manager_next_folder_page():
|
|
"""next_folder_page"""
|
|
global current_folder_page
|
|
if gui_folders.showing:
|
|
if current_folder_page != total_folder_pages:
|
|
current_folder_page += 1
|
|
else:
|
|
current_folder_page = 1
|
|
|
|
gui_folders.show()
|
|
|
|
def file_manager_previous_folder_page():
|
|
"""previous_folder_page"""
|
|
global current_folder_page
|
|
if gui_folders.showing:
|
|
if current_folder_page != 1:
|
|
current_folder_page -= 1
|
|
else:
|
|
current_folder_page = total_folder_pages
|
|
|
|
gui_folders.show()
|
|
|
|
|
|
pattern = re.compile(r"[A-Z][a-z]*|[a-z]+|\d")
|
|
|
|
|
|
def create_spoken_forms(symbols, max_len=30):
|
|
return [" ".join(list(islice(pattern.findall(s), max_len))) for s in symbols]
|
|
|
|
|
|
def is_dir(f):
|
|
try:
|
|
return f.is_dir()
|
|
except:
|
|
return False
|
|
|
|
|
|
def is_file(f):
|
|
try:
|
|
return f.is_file()
|
|
except:
|
|
return False
|
|
|
|
|
|
def get_directory_map(current_path):
|
|
directories = [
|
|
f.name
|
|
for f in islice(
|
|
current_path.iterdir(), settings.get("user.file_manager_folder_limit", 1000)
|
|
)
|
|
if is_dir(f)
|
|
]
|
|
# print(len(directories))
|
|
spoken_forms = create_spoken_forms(directories)
|
|
return dict(zip(spoken_forms, directories))
|
|
|
|
|
|
def get_file_map(current_path):
|
|
files = [
|
|
f.name
|
|
for f in islice(
|
|
current_path.iterdir(), settings.get("user.file_manager_file_limit", 1000)
|
|
)
|
|
if is_file(f)
|
|
]
|
|
# print(str(files))
|
|
spoken_forms = create_spoken_forms([p for p in files])
|
|
return dict(zip(spoken_forms, [f for f in files]))
|
|
|
|
|
|
@imgui.open(y=10, x=900)
|
|
def gui_folders(gui: imgui.GUI):
|
|
global current_folder_page, total_folder_pages
|
|
total_folder_pages = math.ceil(
|
|
len(ctx.lists["self.file_manager_directories"]) / setting_imgui_limit.get()
|
|
)
|
|
gui.text(
|
|
"Select a directory ({}/{})".format(current_folder_page, total_folder_pages)
|
|
)
|
|
gui.line()
|
|
|
|
index = 1
|
|
current_index = (current_folder_page - 1) * setting_imgui_limit.get()
|
|
|
|
while index <= setting_imgui_limit.get() and current_index < len(folder_selections):
|
|
name = (
|
|
(
|
|
folder_selections[current_index][: setting_imgui_string_limit.get()]
|
|
+ ".."
|
|
)
|
|
if len(folder_selections[current_index]) > setting_imgui_string_limit.get()
|
|
else folder_selections[current_index]
|
|
)
|
|
gui.text("{}: {} ".format(index, name))
|
|
current_index += 1
|
|
index = index + 1
|
|
|
|
# if total_folder_pages > 1:
|
|
# gui.spacer()
|
|
|
|
# if gui.button('Next...'):
|
|
# actions.user.file_manager_next_folder_page()
|
|
|
|
# if gui.button("Previous..."):
|
|
# actions.user.file_manager_previous_folder_page()
|
|
|
|
|
|
@imgui.open(y=10, x=1300)
|
|
def gui_files(gui: imgui.GUI):
|
|
global file_selections, current_file_page, total_file_pages
|
|
total_file_pages = math.ceil(len(file_selections) / setting_imgui_limit.get())
|
|
|
|
gui.text("Select a file ({}/{})".format(current_file_page, total_file_pages))
|
|
gui.line()
|
|
index = 1
|
|
current_index = (current_file_page - 1) * setting_imgui_limit.get()
|
|
|
|
while index <= setting_imgui_limit.get() and current_index < len(file_selections):
|
|
name = (
|
|
(file_selections[current_index][: setting_imgui_string_limit.get()] + "..")
|
|
if len(file_selections[current_index]) > setting_imgui_string_limit.get()
|
|
else file_selections[current_index]
|
|
)
|
|
|
|
gui.text("{}: {} ".format(index, name))
|
|
current_index = current_index + 1
|
|
index = index + 1
|
|
|
|
# if total_file_pages > 1:
|
|
# gui.spacer()
|
|
|
|
# if gui.button('Next...'):
|
|
# actions.user.file_manager_next_file_page()
|
|
|
|
# if gui.button("Previous..."):
|
|
# actions.user.file_manager_previous_file_page()
|
|
|
|
|
|
def clear_lists():
|
|
global folder_selections, file_selections
|
|
if (
|
|
len(ctx.lists["self.file_manager_directories"]) > 0
|
|
or len(ctx.lists["self.file_manager_files"]) > 0
|
|
):
|
|
current_folder_page = current_file_page = 1
|
|
ctx.lists["self.file_manager_directories"] = []
|
|
ctx.lists["self.file_manager_files"] = []
|
|
folder_selections = []
|
|
file_selections = []
|
|
|
|
|
|
def update_gui():
|
|
if gui_folders.showing or setting_auto_show_pickers.get() >= 1:
|
|
gui_folders.show()
|
|
gui_files.show()
|
|
|
|
|
|
def update_lists():
|
|
global folder_selections, file_selections, current_folder_page, current_file_page
|
|
is_valid_path = False
|
|
path = actions.user.file_manager_current_path()
|
|
directories = {}
|
|
files = {}
|
|
folder_selections = []
|
|
file_selections = []
|
|
# print(path)
|
|
try:
|
|
current_path = Path(path)
|
|
is_valid_path = current_path.is_dir()
|
|
except:
|
|
is_valid_path = False
|
|
|
|
if is_valid_path:
|
|
# print("valid..." + str(current_path))
|
|
try:
|
|
directories = get_directory_map(current_path)
|
|
files = get_file_map(current_path)
|
|
except:
|
|
# print("invalid path...")
|
|
|
|
directories = {}
|
|
files = {}
|
|
|
|
current_folder_page = current_file_page = 1
|
|
ctx.lists["self.file_manager_directories"] = directories
|
|
ctx.lists["self.file_manager_files"] = files
|
|
folder_selections = sorted(directories.values(), key=str.casefold)
|
|
file_selections = sorted(files.values(), key=str.casefold)
|
|
|
|
update_gui()
|
|
|
|
|
|
def win_event_handler(window):
|
|
global cached_path
|
|
|
|
# on windows, we get events from the clock
|
|
# and such, so this check is important
|
|
if not window.app.exe or window != ui.active_window():
|
|
return
|
|
|
|
path = actions.user.file_manager_current_path()
|
|
|
|
if not "user.file_manager" in registry.tags:
|
|
actions.user.file_manager_hide_pickers()
|
|
clear_lists()
|
|
elif path:
|
|
if cached_path != path:
|
|
update_lists()
|
|
elif cached_path:
|
|
clear_lists()
|
|
actions.user.file_manager_hide_pickers()
|
|
|
|
cached_path = path
|
|
|
|
|
|
def register_events():
|
|
ui.register("win_title", win_event_handler)
|
|
ui.register("win_focus", win_event_handler)
|
|
|
|
|
|
# prevent scary errors in the log by waiting for talon to be fully loaded
|
|
# before registering the events
|
|
app.register("ready", register_events)
|
|
|