Dopus Plugin for Keypirinha

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]

2 Likes