From e80b02dcaeafc2556f50e31f8876dcff6189bea2 Mon Sep 17 00:00:00 2001 From: Enyium <123484196+Enyium@users.noreply.github.com> Date: Tue, 29 Apr 2025 00:46:38 +0200 Subject: [PATCH 1/6] Improved `system_paths` list generation for Windows. - Added various paths including for virtual folders. - Corrected resolving of paths like "Downloads", which the user may have changed using the Explorer properties dialog of the respective virtual or resolved folder. - Fixed bug where path adjustments made by the algorithm weren't reflected in aliases. --- apps/windows_explorer/windows_explorer.talon | 3 - core/system_paths.py | 151 ++++++++++++++++--- 2 files changed, 133 insertions(+), 21 deletions(-) diff --git a/apps/windows_explorer/windows_explorer.talon b/apps/windows_explorer/windows_explorer.talon index 28d83c0e7b..5bfa72f77a 100644 --- a/apps/windows_explorer/windows_explorer.talon +++ b/apps/windows_explorer/windows_explorer.talon @@ -4,6 +4,3 @@ app: windows_file_browser tag(): user.address tag(): user.file_manager tag(): user.navigation - -go app data: user.file_manager_open_directory("%AppData%") -go program files: user.file_manager_open_directory("%programfiles%") diff --git a/core/system_paths.py b/core/system_paths.py index bdcb15c80c..a527070e6a 100644 --- a/core/system_paths.py +++ b/core/system_paths.py @@ -5,6 +5,7 @@ custom paths. """ +import os.path from pathlib import Path from talon import Module, actions, app @@ -28,32 +29,146 @@ def on_ready(): default_system_paths = { "user": home, - "desktop": home / "Desktop", - "desk": home / "Desktop", - "documents": home / "Documents", - "docks": home / "Documents", - "downloads": home / "Downloads", - "music": home / "Music", - "pictures": home / "Pictures", - "videos": home / "Videos", "talon home": talon_home, "talon recordings": talon_home / "recordings", "talon user": actions.path.talon_user(), } if app.platform == "windows": - default_system_paths["profile"] = home - onedrive_path = home / "OneDrive" - - # this is probably not the correct way to check for OneDrive, quick and dirty - if (onedrive_path / "Desktop").is_dir(): - default_system_paths["desktop"] = onedrive_path / "Desktop" - default_system_paths["documents"] = onedrive_path / "Documents" - default_system_paths["one drive"] = onedrive_path - default_system_paths["pictures"] = onedrive_path / "Pictures" + import winreg + + # Virtual folders. + default_system_paths.update( + { + # Virtual-only folders. + "quick access": "shell:::{679F85CB-0220-4080-B29B-5540CC05AAB6}", + "this pc": "shell:MyComputerFolder", # Or `shell:::{20D04FE0-3AEA-1069-A2D8-08002B30309D}`, also without `shell:`. + "network": "shell:NetworkPlacesFolder", # Or `shell:::{F02C1A0D-BE21-4350-88B0-7367FC96EF3C}`, also without `shell:`. + "recycle bin": "shell:RecycleBinFolder", # Or `shell:::{645FF040-5081-101B-9F08-00AA002F954E}`, also without `shell:`. + # Folders with virtual and resolved form. + "virtual desktop": "shell:Desktop", + # More like, e.g., `shell:Downloads` can be found under the registry key + # `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\FolderDescriptions`. + # You can also find a long list of GUIDs under + # . + } + ) + + # Resolved forms of virtual folders whose locations the user may have changed. + with winreg.OpenKey( + winreg.HKEY_CURRENT_USER, + r"SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders", + ) as key: + + def get_path(value_name, fallback): + try: + path_str, value_type = winreg.QueryValueEx(key, value_name) + assert value_type == winreg.REG_EXPAND_SZ + except (FileNotFoundError, AssertionError): + path_str = str(fallback) + return Path(os.path.expandvars(path_str)) + + local_downloads_path = get_path( + "{7D83EE9B-2244-4E70-B1F5-5393042AF1E4}", r"%USERPROFILE%\Downloads" + ) + + default_system_paths.update( + { + "desktop": get_path("Desktop", r"%USERPROFILE%\Desktop"), + "documents": get_path("Personal", r"%USERPROFILE%\Documents"), + "downloads": get_path( + "{374DE290-123F-4565-9164-39C4925E467B}", local_downloads_path + ), + "local downloads": local_downloads_path, + "music": get_path("My Music", r"%USERPROFILE%\Music"), + "pictures": get_path("My Pictures", r"%USERPROFILE%\Pictures"), + "videos": get_path("My Video", r"%USERPROFILE%\Videos"), + } + ) + + # Paths from environment variables. + def get_env_path(var_name): + try: + return Path(os.environ[var_name]) + except KeyError: + return None + + onedrive_path = get_env_path("OneDrive") + + default_system_paths.update( + { + spoken_form: path + for spoken_form, path in { + "system root": get_env_path("SystemRoot"), + "program files": get_env_path("ProgramFiles"), + "program files ex eighty six": get_env_path("ProgramFiles(x86)"), + "all users profile": get_env_path("ALLUSERSPROFILE"), + "public user profile": get_env_path("PUBLIC"), + "app data": get_env_path("APPDATA"), + "local app data": get_env_path("LOCALAPPDATA"), + "program data": get_env_path("ProgramData"), + "temp dir": get_env_path("TEMP"), + "one drive": onedrive_path, + }.items() + if path is not None + } + ) + + # OneDrive subpaths. + if onedrive_path: + # this is probably not the correct way to check for OneDrive, quick and dirty + onedrive_desktop_path = onedrive_path / "Desktop" + if onedrive_desktop_path.is_dir(): + default_system_paths.update( + { + "desktop": onedrive_desktop_path, + "documents": onedrive_path / "Documents", + "pictures": onedrive_path / "Pictures", + } + ) + else: # Mac and Linux. + default_system_paths.update( + { + # TODO: Correct this for Mac and Linux. + "desktop": home / "Desktop", + "documents": home / "Documents", + "downloads": home / "Downloads", + "music": home / "Music", + "pictures": home / "Pictures", + "videos": home / "Videos", + } + ) + + # Aliases. + default_system_paths.update( + { + "desk": default_system_paths["desktop"], + "docks": default_system_paths["documents"], + } + ) + + if app.platform == "windows": + default_system_paths.update( + { + "virtual desk": default_system_paths["virtual desktop"], + "vee desk": default_system_paths["virtual desktop"], + "trash": default_system_paths["recycle bin"], + "win dir": default_system_paths["system root"], + "user profile": default_system_paths["user"], + "profile": default_system_paths["user"], + "public profile": default_system_paths["public user profile"], + "temp files": default_system_paths["temp dir"], + "temporary": default_system_paths["temp dir"], + } + ) else: - default_system_paths["home"] = home + default_system_paths.update( + { + "home": default_system_paths["user"], + } + ) + # Build .talon-list file. with open(system_paths, "x") as f: print("list: user.system_paths", file=f) print(f"hostname: {hostname}", file=f) From 5278f7c05f1c331b823fcad4abe8034ff06815b0 Mon Sep 17 00:00:00 2001 From: Enyium <123484196+Enyium@users.noreply.github.com> Date: Wed, 31 Dec 2025 21:15:36 +0100 Subject: [PATCH 2/6] No explicit back-and-forth path conversion. --- core/system_paths.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/system_paths.py b/core/system_paths.py index a527070e6a..47d73942f1 100644 --- a/core/system_paths.py +++ b/core/system_paths.py @@ -62,11 +62,11 @@ def on_ready(): def get_path(value_name, fallback): try: - path_str, value_type = winreg.QueryValueEx(key, value_name) + path, value_type = winreg.QueryValueEx(key, value_name) assert value_type == winreg.REG_EXPAND_SZ except (FileNotFoundError, AssertionError): - path_str = str(fallback) - return Path(os.path.expandvars(path_str)) + path = fallback + return Path(os.path.expandvars(path)) local_downloads_path = get_path( "{7D83EE9B-2244-4E70-B1F5-5393042AF1E4}", r"%USERPROFILE%\Downloads" From 291ca97605a8edaf37cde09e0d397a0008316153 Mon Sep 17 00:00:00 2001 From: Enyium <123484196+Enyium@users.noreply.github.com> Date: Wed, 31 Dec 2025 21:31:31 +0100 Subject: [PATCH 3/6] Reduced system path aliases. Corrected spoken forms for OneDrive. --- core/system_paths.py | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/core/system_paths.py b/core/system_paths.py index 47d73942f1..56318c09cd 100644 --- a/core/system_paths.py +++ b/core/system_paths.py @@ -121,9 +121,9 @@ def get_env_path(var_name): if onedrive_desktop_path.is_dir(): default_system_paths.update( { - "desktop": onedrive_desktop_path, - "documents": onedrive_path / "Documents", - "pictures": onedrive_path / "Pictures", + "one drive desktop": onedrive_desktop_path, + "one drive documents": onedrive_path / "Documents", + "one drive pictures": onedrive_path / "Pictures", } ) else: # Mac and Linux. @@ -150,15 +150,9 @@ def get_env_path(var_name): if app.platform == "windows": default_system_paths.update( { - "virtual desk": default_system_paths["virtual desktop"], - "vee desk": default_system_paths["virtual desktop"], "trash": default_system_paths["recycle bin"], "win dir": default_system_paths["system root"], - "user profile": default_system_paths["user"], - "profile": default_system_paths["user"], - "public profile": default_system_paths["public user profile"], - "temp files": default_system_paths["temp dir"], - "temporary": default_system_paths["temp dir"], + "user profile": default_system_paths["user"], # We also have the long form for other profile folders. } ) else: From e8698ae3a8e04657d2dcea8e6d97fb75eea2f01b Mon Sep 17 00:00:00 2001 From: Enyium <123484196+Enyium@users.noreply.github.com> Date: Wed, 31 Dec 2025 23:26:15 +0100 Subject: [PATCH 4/6] Removed path visiting commands that became redundant. `address.talon` already handles this with `go [to] `. --- apps/windows_explorer/windows_explorer.talon | 3 --- 1 file changed, 3 deletions(-) diff --git a/apps/windows_explorer/windows_explorer.talon b/apps/windows_explorer/windows_explorer.talon index ea7276f61d..5bfa72f77a 100644 --- a/apps/windows_explorer/windows_explorer.talon +++ b/apps/windows_explorer/windows_explorer.talon @@ -4,6 +4,3 @@ app: windows_file_browser tag(): user.address tag(): user.file_manager tag(): user.navigation - -go app data: user.file_manager_open_directory("%AppData%") -go program files: user.file_manager_open_directory("%ProgramFiles%") From d6c4e6a5dd08b1e63b14b2baf9d64d62a71da53c Mon Sep 17 00:00:00 2001 From: Enyium <123484196+Enyium@users.noreply.github.com> Date: Wed, 31 Dec 2025 23:55:43 +0100 Subject: [PATCH 5/6] Transferred static virtual-folder paths to own list. Also provided a capture that combines non-virtual and virtual system paths. --- core/{ => system_paths}/system_paths.py | 31 ++++++------------- .../virtual_system_path_win.talon-list | 21 +++++++++++++ 2 files changed, 31 insertions(+), 21 deletions(-) rename core/{ => system_paths}/system_paths.py (81%) create mode 100644 core/system_paths/virtual_system_path_win.talon-list diff --git a/core/system_paths.py b/core/system_paths/system_paths.py similarity index 81% rename from core/system_paths.py rename to core/system_paths/system_paths.py index 56318c09cd..b5af0c941f 100644 --- a/core/system_paths.py +++ b/core/system_paths/system_paths.py @@ -1,8 +1,9 @@ """ This module gives us the list {user.system_paths} and the capture that wraps -the list to easily refer to system paths in talon and python files. It also creates a file -system_paths-.talon-list in the core folder so the user can easily add their own -custom paths. +the list to easily refer to system paths in Talon and Python files. It also creates a file +system_paths-.talon-list in the core/system_paths folder, so the user can easily add +their own custom paths. The additional capture represents a +combination of the lists {user.system_path} and {user.virtual_system_path}. """ import os.path @@ -12,6 +13,7 @@ mod = Module() mod.list("system_paths", desc="List of system paths") +mod.list("virtual_system_path", desc="Path of virtual system folder like Recycle Bin") def on_ready(): @@ -37,23 +39,6 @@ def on_ready(): if app.platform == "windows": import winreg - # Virtual folders. - default_system_paths.update( - { - # Virtual-only folders. - "quick access": "shell:::{679F85CB-0220-4080-B29B-5540CC05AAB6}", - "this pc": "shell:MyComputerFolder", # Or `shell:::{20D04FE0-3AEA-1069-A2D8-08002B30309D}`, also without `shell:`. - "network": "shell:NetworkPlacesFolder", # Or `shell:::{F02C1A0D-BE21-4350-88B0-7367FC96EF3C}`, also without `shell:`. - "recycle bin": "shell:RecycleBinFolder", # Or `shell:::{645FF040-5081-101B-9F08-00AA002F954E}`, also without `shell:`. - # Folders with virtual and resolved form. - "virtual desktop": "shell:Desktop", - # More like, e.g., `shell:Downloads` can be found under the registry key - # `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\FolderDescriptions`. - # You can also find a long list of GUIDs under - # . - } - ) - # Resolved forms of virtual folders whose locations the user may have changed. with winreg.OpenKey( winreg.HKEY_CURRENT_USER, @@ -150,7 +135,6 @@ def get_env_path(var_name): if app.platform == "windows": default_system_paths.update( { - "trash": default_system_paths["recycle bin"], "win dir": default_system_paths["system root"], "user profile": default_system_paths["user"], # We also have the long form for other profile folders. } @@ -179,5 +163,10 @@ def get_env_path(var_name): def system_path(m) -> str: return m.system_paths +@mod.capture(rule="{user.system_paths} | {user.virtual_system_path}") +def possibly_virtual_system_path(m) -> str: + """A physical or virtual system path, i.e., a combination of the lists `user.system_path` and `user.virtual_system_path`.""" + return str(m) + app.register("ready", on_ready) diff --git a/core/system_paths/virtual_system_path_win.talon-list b/core/system_paths/virtual_system_path_win.talon-list new file mode 100644 index 0000000000..3d7e2fb784 --- /dev/null +++ b/core/system_paths/virtual_system_path_win.talon-list @@ -0,0 +1,21 @@ +list: user.virtual_system_path +os: windows +- + +# Virtual-only folders. +quick access: shell:::{679F85CB-0220-4080-B29B-5540CC05AAB6} +# Or `shell:::{20D04FE0-3AEA-1069-A2D8-08002B30309D}` for "This PC", also without `shell:`. +this pc: shell:MyComputerFolder +# Or `shell:::{F02C1A0D-BE21-4350-88B0-7367FC96EF3C}` for "Network", also without `shell:`. +network: shell:NetworkPlacesFolder +# Or `shell:::{645FF040-5081-101B-9F08-00AA002F954E}` for "Recycle Bin", also without `shell:`. +recycle bin: shell:RecycleBinFolder +trash: shell:RecycleBinFolder + +# Folders with virtual and resolved form. +virtual desktop: shell:Desktop + +# More like, e.g., `shell:Downloads` can be found under the registry key +# `HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\FolderDescriptions`. +# You can also find a long list of GUIDs under +# . From 7b3de781a6f042e779a416b17ba2b641d147fe0e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 31 Dec 2025 23:05:34 +0000 Subject: [PATCH 6/6] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- core/system_paths/system_paths.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/core/system_paths/system_paths.py b/core/system_paths/system_paths.py index b5af0c941f..337ca99f39 100644 --- a/core/system_paths/system_paths.py +++ b/core/system_paths/system_paths.py @@ -136,7 +136,9 @@ def get_env_path(var_name): default_system_paths.update( { "win dir": default_system_paths["system root"], - "user profile": default_system_paths["user"], # We also have the long form for other profile folders. + "user profile": default_system_paths[ + "user" + ], # We also have the long form for other profile folders. } ) else: @@ -163,6 +165,7 @@ def get_env_path(var_name): def system_path(m) -> str: return m.system_paths + @mod.capture(rule="{user.system_paths} | {user.virtual_system_path}") def possibly_virtual_system_path(m) -> str: """A physical or virtual system path, i.e., a combination of the lists `user.system_path` and `user.virtual_system_path`."""