-
-
Save Biont/40ef59652acf3673520c7a03c9f22d2a to your computer and use it in GitHub Desktop.
#!/usr/bin/env bash | |
# terminal application launcher for sway, using fzf | |
# Based on: https://gitlab.com/FlyingWombat/my-scripts/blob/master/sway-launcher | |
shopt -s nullglob | |
if [[ "$1" == 'describe' ]]; then | |
shift | |
if [[ $2 == 'command' ]]; then | |
title=$1 | |
readarray arr < <(whatis -l "$1" 2>/dev/null) | |
description="${arr[0]}" | |
description="${description%*-}" | |
else | |
title=$(sed -ne '/^Name=/{s/^Name=//;p;q}' "$1") | |
description=$(sed -ne '/^Comment=/{s/^Comment=//;p;q}' "$1") | |
fi | |
echo -e "\033[33m$title\033[0m" | |
echo "${description:-No description}" | |
exit | |
fi | |
HIST_FILE="${XDG_CACHE_HOME:-$HOME/.cache}/${0##*/}-history.txt" | |
DIRS=( | |
/usr/share/applications | |
"$HOME/.local/share/applications" | |
/usr/local/share/applications | |
) | |
GLYPH_COMMAND=" " | |
GLYPH_DESKTOP=" " | |
touch "$HIST_FILE" | |
readarray HIST_LINES <"$HIST_FILE" | |
FZFPIPE=$(mktemp) | |
PIDFILE=$(mktemp) | |
trap 'rm "$FZFPIPE" "$PIDFILE"' EXIT INT | |
# Append Launcher History, removing usage count | |
(printf '%s' "${HIST_LINES[@]#* }" >>"$FZFPIPE") & | |
# Load and append Desktop entries | |
( | |
for dir in "${DIRS[@]}"; do | |
[[ -d "$dir" ]] || continue | |
awk -v pre="$GLYPH_DESKTOP" -F= ' | |
BEGINFILE{application=0;block="";a=0} | |
/^\[Desktop Entry\]/{block="entry"} | |
/^Type=Application/{application=1} | |
/^\[Desktop Action/{ | |
sub("^\\[Desktop Action ", ""); | |
sub("\\]$", ""); | |
block="action"; | |
a++; | |
actions[a,"key"]=$0 | |
} | |
/^Name=/{ | |
if(block=="action") { | |
actions[a,"name"]=$2; | |
} else { | |
name=$2 | |
} | |
} | |
ENDFILE{ | |
if (application){ | |
print FILENAME "\034desktop\034\033[33m" pre name "\033[0m"; | |
if (a>0) | |
for (i=1; i<=a; i++) | |
print FILENAME "\034desktop\034\033[33m" pre name "\033[0m (" actions[i, "name"] ")\034" actions[i, "key"] | |
} | |
}' \ | |
"$dir/"*.desktop </dev/null >>"$FZFPIPE" | |
# the empty stdin is needed in case no *.desktop files | |
done | |
) & | |
# Load and append command list | |
( | |
IFS=: | |
read -ra path <<<"$PATH" | |
for dir in "${path[@]}"; do | |
printf '%s\n' "$dir/"* | | |
awk -F / -v pre="$GLYPH_COMMAND" '{print $NF "\034command\034\033[31m" pre "\033[0m" $NF;}' | |
done | sort -u >>"$FZFPIPE" | |
) & | |
COMMAND_STR=$( | |
( | |
tail -n +0 -f "$FZFPIPE" & | |
echo $! >"$PIDFILE" | |
) | | |
fzf +s -x -d '\034' --nth ..3 --with-nth 3 \ | |
--preview "$0 describe {1} {2}" \ | |
--preview-window=up:3:wrap --ansi | |
kill -9 "$(<"$PIDFILE")" | tail -n1 | |
) || exit 1 | |
[ -z "$COMMAND_STR" ] && exit 1 | |
# update history | |
for i in "${!HIST_LINES[@]}"; do | |
if [[ "${HIST_LINES[i]}" == *" $COMMAND_STR"$'\n' ]]; then | |
HIST_COUNT=${HIST_LINES[i]%% *} | |
HIST_LINES[$i]="$((HIST_COUNT + 1)) $COMMAND_STR"$'\n' | |
match=1 | |
break | |
fi | |
done | |
if ! ((match)); then | |
HIST_LINES+=("1 $COMMAND_STR"$'\n') | |
fi | |
printf '%s' "${HIST_LINES[@]}" | sort -nr >"$HIST_FILE" | |
command='echo "nope"' | |
# shellcheck disable=SC2086 | |
readarray -d $'\034' -t PARAMS <<<${COMMAND_STR} | |
# COMMAND_STR is "<string>\034<type>" | |
case ${PARAMS[1]} in | |
desktop) | |
# Define the search pattern that specifies the block to search for within the .desktop file | |
PATTERN="^\\\\[Desktop Entry\\\\]" | |
if [[ -n ${PARAMS[3]} ]]; then | |
PATTERN="^\\\\[Desktop Action ${PARAMS[3]%?}\\\\]" | |
fi | |
# 1. We see a line starting [Desktop, but we're already searching: deactivate search again | |
# 2. We see the specified pattern: start search | |
# 3. We see an Exec= line during search: remove field codes and set variable | |
# 3. We see a Path= line during search: set variable | |
# 4. Finally, build command line | |
command=$(awk -v pattern="${PATTERN}" -F= ' | |
BEGIN{a=0;exec=0; path=0} | |
/^\[Desktop/{ | |
if(a){ | |
a=0 | |
} | |
} | |
$0 ~ pattern{ | |
a=1 | |
} | |
/^Exec=/{ | |
if(a && !exec){ | |
sub("^Exec=", ""); | |
gsub(" ?%[cDdFfikmNnUuv]", ""); | |
exec=$0; | |
} | |
} | |
/^Path=/{ | |
if(a && !path){ | |
path=$2 | |
} | |
} | |
END{ | |
if(path){ | |
print "cd " path " &&" | |
} | |
print exec | |
}' "${PARAMS[0]}") | |
;; | |
command) | |
command="${PARAMS[0]}" | |
;; | |
esac | |
swaymsg -t command exec "$command" |
@Biont I updated the script to handle Terminal=true
in desktop files so they open in a terminal (my original use case for this was opening ranger
). Seems like this could be useful in the core script, but it's currently hardcoded to use termite
. I could extract it into an environment variable for now, probably the simplest way to solve the problem without having to search for different terminals. Seems like +1 use case for configuration.
@nstickney @joefiorini Thank you both! I have created an actual repository for this project so we can use issues and PRs in the future: https://github.com/Biont/sway-launcher-desktop/tree/master
I will look at your suggestions asap.
@Biont I just updated mine to make the terminal command an env var so it's easier to change. I'll send a PR with this update to make it easier for you to review and pull in if you want it.
is there any way i can make it look into /opt? adding it to DIRS doesn't seem to do the trick... or read .desktop files, that would work as well.
So, I've found another issue, and I believe I have a solution, but I'm a n00b here, so check me.
PROBLEM:
Some *.desktop files include not just the command, but also environment variables, in the Exec line, i.e. "
Exec=env GDK_BACKEND=x11 /opt/minecraft-launcher/minecraft-launcher
". This is apparently a standard pattern, butswaymsg -t command "$command"
leads toError: Unknown/invalid command 'env'
. I tried changing theExec
line to/usr/bin/env
, but I get a similar message:Error: Unknown/invalid command '/usr/bin/env'
.SOLUTION:
I changed the way the
$command
variable is formatted by removing the line break and replacing it with a space. Then, when running the command, I cut outenv
using bash string substitution.EDIT:
Note, I also changed the ordering of the
$DIRS
hoping to make the~/.local
entry override the others, but I don't believe it has any effect.