Replace --exec-arg with an ergonomic --exec that consumes the rest of the line as the click command. The caller's shell tokenizes the words into discrete arguments before the tool sees them, and the shell runs them as positional parameters (never a re-parsed string), so safety is identical to the argv form while the call sites read naturally: `--exec omarchy toggle something`. Crucially the tool never splits a string itself — a single quoted whole-command argument is rejected and points at the unquoted form, because whitespace- splitting a string hands argument boundaries to whoever controls its content (the injection we are avoiding). --exec must come last; migrate every caller.
100 lines
2.6 KiB
Bash
Executable File
100 lines
2.6 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# omarchy:summary=Save incoming Taildrop files and announce them
|
|
# omarchy:args=[--once] [directory]
|
|
# omarchy:examples=omarchy tailscale receive | omarchy tailscale receive --once ~/Desktop
|
|
|
|
set -euo pipefail
|
|
|
|
once=false
|
|
if [[ ${1:-} == "--once" ]]; then
|
|
once=true
|
|
shift
|
|
fi
|
|
|
|
dir="${1:-${XDG_DOWNLOAD_DIR:-$HOME/Downloads}}"
|
|
|
|
# Taildrop lands in a staging directory next door rather than straight in the
|
|
# downloads directory: waiting for a delivery can take hours, and everything
|
|
# else that shows up meanwhile is somebody else's file. Same filesystem, so
|
|
# handing the finished file over is a rename.
|
|
staging="$dir/.omarchy-taildrop"
|
|
mkdir -p "$staging"
|
|
|
|
# Take the name by linking to it rather than by looking and then renaming.
|
|
# link(2) refuses an existing name, so nothing can land on the chosen one in
|
|
# the gap between the two. Staging shares the filesystem with the downloads
|
|
# directory, so the link always resolves and unlinking the staged name
|
|
# finishes the move. Prints the name it took.
|
|
claim_path() {
|
|
local staged="$1" name="${staged##*/}" base ext candidate index=0
|
|
|
|
base="${name%.*}"
|
|
ext="${name#"$base"}"
|
|
[[ -z $base ]] && { base="$name"; ext=""; }
|
|
|
|
while (( index < 1000 )); do
|
|
if (( index == 0 )); then
|
|
candidate="$dir/$name"
|
|
else
|
|
candidate="$dir/$base-$index$ext"
|
|
fi
|
|
|
|
if ln -- "$staged" "$candidate" 2>/dev/null; then
|
|
rm -f -- "$staged"
|
|
printf '%s\n' "$candidate"
|
|
return 0
|
|
fi
|
|
|
|
# Only a taken name is worth another spin. Anything else failed the link
|
|
# itself, and the file keeps its place in staging for the next run.
|
|
[[ -e $candidate ]] || return 1
|
|
|
|
((index++))
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
announce() {
|
|
local path="$1"
|
|
local name="${path##*/}"
|
|
local args=("Received $name" "Saved to ${dir/#$HOME/~}")
|
|
|
|
case "${name,,}" in
|
|
*.png | *.jpg | *.jpeg | *.gif | *.webp | *.avif | *.bmp | *.tif | *.tiff)
|
|
args+=(--image "$path")
|
|
;;
|
|
*)
|
|
args+=(-g )
|
|
;;
|
|
esac
|
|
|
|
# Announcing is best-effort: the file is already delivered, and under `set -e`
|
|
# a notification outage would otherwise kill the long-running receiver.
|
|
omarchy-notification-send "${args[@]}" --exec xdg-open "$path" || true
|
|
}
|
|
|
|
deliver() {
|
|
local staged target
|
|
|
|
while IFS= read -r staged; do
|
|
target=$(claim_path "$staged") || continue
|
|
announce "$target"
|
|
done < <(find "$staging" -mindepth 1 -maxdepth 1)
|
|
}
|
|
|
|
# Anything left staged by an interrupted run still deserves delivering.
|
|
deliver
|
|
|
|
while true; do
|
|
if ! tailscale file get --wait --conflict=rename "$staging"; then
|
|
$once && exit 1
|
|
sleep 10
|
|
continue
|
|
fi
|
|
|
|
deliver
|
|
$once && exit 0
|
|
done
|