mimix/xdg-open

175 lines
4.9 KiB
Bash
Executable File

#!/bin/sh
set -euf
BN="${0##*/}"
### HELPER FUNCTIONS
exists() { type "$1" >/dev/null 2>&1; }
errecho() { echo "$*" >&2 ; }
die() {
errecho "$*"
exists notify-send && notify-send "$*"
exit 1
}
config_search() {
# substitute $ if search is empty to match and return nothing
grep -m 1 "^${1:-$}:" "$CONFIG" | cut -d ':' -f 2- | awk '{$1=$1}1'
}
# grab a list of compatible desktop files with the value, pick the one that has the key in the earliest index
desktop_search_by() {
[ -z "$2" ] && return
key="$1" val="$2"
set -- '/usr/share/applications'
if [ -d "${XDG_DATA_HOME:-$HOME/.local/share}/applications" ]; then
set -- "${XDG_DATA_HOME:-$HOME/.local/share}/applications" "$@"
fi
# NOTE: not posix compliant: grep -m -R
# but it's supported in at least coreutils, *bsd, and busybox, so I think we're fine
grep -m 1 -e "^$key=.*$val" -R "$@" | awk -F : -v pat="$val" '{ print index($2, pat), length($2), $1 }' | sort -t ' ' -k1,1n -k2,2nr | awk '{ print $3; exit }'
}
desktop_exec_parse() {
# finds an exec line and removes all of the xdg-isms, leaving just a *command line*
# this command line ~should~ conform to "typing in a shell" type stuff.
# *every single desktop file* I have seen does *not* use any of the %. substitutions
# as something other than the final argument. I don't know why the different ones exist anyway
grep -m 1 -e '^Exec' "$@" | sed -e 's/^Exec=//' -e 's/ \{0,1\}%. \{0,1\}/ /g' -e 's/^"//g' -e 's/" *$//g'
}
desktop_term_check() {
grep -m 1 -q '^Terminal=true$' "$@"
}
fork_run() {
if tty -s; then # interactive, run normally
exec sh -c "exec $*"
else
nohup sh -c "exec $*" >/dev/null 2>&1 &
fi
exit 0
}
urldecode() {
# random chinese github repo to the rescue
# https://github.com/mjwtc0722/awk_urldecode
awk 'BEGIN {
for (i = 0; i < 10; i++)
hex[i] = i;
hex["A"]=hex["a"]=10;
hex["B"]=hex["b"]=11;
hex["C"]=hex["c"]=12;
hex["D"]=hex["d"]=13;
hex["E"]=hex["e"]=14;
hex["F"]=hex["f"]=15;
}
{
gsub(/\+/, " ");
i = $0;
while (match(i, /%../)) {
printf "%s", substr(i, 1, RSTART-1);
printf "%c", hex[substr(i, RSTART+1, 1)] * 16 + hex[substr(i, RSTART+2, 1)];
i = substr(i, RSTART+RLENGTH);
}
print i
}' <<-EOF
$*
EOF
}
usage() { >&2 cat <<-EOF
usage: $BN [file|directory|protocol]
that's all there is to it!
config files are checked in the following order:
- ${XDG_CONFIG_HOME}/mimix/mimix.conf
- ${XDG_CONFIG_HOME}/mimix.conf
- $HOME/.mimix.conf
exit code 1 on error
EOF
}
### SCRIPT START
# get config path
XDG_CONFIG_HOME="${XDG_CONFIG_HOME:-$HOME/.config}"
[ -z "$*" ] && usage && exit 1
ARG="$*"
for path in \
"$XDG_CONFIG_HOME/mimix/mimix.conf" \
"$XDG_CONFIG_HOME/mimix.conf" \
"$HOME/.mimix.conf"; do
[ -f "$path" ] && CONFIG="$path" && break
done
[ -z "${CONFIG:-}" ] && usage && die 'Error: no config file found!'
# special variables (TERMINAL, MENU)
conf_term="$(config_search TERMINAL)"
[ -n "$conf_term" ] && TERMINAL="$conf_term"
! exists "$TERMINAL" && die 'Error: $TERMINAL is not executable! Try setting it in the config.'
export TERMINAL
MENU="$(config_search MENU)" || MENU=dmenu
# if the argument is file://<something>, strip and open in default handler
# note that this is *not* the same behavior as mimi, but I don't think anyone relied on this
# this matches freedesktop's xdg-open, including stripping #section stuff
# unused match just in case: expr -- "$ARG" : '^file://[^#]*\.html\{0,1\}.*$'
if rematch="$(expr -- "$ARG" : 'file://\([^#?]\{1,\}\)')"; then
ARG="$(urldecode "$rematch")"
fi
# ext and mime handling
ext="" mime=""
if [ -e "$ARG" ]; then
ext="\\.$(tr '[:upper:]' '[:lower:]' <<-EOF
${ARG##*.}
EOF
)"
mime="$(file -biL "$ARG" | cut -d ';' -f 1)"
fi
# check if it's in a protocol-esque form, if so then make mime extension
rematch="$(expr -- "$ARG" : '\([a-zA-Z-]\{1,\}\):' | tr '[:upper:]' '[:lower:]')"
if [ -n "$rematch" ]; then
mime="x-scheme-handler/$rematch"
fi
# make a general mime if normal mime exists
general_mime=""
[ -n "$mime" ] && general_mime="${mime%%/*}/"
# escape quotes in the xdg-open arg so sh doesn't double-evaluate the arg
ARG="$(printf "'%s'" "$(printf '%s' "$ARG" | sed "s/'/'\\\\''/g")")"
# config
for search in "$ext" "$mime" "$general_mime"; do
cmd="$(config_search "$search")"
[ -n "$cmd" ] && fork_run "$cmd $ARG"
done
# .desktop
for search in "$mime" "$general_mime"; do
desktop="$(desktop_search_by MimeType "$search")"
if [ -n "$desktop" ]; then
errecho "$desktop"
cmd="$(desktop_exec_parse "$desktop")"
if desktop_term_check "$desktop"; then
fork_run "$TERMINAL -e $cmd $ARG"
else
fork_run "$cmd $ARG"
fi
fi
done
# ask
if exists "$MENU"; then
cmd="$(IFS=':'; set -- $PATH; find "$@" \( -type f -o -type l \) | awk -F '/' '{print $NF}' | sort | uniq | eval "$MENU"' -p "How open ${ARG}?"')"
if [ -n "$cmd" ]; then
fork_run "$cmd $ARG"
else die 'Blank command from menu, exiting...'
fi
else die 'Could not find an opener nor menu, exiting...'
fi