- active-window: Get focused window info (class, pid) for Niri/Hyprland - active-path: Get CWD of focused terminal window These utilities abstract compositor-specific APIs for use in scripts. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
116 lines
3.3 KiB
Bash
116 lines
3.3 KiB
Bash
#!/usr/bin/env bash
|
|
# active-path <pid>
|
|
#
|
|
# Given a PID (e.g. from active-window), output the active working directory
|
|
# or project root for that process. Handles:
|
|
# - VSCode/Electron: parses --folder-uri from the process tree
|
|
# - Terminals (kitty, etc.): finds the deepest meaningful cwd in the tree
|
|
# - Fallback: own cwd
|
|
|
|
set -o errexit
|
|
set -o nounset
|
|
set -o pipefail
|
|
|
|
PID="${1:?Usage: active-path <pid>}"
|
|
|
|
PROJECT_MARKERS=(.git Cargo.toml package.json Gemfile pyproject.toml go.mod flake.nix)
|
|
|
|
# Walk up from a path looking for a project root marker.
|
|
# Returns the marker directory, or the original path if nothing is found.
|
|
find_project_root() {
|
|
local dir="$1"
|
|
local d="$dir"
|
|
while [[ "$d" != "/" && -n "$d" ]]; do
|
|
for marker in "${PROJECT_MARKERS[@]}"; do
|
|
[[ -e "$d/$marker" ]] && { printf '%s\n' "$d"; return 0; }
|
|
done
|
|
d="$(dirname "$d")"
|
|
done
|
|
printf '%s\n' "$dir"
|
|
}
|
|
|
|
# Decode a percent-encoded URI path component (file:/// paths are rarely
|
|
# exotic, but spaces and non-ASCII chars do occur).
|
|
url_decode() {
|
|
printf '%b' "${1//%/\\x}"
|
|
}
|
|
|
|
# Read null-terminated argv of a pid and look for --folder-uri.
|
|
# Prints the decoded path and returns 0 if found, else returns 1.
|
|
folder_uri_from_cmdline() {
|
|
local pid="$1"
|
|
local prev=""
|
|
while IFS= read -r -d $'\0' arg; do
|
|
case "$arg" in
|
|
--folder-uri=file://*)
|
|
url_decode "${arg#--folder-uri=file://}"
|
|
return 0
|
|
;;
|
|
file://*)
|
|
if [[ "$prev" == "--folder-uri" ]]; then
|
|
url_decode "${arg#file://}"
|
|
return 0
|
|
fi
|
|
;;
|
|
esac
|
|
prev="$arg"
|
|
done < "/proc/$pid/cmdline" 2>/dev/null
|
|
return 1
|
|
}
|
|
|
|
# Collect PIDs of all processes in the subtree rooted at $1.
|
|
all_pids() {
|
|
local pid="$1"
|
|
printf '%s\n' "$pid"
|
|
for child in $(pgrep -P "$pid" 2>/dev/null); do
|
|
all_pids "$child"
|
|
done
|
|
}
|
|
|
|
# Given a list of PIDs, return the most specific (longest) cwd that lives
|
|
# under the user's home directory. Falls back to any non-root, non-proc cwd.
|
|
best_cwd_from_pids() {
|
|
local best=""
|
|
for pid in "$@"; do
|
|
local cwd
|
|
cwd=$(readlink -f "/proc/$pid/cwd" 2>/dev/null) || continue
|
|
[[ "$cwd" == "/" || "$cwd" == "/root" || "$cwd" == "${HOME}" ]] && continue
|
|
[[ "$cwd" =~ ^/(proc|sys|run|dev) ]] && continue
|
|
|
|
# Prefer paths under home; among those prefer longer (more specific) ones
|
|
if [[ "$cwd" == "${HOME}"/* ]]; then
|
|
if [[ -z "$best" || "${#cwd}" -gt "${#best}" ]]; then
|
|
best="$cwd"
|
|
fi
|
|
elif [[ -z "$best" ]]; then
|
|
best="$cwd"
|
|
fi
|
|
done
|
|
[[ -n "$best" ]] && printf '%s\n' "$best"
|
|
}
|
|
|
|
# --- Main ---
|
|
|
|
readarray -t TREE < <(all_pids "$PID")
|
|
|
|
# 1. VSCode / Electron: search entire process tree for --folder-uri
|
|
for pid in "${TREE[@]}"; do
|
|
if path=$(folder_uri_from_cmdline "$pid" 2>/dev/null); then
|
|
find_project_root "$path"
|
|
exit 0
|
|
fi
|
|
done
|
|
|
|
# 2. Terminal / generic: find the best cwd among all processes in the tree
|
|
# (prefers the shell or its running child over the terminal emulator itself,
|
|
# because terminal emulators typically sit at $HOME while the shell has moved)
|
|
if cwd=$(best_cwd_from_pids "${TREE[@]}"); then
|
|
find_project_root "$cwd"
|
|
exit 0
|
|
fi
|
|
|
|
# 3. Absolute fallback: own cwd, even if it's /
|
|
own=$(readlink -f "/proc/$PID/cwd" 2>/dev/null || true)
|
|
[[ -n "$own" ]] && find_project_root "$own" && exit 0
|
|
|
|
exit 1
|