aboutsummaryrefslogtreecommitdiff
path: root/.config/otter-launcher
diff options
context:
space:
mode:
Diffstat (limited to '.config/otter-launcher')
-rw-r--r--.config/otter-launcher/config.toml126
-rwxr-xr-x.config/otter-launcher/sway-launcher-desktop.sh390
2 files changed, 516 insertions, 0 deletions
diff --git a/.config/otter-launcher/config.toml b/.config/otter-launcher/config.toml
new file mode 100644
index 0000000..a0a53ab
--- /dev/null
+++ b/.config/otter-launcher/config.toml
@@ -0,0 +1,126 @@
+[general]
+default_module = "app" # The module to run when no prefix is matched
+empty_module = "a" # run with an empty prompt
+exec_cmd = "sh -c" # The exec command of your shell, default to sh
+# for example: "bach -c" for bash; "zsh -c" for zsh. This can also runs wm exec commands, like hyprctl dispatch exec
+vi_mode = true # set true to use vi keybinds, false to use emacs keybinds; default to emacs
+esc_to_abort = true # allow to quit with esc keypress; a useful option for vi users
+cheatsheet_entry = "?" # when entered, otter-launcher will show a list of configured modules
+cheatsheet_viewer = "less -R; clear" # the program that otter-launcher will pipe cheatsheet into
+clear_screen_after_execution = false # useful when chafa image flash back after module execution
+loop_mode = false # in loop mode, otter-launcher won't quit after running a module, useful when using scratchpad
+external_editor = "" # if set, pressing ctrl+e (or pressing v in vi normal mode) will edit the input field in the specified program; default to no external editor
+#callback = "" # if set, otter-launcher will run the command after a module is executed; for example, it can call swaymsg to adjust window size
+
+# ASCII color codes are allowed with these options. However, \x1b should be replaced with \u001B (unicode escape) because the rust toml crate cannot read \x as an escaped character...
+[interface]
+# use three quotes to write longer commands
+header = """
+ \u001B[34;1m >\u001B[0m $USER@$(echo $HOSTNAME) \u001B[31m\u001B[0m $(cat /proc/loadavg | cut -d ' ' -f 1) \u001B[33m󰍛\u001B[0m $(free -h | awk 'FNR == 2 {print $3}' | sed 's/i//')
+ \u001B[34;1m>\u001B[0;1m """
+# Run a shell command and make the stdout printed above the header
+header_cmd = ""
+header_cmd_trimmed_lines = 0 # Remove a number of lines from header_cmd output, in case of some programs printing excessive empty lines at the end of its output
+header_concatenate = false # print header and header_cmd output at the same line, default to false
+list_prefix = " "
+selection_prefix = " \u001B[31;1m> "
+place_holder = "type and search"
+default_module_message = " \u001B[33msearch\u001B[0m the internet" # if set, the text will be shown when the default module is in use
+empty_module_message = "" # the text to show when empty module is in use
+suggestion_mode = "list" # available options: list, hint
+suggestion_lines = 12 # length of the suggestion list, set to 0 to disable suggestions and tab completion
+indicator_with_arg_module = "\u001B[31m^\u001B[0m " # a sign showing whether the module should run with an argument
+indicator_no_arg_module = "\u001B[31m$\u001B[0m "
+prefix_padding = 3 # format prefixes to have a uniformed width; prefixes will be padded with spaces to have a least specified number of chars
+# below color options affect all modules; per-module coloring is allowed by using ascii color codes at each module's configurations
+prefix_color = "\u001B[33m"
+description_color = "\u001B[39m"
+place_holder_color = "\u001B[30m"
+hint_color = "\u001B[30m" # the color of hint mode suggestions
+# move the whole interface rightward or upward, easier for styling with chafa image
+move_right = 0
+move_up = 0
+
+
+[[modules]]
+description = "search with brave"
+prefix = "br"
+cmd = "setsid -f xdg-open 'https://search.brave.com/search?q={}'"
+with_argument = true
+url_encode = true
+
+[[modules]]
+description = "kill a runing app"
+prefix = "k"
+cmd = 'ps -u "$USER" -o comm= | sort -u | fsel --dmenu | xargs -r pkill -9'
+with_argument = true
+url_encode = true
+
+[[modules]]
+description = "launch apps with fsel"
+prefix = "a"
+cmd = "fsel -vv -r -d -ss \"{}\""
+with_argument = true
+
+[[modules]]
+description = "launch apps instantly"
+prefix = "app"
+cmd = "fsel -vv -r -d -p {}"
+with_argument = true
+
+[[modules]]
+description = "manage clipboard with fsel"
+prefix = "cl"
+cmd = """
+fsel --cclip
+"""
+
+[[modules]]
+description = "find pkgs"
+prefix = "pm"
+cmd = "pmux -SD {}"
+with_argument = true
+
+[[modules]]
+description = "install pkgs"
+prefix = "i"
+cmd = "pmux -S {}"
+with_argument = true
+
+[[modules]]
+description = "power menu with fzf"
+prefix = "p"
+cmd = """
+function power {
+if [[ -n $1 ]]; then
+case $1 in
+"logout") session=`loginctl session-status | head -n 1 | awk '{print $1}'`; loginctl terminate-session $session ;;
+"suspend") systemctl suspend ;;
+"hibernate") systemctl hibernate ;;
+"reboot") systemctl reboot ;;
+"shutdown") systemctl poweroff ;;
+esac fi }
+power $(echo -e 'reboot\nshutdown\nlogout\nsuspend\nhibernate' | fzf --reverse --no-scrollbar --padding 1,3 --prompt 'Power Menu: ' | tail -1)
+"""
+
+[[modules]]
+description = "run command in terminal"
+prefix = "s"
+cmd = """
+setsid -f "$(echo $TERM | sed 's/xterm-//g')" -e {}
+"""
+with_argument = true
+
+[[modules]]
+description = "search archwiki"
+prefix = "w"
+cmd = "setsid -f xdg-open https://wiki.archlinux.org/index.php?search='{}'"
+with_argument = true
+url_encode = true
+
+[[modules]]
+description = "cambridge dictionary"
+prefix = "dc"
+cmd = "setsid -f xdg-open 'https://dictionary.cambridge.org/dictionary/english/{}'"
+with_argument = true
+url_encode = true
diff --git a/.config/otter-launcher/sway-launcher-desktop.sh b/.config/otter-launcher/sway-launcher-desktop.sh
new file mode 100755
index 0000000..0153175
--- /dev/null
+++ b/.config/otter-launcher/sway-launcher-desktop.sh
@@ -0,0 +1,390 @@
+#!/usr/bin/env bash
+# terminal application launcher for sway, using fzf
+# Based on: https://gitlab.com/FlyingWombat/my-scripts/blob/master/sway-launcher
+# https://gist.github.com/Biont/40ef59652acf3673520c7a03c9f22d2a
+shopt -s nullglob globstar
+set -o pipefail
+if ! { exec 0>&3; } 1>/dev/null 2>&1; then
+ exec 3>/dev/null # If file descriptor 3 is unused in parent shell, output to /dev/null
+fi
+# shellcheck disable=SC2154
+trap 's=$?; echo "$0: Error on line "$LINENO": $BASH_COMMAND"; exit $s' ERR
+IFS=$'\n\t'
+DEL=$'\34'
+
+FZF_COMMAND="${FZF_COMMAND:=fzf}"
+TERMINAL_COMMAND="${TERMINAL_COMMAND:="$TERMINAL -e"}"
+GLYPH_COMMAND="${GLYPH_COMMAND- }"
+GLYPH_DESKTOP="${GLYPH_DESKTOP- }"
+CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/sway-launcher-desktop"
+PROVIDERS_FILE="${PROVIDERS_FILE:=providers.conf}"
+if [[ "${PROVIDERS_FILE#/}" == "${PROVIDERS_FILE}" ]]; then
+ # $PROVIDERS_FILE is a relative path, prepend $CONFIG_DIR
+ PROVIDERS_FILE="${CONFIG_DIR}/${PROVIDERS_FILE}"
+fi
+if [[ ! -v PREVIEW_WINDOW ]]; then
+ PREVIEW_WINDOW=up:2:noborder
+fi
+
+# Provider config entries are separated by the field separator \034 and have the following structure:
+# list_cmd,preview_cmd,launch_cmd,purge_cmd
+declare -A PROVIDERS
+if [ -f "${PROVIDERS_FILE}" ]; then
+ eval "$(awk -F= '
+ BEGINFILE{ provider=""; }
+ /^\[.*\]/{sub("^\\[", "");sub("\\]$", "");provider=$0}
+ /^(launch|list|preview|purge)_cmd/{st = index($0,"=");providers[provider][$1] = substr($0,st+1)}
+ ENDFILE{
+ for (key in providers){
+ if(!("list_cmd" in providers[key])){continue;}
+ if(!("launch_cmd" in providers[key])){continue;}
+ if(!("preview_cmd" in providers[key])){continue;}
+ if(!("purge_cmd" in providers[key])){providers[key]["purge_cmd"] = "exit 0";}
+ for (entry in providers[key]){
+ gsub(/[\x27,\047]/,"\x27\"\x27\"\x27", providers[key][entry])
+ }
+ print "PROVIDERS[\x27" key "\x27]=\x27" providers[key]["list_cmd"] "\034" providers[key]["preview_cmd"] "\034" providers[key]["launch_cmd"] "\034" providers[key]["purge_cmd"] "\x27\n"
+ }
+ }' "${PROVIDERS_FILE}")"
+ if [[ ! -v HIST_FILE ]]; then
+ HIST_FILE="${XDG_CACHE_HOME:-$HOME/.cache}/${0##*/}-${PROVIDERS_FILE##*/}-history.txt"
+ fi
+else
+ PROVIDERS['desktop']="${0} list-entries${DEL}${0} describe-desktop \"{1}\"${DEL}${0} run-desktop '{1}' {2}${DEL}test -f '{1}' || exit 43"
+ PROVIDERS['command']="${0} list-commands${DEL}${0} describe-command \"{1}\"${DEL}${TERMINAL_COMMAND} {1}${DEL}command -v '{1}' || exit 43"
+ if [[ ! -v HIST_FILE ]]; then
+ HIST_FILE="${XDG_CACHE_HOME:-$HOME/.cache}/${0##*/}-history.txt"
+ fi
+fi
+PROVIDERS['user']="exit${DEL}exit${DEL}{1}" # Fallback provider that simply executes the exact command if there were no matches
+
+if [[ -n "${HIST_FILE}" ]]; then
+ mkdir -p "${HIST_FILE%/*}" && touch "$HIST_FILE"
+ readarray HIST_LINES <"$HIST_FILE"
+fi
+
+function describe() {
+ # shellcheck disable=SC2086
+ readarray -d ${DEL} -t PROVIDER_ARGS <<<${PROVIDERS[${1}]}
+ # shellcheck disable=SC2086
+ [ -n "${PROVIDER_ARGS[1]}" ] && eval "${PROVIDER_ARGS[1]//\{1\}/${2}}"
+}
+function describe-desktop() {
+ description=$(sed -ne '/^Comment=/{s/^Comment=//;p;q}' "$1")
+ echo -e "\033[33m$(sed -ne '/^Name=/{s/^Name=//;p;q}' "$1")\033[0m"
+ echo "${description:-No description}"
+}
+function describe-command() {
+ readarray arr < <(whatis -l "$1" 2>/dev/null)
+ description="${arr[0]}"
+ description="${description#* - }"
+ echo -e "\033[33m${1}\033[0m"
+ echo "${description:-No description}"
+}
+
+function provide() {
+ # shellcheck disable=SC2086
+ readarray -d ${DEL} -t PROVIDER_ARGS <<<${PROVIDERS[$1]}
+ eval "${PROVIDER_ARGS[0]}"
+}
+#function list-commands() {
+# 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
+#}
+function list-commands() {
+ # Add your path
+ CUSTOM_BIN_DIR="$HOME/.local/share/uspm/bin/"
+
+ # Og PATH directories
+ IFS=: read -ra path <<<"$PATH"
+
+ # directory
+ if [[ -d "$CUSTOM_BIN_DIR" ]]; then
+ path+=("$CUSTOM_BIN_DIR")
+ fi
+
+ 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
+}
+function list-entries() {
+ # Get locations of desktop application folders according to spec
+ # https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
+ IFS=':' read -ra DIRS <<<"${XDG_DATA_HOME-${HOME}/.local/share}:${XDG_DATA_DIRS-/usr/local/share:/usr/share}"
+ for i in "${!DIRS[@]}"; do
+ if [[ ! -d "${DIRS[i]}" ]]; then
+ unset -v 'DIRS[$i]'
+ else
+ DIRS[$i]="${DIRS[i]}/applications/**/*.desktop"
+ fi
+ done
+
+ # shellcheck disable=SC2068
+ entries ${DIRS[@]} | sort -k2
+}
+function entries() {
+ # shellcheck disable=SC2068
+ awk -v pre="$GLYPH_DESKTOP" -F= '
+ function desktopFileID(filename){
+ sub("^.*applications/", "", filename);
+ sub("/", "-", filename);
+ return filename
+ }
+ BEGINFILE{
+ application=0;
+ hidden=0;
+ block="";
+ a=0
+
+ id=desktopFileID(FILENAME)
+ if(id in fileIds){
+ nextfile;
+ }else{
+ fileIds[id]=0
+ }
+ }
+ /^\[Desktop Entry\]/{block="entry"}
+ /^Type=Application/{application=1}
+ /^\[Desktop Action/{
+ sub("^\\[Desktop Action ", "");
+ sub("\\]$", "");
+ block="action";
+ a++;
+ actions[a,"key"]=$0
+ }
+ /^\[X-/{
+ sub("^\\[X-", "");
+ sub("\\]$", "");
+ block="action";
+ a++;
+ actions[a,"key"]=$0
+ }
+ /^Name=/{ (block=="action")? actions[a,"name"]=$2 : name=$2 }
+ /^NoDisplay=true/{ (block=="action")? actions[a,"hidden"]=1 : hidden=1 }
+ ENDFILE{
+ if (application){
+ if (!hidden)
+ print FILENAME "\034desktop\034\033[33m" pre name "\033[0m";
+ if (a>0)
+ for (i=1; i<=a; i++)
+ if (!actions[i, "hidden"])
+ print FILENAME "\034desktop\034\033[33m" pre name "\033[0m (" actions[i, "name"] ")\034" actions[i, "key"]
+ }
+ }' \
+ $@ </dev/null
+ # the empty stdin is needed in case no *.desktop files
+}
+function run-desktop() {
+ CMD="$("${0}" generate-command "$@" 2>&3)"
+ echo "Generated Launch command from .desktop file: ${CMD}" >&3
+ eval "${CMD}"
+}
+function generate-command() {
+ # Define the search pattern that specifies the block to search for within the .desktop file
+ PATTERN="^\\\\[Desktop Entry\\\\]"
+ if [[ -n $2 ]]; then
+ PATTERN="^\\\\[Desktop Action ${2}\\\\]"
+ fi
+ echo "Searching for pattern: ${PATTERN}" >&3
+ # 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
+ awk -v pattern="${PATTERN}" -v terminal_cmd="${TERMINAL_COMMAND}" -F= '
+ BEGIN{a=0;exec=0;path=0}
+ /^\[Desktop/{
+ if(a){ a=0 }
+ }
+ $0 ~ pattern{ a=1 }
+ /^Terminal=/{
+ sub("^Terminal=", "");
+ if ($0 == "true") { terminal=1 }
+ }
+ /^Exec=/{
+ if(a && !exec){
+ sub("^Exec=", "");
+ gsub(" ?%[cDdFfikmNnUuv]", "");
+ exec=$0;
+ }
+ }
+ /^Path=/{
+ if(a && !path){ path=$2 }
+ }
+ END{
+ if(path){ printf "cd " path " && " }
+ printf "exec "
+ if (terminal){ printf terminal_cmd " " }
+ print exec
+ }' "$1"
+}
+
+function shouldAutostart() {
+ local condition="$(cat $1 | grep "AutostartCondition" | cut -d'=' -f2)"
+ local filename="${XDG_CONFIG_HOME-${HOME}/.config}/${condition#* }"
+ case $condition in
+ if-exists*)
+ [[ -e $filename ]]
+ ;;
+ unless-exists*)
+ [[ ! -e $filename ]]
+ ;;
+ *)
+ return 0
+ ;;
+ esac
+}
+
+function autostart() {
+ for application in $(list-autostart); do
+ if shouldAutostart "$application"; then
+ (exec setsid /bin/sh -c "$(run-desktop "${application}")" &>/dev/null &)
+ fi
+ done
+}
+
+function list-autostart() {
+ # Get locations of desktop application folders according to spec
+ # https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
+ IFS=':' read -ra DIRS <<<"${XDG_CONFIG_HOME-${HOME}/.config}:${XDG_CONFIG_DIRS-/etc/xdg}"
+ for i in "${!DIRS[@]}"; do
+ if [[ ! -d "${DIRS[i]}" ]]; then
+ unset -v 'DIRS[$i]'
+ else
+ DIRS[$i]="${DIRS[i]}/autostart/*.desktop"
+ fi
+ done
+
+ # shellcheck disable=SC2068
+ awk -v pre="$GLYPH_DESKTOP" -F= '
+ function desktopFileID(filename){
+ sub("^.*autostart/", "", filename);
+ sub("/", "-", filename);
+ return filename
+ }
+ BEGINFILE{
+ application=0;
+ block="";
+ disabled=0;
+ a=0
+
+ id=desktopFileID(FILENAME)
+ if(id in fileIds){
+ nextfile;
+ }else{
+ fileIds[id]=0
+ }
+ }
+ /^\[Desktop Entry\]/{block="entry"}
+ /^Type=Application/{application=1}
+ /^Name=/{ iname=$2 }
+ /^Hidden=true/{disabled=1}
+ ENDFILE{
+ if (application && !disabled){
+ print FILENAME;
+ }
+ }' \
+ ${DIRS[@]} </dev/null
+}
+
+purge() {
+ # shellcheck disable=SC2188
+ >"${HIST_FILE}"
+ declare -A PURGE_CMDS
+ for PROVIDER_NAME in "${!PROVIDERS[@]}"; do
+ readarray -td ${DEL} PROVIDER_ARGS <<<${PROVIDERS[${PROVIDER_NAME}]}
+ PURGE_CMD=${PROVIDER_ARGS[3]}
+ [ -z "${PURGE_CMD}" ] && PURGE_CMD='test -f "{1}" || exit 43'
+ PURGE_CMDS[$PROVIDER_NAME]="${PURGE_CMD%$'\n'}"
+ done
+ for HIST_LINE in "${HIST_LINES[@]#*' '}"; do
+ readarray -td $'\034' HIST_ENTRY <<<${HIST_LINE}
+ ENTRY=${HIST_ENTRY[1]}
+ readarray -td ' ' FILTER <<<${PURGE_CMDS[$ENTRY]//\{1\}/${HIST_ENTRY[0]}}
+ (eval "${FILTER[@]}" 1>/dev/null) # Run filter command discarding output. We only want the exit status
+ if [[ $? -ne 43 ]]; then
+ echo "1 ${HIST_LINE[@]%$'\n'}" >>"${HIST_FILE}"
+ fi
+ done
+}
+
+case "$1" in
+describe | describe-desktop | describe-command | entries | list-entries | list-commands | list-autostart | generate-command | autostart | run-desktop | provide | purge)
+ "$@"
+ exit
+ ;;
+esac
+echo "Starting launcher instance with the following providers:" "${!PROVIDERS[@]}" >&3
+
+FZFPIPE=$(mktemp -u)
+mkfifo "$FZFPIPE"
+trap 'rm "$FZFPIPE"' EXIT INT
+
+# Append Launcher History, removing usage count
+(printf '%s' "${HIST_LINES[@]#* }" >>"$FZFPIPE") &
+
+# Iterate over providers and run their list-command
+for PROVIDER_NAME in "${!PROVIDERS[@]}"; do
+ (bash -c "${0} provide ${PROVIDER_NAME}" >>"$FZFPIPE") &
+done
+
+readarray -t COMMAND_STR <<<$(
+ ${FZF_COMMAND} --ansi +s -x -d '\034' --nth ..3 --with-nth 3 \
+ --print-query \
+ --preview "$0 describe {2} {1}" \
+ --preview-window="${PREVIEW_WINDOW}" \
+ --no-multi --cycle \
+ --prompt="${GLYPH_PROMPT-# }" \
+ --header='' --no-info --margin='1,2' \
+ --color='16,gutter:-1' \
+ <"$FZFPIPE"
+) || exit 1
+# Get the last line of the fzf output. If there were no matches, it contains the query which we'll treat as a custom command
+# If there were matches, it contains the selected item
+COMMAND_STR=$(printf '%s\n' "${COMMAND_STR[@]: -1}")
+# We still need to format the query to conform to our fallback provider.
+# We check for the presence of field separator character to determine if we're dealing with a custom command
+if [[ $COMMAND_STR != *$'\034'* ]]; then
+ COMMAND_STR="${COMMAND_STR}"$'\034user\034'"${COMMAND_STR}"$'\034'
+ SKIP_HIST=1 # I chose not to include custom commands in the history. If this is a bad idea, open an issue please
+fi
+
+[ -z "$COMMAND_STR" ] && exit 1
+
+if [[ -n "${HIST_FILE}" && ! "$SKIP_HIST" ]]; then
+ # 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"
+fi
+
+# shellcheck disable=SC2086
+readarray -d $'\034' -t PARAMS <<<${COMMAND_STR}
+# shellcheck disable=SC2086
+readarray -d ${DEL} -t PROVIDER_ARGS <<<${PROVIDERS[${PARAMS[1]}]}
+# Substitute {1}, {2} etc with the correct values
+COMMAND=${PROVIDER_ARGS[2]//\{1\}/${PARAMS[0]}}
+COMMAND=${COMMAND//\{2\}/${PARAMS[3]}}
+COMMAND=${COMMAND%%[[:space:]]}
+
+if [ -t 1 ]; then
+ echo "Launching command: ${COMMAND}" >&3
+ setsid /bin/sh -c "${COMMAND}" >&/dev/null </dev/null &
+ sleep 0.01
+else
+ echo "${COMMAND}"
+fi