175 lines
4.9 KiB
Bash
Executable File
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
|