#!/usr/bin/env bash # This is an attempt to replace xdg-open with something sane. usage() { cat <<- EOF sx-open [-dhv] Flags: -d Dry run -v Verbose mode -n Disable desktop notifications -h Help EOF } act() { (( verbose )) && printf 'CMD: %s\n' "$*" >&2 (( dry_run )) || { "$@"; return $?; } return 0 } notify() { [[ $DISPLAY ]] || return 3 [[ $notifier ]] || return 5 "${notifier[@]}" 'sx-open' "$@" } error() { printf '%s\n' "$*" >&2 notify "$*" } # handle_target # 1: cmd failed # 3: no handler handle_target() { declare -n result=$1 declare h cmd regex target_is_file target target_left cmd_is_template target_is_file=0 target=$2 target_left=$target if [[ -e "$target" ]]; then target_is_file=1 [[ "$target" =~ ^/.* ]] || { target="${PWD}/${target}"; } # Turn relative paths to absolute ones. IFS=';' read target_mimetype charset <<< $( file -ib "$target" ) target_left=$target_mimetype set -- "${mime_handlers[@]}" elif is_uri "$target"; then set -- "${uri_handlers[@]}" else return 2 fi while (( $# )); do cmd=( $1 ); regex=$2 for c in "${!cmd[@]}"; do if [[ "${cmd[c]}" == '%target%' ]]; then cmd_is_template=1 cmd[c]="$target" fi done (( cmd_is_template )) || cmd+=( "$target" ) if [[ "$target_left" =~ $regex ]]; then act "${cmd[@]}"; result=$? (( result )) && return 1 return 0 fi shift 2 done return 3 } # DSL uri() { declare r handler=$1; shift for r in "$@"; do uri_handlers+=( "$handler" "$r" ) done } mime() { declare r handler=$1; shift for r in "$@"; do mime_handlers+=( "$handler" "$r" ) done } scheme() { declare r handler=$1; shift for s in "$@"; do uri_handlers+=( "$handler" "^$s:" ) done } is_uri() [[ $1 =~ ^[a-zA-Z][a-zA-Z0-9\+\.\-]+:.+ ]] main() { declare cmd_result target # Source the config file. cfg_file="$HOME/.config/sx-open.cfg" [[ -f "$cfg_file" ]] && { source "$cfg_file"; } while (( $# )); do case $1 in (-d) dry_run=1; verbose=1;; (-v) verbose=1;; (-h) usage; return 0;; (-n) notify() { true; };; # disable desktop notifications (--) shift; break;; (*) break;; esac shift done target=$1; [[ "$target" ]] || { usage; exit; } (( dry_run )) && printf 'Dry run: not actually running the handler\n' >&2 # Treat file:// as local paths. [[ "$target" =~ ^file:(//)?(/.+) ]] && target=${BASH_REMATCH[2]} # Figure out if we're in X and set $notifier if we are. if [[ $DISPLAY ]]; then # Do we have notify-send? notifier=$(type -P 'notify-send') fi handle_target cmd_result "$target" case $? in (1) error "Action on “$target” failed with exit code: “$cmd_result”" return 4 ;; (2) error "No such file or directory: “$target”" return 2 ;; (3) error "No handlers found for “$target”" return 3 ;; esac return 0 } main "$@"