A Keypirinha plugin which dynamically imports DOpus aliases into the Keypirinha launcher at runtime (very fast).
Additional features:
- Allows you to browse through aliased folders using the tab key.
- Allows you to quickly launch/open aliased folders in DOpus
- Allows you to quickly open/launch files within aliased folders without opening DOpus.
- Allows you to preview files using the DOpus quickshow feature, as Keypirinha does not have a native previewer. Unfortunately, it does not currently appear possible to preview without closing the Keypirinha window.
The .ini file and .py file need to be saved in the following location.
Keypirinha\portable\Profile\Packages\DOpusAliases\DOpusAliases.py
Keypirinha\portable\Profile\Packages\DOpusAliases\DOpusAliases.ini
Keypirinha
└───portable
│
├───Profile
│
├───Packages
├───DOpusAliases
├───DOpusAliases.ini
├───DOpusAliases.py
DOpusAliases.py
# Keypirinha package: DOpusAliases
# Imports Directory Opus folder aliases directly from folderaliases.oxc.
# Also provides native actions for browsed files, including DOpus QuickShow.
import keypirinha as kp
import keypirinha_util as kpu
import os
import traceback
import xml.etree.ElementTree as ET
class DOpusAliases(kp.Plugin):
"""Catalog Directory Opus folder aliases as native Keypirinha items."""
ITEMCAT_ALIAS = kp.ItemCategory.USER_BASE + 1
ITEMCAT_ALIAS_DIR = kp.ItemCategory.USER_BASE + 2
ITEMCAT_ALIAS_FILE = kp.ItemCategory.USER_BASE + 3
CONFIG_SECTION_MAIN = "main"
CONFIG_SECTION_BROWSE = "browse"
CONFIG_SECTION_QUICKSHOW = "quickshow"
ACTION_OPEN_DOPUS = "open_dopus"
ACTION_COPY_TARGET = "copy_target"
ACTION_OPEN_EXPLORER = "open_explorer"
ACTION_FILE_OPEN = "file_open"
ACTION_FILE_QUICKSHOW = "file_quickshow"
ACTION_FILE_VIEWER = "file_viewer"
ACTION_FILE_COPY_PATH = "file_copy_path"
ACTION_FILE_OPEN_PARENT_DOPUS = "file_open_parent_dopus"
ACTION_FILE_OPEN_PARENT_EXPLORER = "file_open_parent_explorer"
ACTION_DIR_OPEN_DOPUS = "dir_open_dopus"
ACTION_DIR_OPEN_EXPLORER = "dir_open_explorer"
ACTION_DIR_COPY_PATH = "dir_copy_path"
DEFAULT_ALIAS_FILE = r"%APPDATA%\GPSoftware\Directory Opus\ConfigFiles\folderaliases.oxc"
DEFAULT_DOPUSRT_EXE = r"C:\Program Files\GPSoftware\Directory Opus\dopusrt.exe"
DEFAULT_ITEM_LABEL = r"/{alias_name}"
DEFAULT_SHORT_DESC = r"DOpus alias: {target}"
DEFAULT_HIDE_MISSING_TARGETS = False
DEFAULT_ENABLE_BROWSE_CHAIN = True
DEFAULT_SHOW_DIRS_FIRST = True
DEFAULT_SHOW_HIDDEN_FILES = True
DEFAULT_SHOW_SYSTEM_FILES = False
DEFAULT_MAX_BROWSE_ITEMS = 500
DEFAULT_QUICKSHOW_ARGUMENTS = "QUICKSHOW"
DEFAULT_VIEWER_ARGUMENTS = "USEEXISTING=no"
def __init__(self):
super().__init__()
self.alias_file = self.DEFAULT_ALIAS_FILE
self.dopusrt_exe = self.DEFAULT_DOPUSRT_EXE
self.item_label_format = self.DEFAULT_ITEM_LABEL
self.short_desc_format = self.DEFAULT_SHORT_DESC
self.hide_missing_targets = self.DEFAULT_HIDE_MISSING_TARGETS
self.enable_browse_chain = self.DEFAULT_ENABLE_BROWSE_CHAIN
self.show_dirs_first = self.DEFAULT_SHOW_DIRS_FIRST
self.show_hidden_files = self.DEFAULT_SHOW_HIDDEN_FILES
self.show_system_files = self.DEFAULT_SHOW_SYSTEM_FILES
self.max_browse_items = self.DEFAULT_MAX_BROWSE_ITEMS
self.quickshow_arguments = self.DEFAULT_QUICKSHOW_ARGUMENTS
self.viewer_arguments = self.DEFAULT_VIEWER_ARGUMENTS
self._last_alias_file_mtime = None
self._alias_actions = []
self._dir_actions = []
self._file_actions = []
def on_start(self):
self._read_config()
self._register_actions()
def _register_actions(self):
self._alias_actions = [
self.create_action(
name=self.ACTION_OPEN_DOPUS,
label="Open in Directory Opus",
short_desc="Open this alias target in Directory Opus"),
self.create_action(
name=self.ACTION_COPY_TARGET,
label="Copy target path",
short_desc="Copy the alias target path to the clipboard"),
self.create_action(
name=self.ACTION_OPEN_EXPLORER,
label="Open in Explorer",
short_desc="Open this alias target with Windows Explorer"),
]
self.set_actions(self.ITEMCAT_ALIAS, self._alias_actions)
self._dir_actions = [
self.create_action(
name=self.ACTION_DIR_OPEN_DOPUS,
label="Open folder in Directory Opus",
short_desc="Open this folder in Directory Opus"),
self.create_action(
name=self.ACTION_DIR_OPEN_EXPLORER,
label="Open folder in Explorer",
short_desc="Open this folder with Windows Explorer"),
self.create_action(
name=self.ACTION_DIR_COPY_PATH,
label="Copy folder path",
short_desc="Copy this folder path to the clipboard"),
]
self.set_actions(self.ITEMCAT_ALIAS_DIR, self._dir_actions)
self._file_actions = [
self.create_action(
name=self.ACTION_FILE_OPEN,
label="Open",
short_desc="Open this file with the default Windows action"),
self.create_action(
name=self.ACTION_FILE_QUICKSHOW,
label="Preview with DOpus QuickShow",
short_desc="Preview this file with Directory Opus QuickShow"),
self.create_action(
name=self.ACTION_FILE_VIEWER,
label="Preview with DOpus Viewer",
short_desc="Open this file in the persistent Directory Opus viewer"),
self.create_action(
name=self.ACTION_FILE_COPY_PATH,
label="Copy path",
short_desc="Copy this file path to the clipboard"),
self.create_action(
name=self.ACTION_FILE_OPEN_PARENT_DOPUS,
label="Open containing folder in Directory Opus",
short_desc="Open this file's containing folder in Directory Opus"),
self.create_action(
name=self.ACTION_FILE_OPEN_PARENT_EXPLORER,
label="Open containing folder in Explorer",
short_desc="Open this file's containing folder with Windows Explorer"),
]
self.set_actions(self.ITEMCAT_ALIAS_FILE, self._file_actions)
def on_catalog(self):
self._read_config()
alias_file = self._expand_path(self.alias_file)
if not os.path.isfile(alias_file):
self.warn("Directory Opus alias file not found: {}".format(alias_file))
self.set_catalog([])
self._last_alias_file_mtime = None
return
try:
aliases = self._parse_aliases(alias_file)
except Exception as exc:
self.err("Failed to parse Directory Opus alias file: {}".format(alias_file))
self.err(str(exc))
traceback.print_exc()
self.set_catalog([])
return
catalog = []
display_names = self._unique_display_names([a[0] for a in aliases])
for idx, alias in enumerate(aliases):
alias_name, target = alias
display_alias_name = display_names[idx]
expanded_target = self._expand_path(target)
if self.hide_missing_targets and not self._target_is_probably_valid(expanded_target):
continue
item_label = self._safe_format(
self.item_label_format,
alias_name=display_alias_name,
raw_alias_name=alias_name,
target=target,
expanded_target=expanded_target,
plugin_name=self.friendly_name())
short_desc = self._safe_format(
self.short_desc_format,
alias_name=display_alias_name,
raw_alias_name=alias_name,
target=target,
expanded_target=expanded_target,
plugin_name=self.friendly_name())
catalog.append(self.create_item(
category=self.ITEMCAT_ALIAS,
label=item_label,
short_desc=short_desc,
target=target,
args_hint=kp.ItemArgsHint.ACCEPTED,
hit_hint=kp.ItemHitHint.NOARGS,
loop_on_suggest=True))
if len(catalog) % 100 == 0 and self.should_terminate():
return
self.set_catalog(catalog)
self._last_alias_file_mtime = self._safe_mtime(alias_file)
self.info("Cataloged {} Directory Opus alias{} from {}".format(
len(catalog), "" if len(catalog) == 1 else "es", alias_file))
def on_suggest(self, user_input, items_chain):
if not self.enable_browse_chain:
return
if not items_chain:
return
current_item = items_chain[-1]
current_category = current_item.category()
if current_category == self.ITEMCAT_ALIAS:
base_dir = self._expand_path(current_item.target())
elif current_category == self.ITEMCAT_ALIAS_DIR:
base_dir = current_item.target()
else:
return
if not os.path.isdir(base_dir):
clone = current_item.clone()
clone.set_args(user_input)
clone.set_loop_on_suggest(False)
self.set_suggestions([clone], kp.Match.ANY, kp.Sort.NONE)
return
suggestions = self._browse_dir(base_dir, user_input)
self.set_suggestions(suggestions, kp.Match.ANY, kp.Sort.NONE)
def on_execute(self, item, action):
if not item:
return
category = item.category()
action_name = action.name() if action else None
if category == self.ITEMCAT_ALIAS:
self._execute_alias(item, action_name)
return
if category == self.ITEMCAT_ALIAS_DIR:
self._execute_dir(item, action_name)
return
if category == self.ITEMCAT_ALIAS_FILE:
self._execute_file(item, action_name)
return
def on_events(self, flags):
if flags & kp.Events.PACKCONFIG:
self.on_catalog()
def on_activated(self):
# Keypirinha does not watch Directory Opus' private config file.
# Re-catalog when the launcher is opened and folderaliases.oxc changed.
alias_file = self._expand_path(self.alias_file)
current_mtime = self._safe_mtime(alias_file)
if current_mtime is not None and current_mtime != self._last_alias_file_mtime:
self.on_catalog()
def _execute_alias(self, item, action_name):
target = item.target()
expanded_target = self._expand_path(target)
if action_name == self.ACTION_COPY_TARGET:
kpu.set_clipboard(expanded_target)
return
if action_name == self.ACTION_OPEN_EXPLORER:
self._open_in_explorer(expanded_target)
return
self._open_in_dopus(target)
def _execute_dir(self, item, action_name):
folder_path = item.target()
if action_name == self.ACTION_DIR_COPY_PATH:
kpu.set_clipboard(folder_path)
return
if action_name == self.ACTION_DIR_OPEN_EXPLORER:
self._open_in_explorer(folder_path)
return
self._open_in_dopus(folder_path)
def _execute_file(self, item, action_name):
file_path = item.target()
if action_name == self.ACTION_FILE_QUICKSHOW:
self._show_in_dopus(file_path, self.quickshow_arguments)
return
if action_name == self.ACTION_FILE_VIEWER:
self._show_in_dopus(file_path, self.viewer_arguments)
return
if action_name == self.ACTION_FILE_COPY_PATH:
kpu.set_clipboard(file_path)
return
if action_name == self.ACTION_FILE_OPEN_PARENT_DOPUS:
self._open_in_dopus(os.path.dirname(file_path))
return
if action_name == self.ACTION_FILE_OPEN_PARENT_EXPLORER:
self._open_in_explorer(os.path.dirname(file_path))
return
self._open_file_default(file_path)
def _browse_dir(self, base_dir, search_terms):
search_terms = (search_terms or "").strip()
suggestions = []
try:
names = os.listdir(base_dir)
except OSError as exc:
return [self.create_error_item(
label="Cannot browse folder",
short_desc="{}: {}".format(base_dir, exc))]
dir_items = []
file_items = []
for name in names:
if self.should_terminate():
return []
full_path = os.path.join(base_dir, name)
try:
st = os.stat(full_path)
except OSError:
continue
if not self._should_show_path(name, full_path, st):
continue
match_score = None
if search_terms:
if search_terms.lower() in name.lower():
match_score = 100000 - len(name)
else:
try:
fuzzy_score = kpu.fuzzy_score(search_terms, name)
except Exception:
fuzzy_score = None
if not fuzzy_score:
continue
match_score = fuzzy_score
if os.path.isdir(full_path):
dir_items.append((match_score, name.lower(), self.create_item(
category=self.ITEMCAT_ALIAS_DIR,
label=name,
short_desc=full_path,
target=full_path,
args_hint=kp.ItemArgsHint.ACCEPTED,
hit_hint=kp.ItemHitHint.KEEPALL,
loop_on_suggest=True)))
else:
file_items.append((match_score, name.lower(), self.create_item(
category=self.ITEMCAT_ALIAS_FILE,
label=name,
short_desc=full_path,
target=full_path,
args_hint=kp.ItemArgsHint.FORBIDDEN,
hit_hint=kp.ItemHitHint.KEEPALL,
loop_on_suggest=False)))
if search_terms:
dir_items.sort(key=lambda x: (-(x[0] or 0), x[1]))
file_items.sort(key=lambda x: (-(x[0] or 0), x[1]))
else:
dir_items.sort(key=lambda x: x[1])
file_items.sort(key=lambda x: x[1])
if self.show_dirs_first:
ordered = dir_items + file_items
else:
ordered = sorted(dir_items + file_items, key=lambda x: x[1])
for _, __, item in ordered[:self.max_browse_items]:
suggestions.append(item)
return suggestions
def _should_show_path(self, name, full_path, st):
if name in (".", ".."):
return False
attrs = getattr(st, "st_file_attributes", 0)
hidden = bool(attrs & 0x2)
system = bool(attrs & 0x4)
if hidden and not self.show_hidden_files:
return False
if system and not self.show_system_files:
return False
return True
def _read_config(self):
settings = self.load_settings()
self.alias_file = settings.get_stripped(
"alias_file", self.CONFIG_SECTION_MAIN, self.DEFAULT_ALIAS_FILE)
self.dopusrt_exe = settings.get_stripped(
"dopusrt_exe", self.CONFIG_SECTION_MAIN, self.DEFAULT_DOPUSRT_EXE)
self.item_label_format = settings.get_stripped(
"item_label", self.CONFIG_SECTION_MAIN, self.DEFAULT_ITEM_LABEL)
self.short_desc_format = settings.get_stripped(
"short_desc", self.CONFIG_SECTION_MAIN, self.DEFAULT_SHORT_DESC)
self.hide_missing_targets = settings.get_bool(
"hide_missing_targets", self.CONFIG_SECTION_MAIN, self.DEFAULT_HIDE_MISSING_TARGETS)
self.enable_browse_chain = settings.get_bool(
"enable_browse_chain", self.CONFIG_SECTION_BROWSE, self.DEFAULT_ENABLE_BROWSE_CHAIN)
self.show_dirs_first = settings.get_bool(
"show_dirs_first", self.CONFIG_SECTION_BROWSE, self.DEFAULT_SHOW_DIRS_FIRST)
self.show_hidden_files = settings.get_bool(
"show_hidden_files", self.CONFIG_SECTION_BROWSE, self.DEFAULT_SHOW_HIDDEN_FILES)
self.show_system_files = settings.get_bool(
"show_system_files", self.CONFIG_SECTION_BROWSE, self.DEFAULT_SHOW_SYSTEM_FILES)
self.max_browse_items = settings.get_int(
"max_browse_items", self.CONFIG_SECTION_BROWSE, self.DEFAULT_MAX_BROWSE_ITEMS,
min=1, max=5000)
self.quickshow_arguments = settings.get_stripped(
"quickshow_arguments", self.CONFIG_SECTION_QUICKSHOW, self.DEFAULT_QUICKSHOW_ARGUMENTS)
self.viewer_arguments = settings.get_stripped(
"viewer_arguments", self.CONFIG_SECTION_QUICKSHOW, self.DEFAULT_VIEWER_ARGUMENTS)
def _parse_aliases(self, alias_file):
tree = ET.parse(alias_file)
root = tree.getroot()
aliases = []
seen = set()
for path_node in root.findall(".//path"):
alias_name = (path_node.get("label") or path_node.get("name") or "").strip()
if not alias_name:
continue
pathstring_node = path_node.find("./dir/pathstring")
if pathstring_node is None or pathstring_node.text is None:
continue
target = pathstring_node.text.strip()
if not target:
continue
key = (alias_name.casefold(), target.casefold())
if key in seen:
continue
seen.add(key)
aliases.append((alias_name, target))
if len(aliases) % 100 == 0 and self.should_terminate():
return []
return aliases
def _open_in_dopus(self, target):
dopusrt = self._expand_path(self.dopusrt_exe)
if not os.path.isfile(dopusrt):
self.err("dopusrt.exe not found: {}".format(dopusrt))
return
try:
kpu.shell_execute(
dopusrt,
args=["/cmd", "Go", target],
verb="",
detect_nongui=False)
except Exception:
self.err("Failed to open Directory Opus target: {}".format(target))
traceback.print_exc()
def _show_in_dopus(self, file_path, extra_arguments):
dopusrt = self._expand_path(self.dopusrt_exe)
if not os.path.isfile(dopusrt):
self.err("dopusrt.exe not found: {}".format(dopusrt))
return
args = ["/cmd", "Show", file_path]
args.extend(self._split_extra_args(extra_arguments))
try:
kpu.shell_execute(
dopusrt,
args=args,
verb="",
detect_nongui=False)
except Exception:
self.err("Failed to show file in Directory Opus: {}".format(file_path))
traceback.print_exc()
def _open_file_default(self, file_path):
try:
kpu.shell_execute(file_path, detect_nongui=False)
except Exception:
self.err("Failed to open file: {}".format(file_path))
traceback.print_exc()
def _open_in_explorer(self, path):
try:
kpu.shell_execute(path, detect_nongui=False)
except Exception:
self.err("Failed to open in Explorer: {}".format(path))
traceback.print_exc()
def _split_extra_args(self, value):
value = (value or "").strip()
if not value:
return []
try:
return kpu.cmdline_split(value)
except Exception:
return value.split()
def _expand_path(self, value):
if value is None:
return ""
return os.path.normpath(os.path.expandvars(value.strip()))
def _safe_mtime(self, path):
try:
return os.path.getmtime(path)
except OSError:
return None
def _target_is_probably_valid(self, target):
if not target:
return False
if target.startswith("::"):
return True
if ":" not in target and not target.startswith("\\\\"):
return True
return os.path.exists(target)
def _safe_format(self, fmt, **kwargs):
try:
return fmt.format(**kwargs)
except Exception:
return fmt
def _unique_display_names(self, names):
used = {}
result = []
for name in names:
base = name.strip() or "Unnamed Alias"
key = base.casefold()
count = used.get(key, 0) + 1
used[key] = count
if count == 1:
result.append(base)
else:
result.append("{} {}".format(base, count))
return result
DOpusAliases.ini
#
# DOpusAliases Package configuration file
# Imports Directory Opus folder aliases from folderaliases.oxc as native
# Keypirinha catalog items, with native file actions including DOpus QuickShow.
#
[main]
# Directory Opus folder alias config file.
# Default is the current user's normal DOpus config path.
#alias_file = %APPDATA%\GPSoftware\Directory Opus\ConfigFiles\folderaliases.oxc
# Directory Opus runtime executable.
#dopusrt_exe = C:\Program Files\GPSoftware\Directory Opus\dopusrt.exe
# Catalog item display. Useful default: type / in Keypirinha to surface aliases.
# Available placeholders:
# {alias_name} de-duplicated alias name
# {raw_alias_name} original alias name from folderaliases.oxc
# {target} raw target from folderaliases.oxc
# {expanded_target} target after environment expansion
# {plugin_name} package/plugin display name
item_label = /{alias_name}
# Description shown under each catalog item.
short_desc = DOpus alias: {target}
# If yes, normal filesystem aliases whose targets do not exist are skipped.
# DOpus virtual paths and namespaces are still kept.
hide_missing_targets = no
[browse]
# After selecting an alias or browsed folder, allow browsing its contents inside Keypirinha.
enable_browse_chain = yes
show_dirs_first = yes
show_hidden_files = yes
show_system_files = no
max_browse_items = 500
[quickshow]
# Arguments appended to: dopusrt.exe /cmd Show "<file>"
# For the transient DOpus QuickShow preview:
quickshow_arguments = QUICKSHOW
# For the persistent Directory Opus viewer:
viewer_arguments = USEEXISTING=no
[var]
[env]
