#!/usr/bin/env bash # spmenu_run # Feature rich run launcher, file lister and .desktop launcher for spmenu # # See LICENSE file for copyright and license details. # Set basic variables, in case the config isn't valid, env variables and config file can override these CONFDIR="${CONFDIR:-${XDG_CONFIG_HOME:-$HOME/.config}}" TERMINAL="${TERMINAL:-st -e}" BROWSER="${BROWSER:-xdg-open}" TORRENT="${TORRENT:-qbittorrent}" PDF_READER="${PDF_READER:-zathura}" EDITOR="${EDITOR:-nvim}" PLAYER="${PLAYER:-mpv}" GENERIC="${GENERIC:-$TERMINAL -e $EDITOR}" WEB_GREP="${WEB_GREP:-http:|https:|www[.]}" MAGNET_GREP="${MAGNET_GREP:-magnet:?}" HISTORY="${HISTORY:-${XDG_CACHE_HOME:-$HOME/.cache/}/spmenu/spmenu_run.hist}" RUNLAUNCHER="${RUNLAUNCHER:-spmenu}" PREFIX="${PREFIX:-/usr}" DESTDIR="${DESTDIR:-}" STDOUT="${STDOUT:-false}" SORT_BY_NUMBER="${SORT_BY_NUMBER:-true}" SORT_IN_REVERSE="${SORT_IN_REVERSE:-true}" SORT_BY_RECENT="${SORT_BY_RECENT:-false}" SORT_ARGS="${SORT_ARGS:-}" UNIQ_ARGS="${UNIQ_ARGS:-}" HIDDEN_KEYWORDS="${HIDDEN_KEYWORDS:-spmenu}" KEYWORDS="${KEYWORDS:-}" DISPLAY_DUPLICATES="${DISPLAY_DUPLICATES:-false}" DISPLAY_DESCRIPTION="${DISPLAY_DESCRIPTION:-false}" DISPLAY_COMMENT="${DISPLAY_COMMENT:-true}" LS_ARGS="${LS_ARGS:- --color=always}" USE_FULL_PATH="${USE_FULL_PATH:-false}" HELP_COLOR="${HELP_COLOR:-#FFFF00}" DESCRIPTION_COLOR="${DESCRIPTION_COLOR:-#999888}" DESCRIPTION_SEPARATOR="${DESCRIPTION_SEPARATOR:- - }" COMMENT_COLOR="${COMMENT_COLOR:-#999888}" COMMENT_SEPARATOR="${COMMENT_SEPARATOR:- - }" DMENU_COMPAT="${DMENU_COMPAT:-false}" AUTOREFRESH="${AUTOREFRESH:-true}" MULTISELECT="${MULTISELECT:-true}" BOOKMARK_FILE="${BOOKMARK_FILE:-${XDG_CACHE_HOME:-$HOME/.cache/}/spmenu/spmenu_run.bookmarks}" BOOKMARK_PROMPT="${BOOKMARK_PROMPT:-Bookmarks}" PRINT_LOGS_STDERR="${PRINT_LOGS_STDERR:-true}" RESPECT_NODISPLAY="${RESPECT_NODISPLAY:-true}" RESPECT_ONLYSHOWIN="${RESPECT_ONLYSHOWIN:-true}" PREFERRED_LANGUAGE="${PREFERRED_LANGUAGE:-generic}" RUNLAUNCHER_RUN_ARGS="" RUNLAUNCHER_BM_ARGS="" RUNLAUNCHER_DESKTOP_ARGS="" RUNLAUNCHER_FM_ARGS="--lines 40 --columns 2" RUNLAUNCHER_HELP_ARGS="" DESKTOP_DIR="${DESKTOP_DIR:-${DESTDIR}${PREFIX}/share/applications ${HOME}/.local/share/applications}" ICON_DIR="${ICON_DIR:-${DESTDIR}${PREFIX}/share/icons/hicolor ${HOME}/.local/share/icons/hicolor ${DESTDIR}${PREFIX}/share/pixmaps}" IMAGE="${IMAGE:-true}" LOGFILE="${LOGFILE:-/tmp/spmenu_run.log}" TITLEFILE="${TITLEFILE:-${XDG_CACHE_HOME:-$HOME/.cache}/spmenu/.desktop_title}" EXECFILE="${EXECFILE:-${XDG_CACHE_HOME:-$HOME/.cache}/spmenu/.desktop_exec}" ICONFILE="${ICONFILE:-${XDG_CACHE_HOME:-$HOME/.cache}/spmenu/.desktop_icon}" DESCFILE="${DESCFILE:-${XDG_CACHE_HOME:-$HOME/.cache}/spmenu/.desktop_desc}" FILEFILE="${FILEFILE:-${XDG_CACHE_HOME:-$HOME/.cache}/spmenu/.desktop_file}" TERMFILE="${TERMFILE:-${XDG_CACHE_HOME:-$HOME/.cache}/spmenu/.desktop_term}" ONLYFILE="${DISPLAYFILE:-${XDG_CACHE_HOME:-$HOME/.cache}/spmenu/.desktop_only}" DISPLAYFILE="${DISPLAYFILE:-${XDG_CACHE_HOME:-$HOME/.cache}/spmenu/.desktop_display}" COMMENTFILE="${COMMENTFILE:-${XDG_CACHE_HOME:-$HOME/.cache}/spmenu/.desktop_comment}" # arrays containing entries declare -a it_title declare -a it_icon declare -a it_exec declare -a it_file declare -a it_desc declare -a it_comment declare -a it_term # arrays containing arguments declare -a rl_fm declare -a rl_bm declare -a rl_run declare -a rl_desktop declare -a rl_help declare -a rl_ex declare -a ls_args declare -a sort_args declare -a uniq_args # directories declare -a desktopdir declare -a icondir prepare_dirs() { mkdir -p \ "$(dirname "$TITLEFILE")" \ "$(dirname "$EXECFILE")" \ "$(dirname "$ICONFILE")" \ "$(dirname "$DESCFILE")" \ "$(dirname "$FILEFILE")" \ "$(dirname "$TERMFILE")" \ "$(dirname "$DISPLAYFILE")" \ "$(dirname "$COMMENTFILE")" \ "$(dirname "$LOGFILE")" \ "$(dirname "$HISTORY")" \ "$(dirname "$BOOKMARK_FILE")" } check() { prepare_dirs [ ! -d "$CONFDIR/spmenu/run" ] && mkdir -p "$CONFDIR/spmenu/run" if [ ! -f "$CONFDIR/spmenu/run/.first_run" ]; then print_help touch "$CONFDIR/spmenu/run/.first_run" fi } check_desktop() { [ ! -d "$CONFDIR/spmenu/run" ] && mkdir -p "$CONFDIR/spmenu/run" if [ ! -f "$CONFDIR/spmenu/run/.first_run" ]; then print_desktop_help touch "$CONFDIR/spmenu/run/.first_run" fi } path() { [ "$SORT_BY_NUMBER" != "false" ] && NUMBERSORTARG="-n" [ "$SORT_IN_REVERSE" != "false" ] && REVERSESORTARG="-r" SORT_ARGS="$NUMBERSORTARG $REVERSESORTARG $SORT_ARGS" read -ra uniq_args <<< "${UNIQ_ARGS}" read -ra sort_args <<< "${SORT_ARGS}" HIDDEN_KEYWORDS="${HIDDEN_KEYWORDS:-NULL_ENTRY}" print_menu() { print() { compgen -c | \ grep -vE "$HIDDEN_KEYWORDS" | \ grep -E "$KEYWORDS" } if [ "$SORT_BY_RECENT" != "false" ]; then print | sort "${sort_args[@]}" | cut -d' ' -f 2 2>&1 else print | sort "${sort_args[@]}" | cut -d' ' -f 2 2>&1 fi } # uniq if [ "$DISPLAY_DUPLICATES" != "false" ]; then print_menu else print_menu | uniq "${uniq_args[@]}" fi command -v run_pre_func && run_pre_func } print_help() { if [ "$DMENU_COMPAT" != "true" ]; then COL='\033[0;31m' RUNLAUNCHER_EX_ARGS="--lines 30 --columns 1 --normal --sgr1 $HELP_COLOR --hide-cursor --no-allow-typing --no-color-items --hide-prompt --hide-powerline --hide-input --hide-right-arrow --hide-left-arrow --hide-mode --hide-match-count --hide-caps" read -ra rl_ex <<< "$RUNLAUNCHER_EX_ARGS" fi cat << EOF | $RUNLAUNCHER "${rl_help[@]}" "${rl_ex[@]}" > /dev/null Start typing in keywords to list out entries. Press Enter to select an entry. The selected entry will be run through a shell. To set spmenu options, you pass arguments to 'spmenu_run' directly. See 'spmenu --help' for a list of valid arguments. To configure spmenu, you may also copy ${DESTDIR}${PREFIX}/share/spmenu/spmenu.conf to $CONFDIR/spmenu/spmenu.conf and edit that. - Type in '?' to show this help screen at any time. - If the entry selected starts with 'www', it will instead be treated as a link and spawned in a web browser (\$BROWSER) - If the entry selected starts with 'magnet', it will instead be treated as a magnet link and spawned in a torrent client (\$TORRENT) - If the entry selected starts with '?' followed by a valid command, it will be opened as a man page in spmenu. - If the entry starts with '#' followed by a valid command, it will be opened in the defined terminal emulator. Enter '@' to show the bookmark list. Enter '@:' to add a bookmark. If the bookmark is selected, the will be executed. Enter @c to clear the bookmark list. '@' is also valid, and then will be executed instead. $(printf "%b" "${COL}")Note: This may also be displayed if you deleted your spmenu configuration directory. EOF } print_cli_help() { cat << EOF spmenu_run - Run launcher for spmenu spmenu_run -x, --run List entries in \$PATH. spmenu_run -fm, --fm List files and directories in spmenu_run -d, --desktop List .desktop entries. spmenu_run -p, --full-path Print the full path to the file selected (-fm) spmenu_run -np, --no-full-path Don't print the full path to the file selected (-fm) spmenu_run -cc, --clear-cache Clear cache, useful if you just installed/uninstalled a program (-d) spmenu_run -sb, --show-bookmarks Show bookmarks immediately spmenu_run -nsb, --no-show-bookmarks Don't show bookmarks immediately spmenu_run -dm, --dmenu Run spmenu_run using dmenu instead of spmenu spmenu_run -ndm, --no-dmenu Run spmenu_run using spmenu instead of dmenu spmenu_run -h, --help Print this help spmenu_run -o, --stdout Print to standard input and do not execute the selected item spmenu_run -no, --no-stdout Don't print to standard input, execute the selected item spmenu_run -a, --args Pass to spmenu See spmenu(1) and spmenu_run(1) for more information. EOF } print_config() { [ -f "$CONFDIR/spmenu/run/config" ] && . "$CONFDIR/spmenu/run/config" && return mkdir -p "$CONFDIR/spmenu/run" cat << EOF > "$CONFDIR/spmenu/run/config" # spmenu_run default configuration file # # This is the configuration file for the run launcher spmenu comes with. # It is not the configuration file for spmenu, see ~/.config/spmenu/spmenu.conf for that. # # spmenu_run also runs these functions if found in the config file: # # 'run_pre_func' before spawning spmenu. # 'run_post_func' after spawning spmenu, selected item passed as an argument. # 'run_pre_bookmark_list_func' while listing selected/marked items. The selected item is passed as an argument. # 'run_post_bookmark_list_func' while listing selected/marked items. The selected item is passed as an argument. # 'run_file_bookmark_list_func' while reading entries from the bookmark file. The current bookmark is passed as an argument. # 'run_single_char_pre_bookmark_list_func' while listing selected/marked items. The first character of the spmenu output is passed as an argument. # 'run_single_char_func' while checking the value of a single character. The first character of the spmenu output is passed as an argument. # 'run_output_func' while checking the value of the spmenu output. spmenu output is passed as an argument. # 'desktop_pre_func' before spawning spmenu. # 'desktop_post_func' after spawning spmenu, selected item passed as an argument. # 'desktop_pre_caching_func' before caching entries. # 'desktop_post_caching_func' after caching entries. # 'desktop_file_caching_func' while caching entries. The current file that is being parsed is passed as an argument. # 'fm_pre_func' before spawning spmenu. # 'fm_post_func' after spawning spmenu, selected item passed as an argument. # 'fm_pre_list_func' right before listing out files. # 'fm_post_list_func' right after listing out files. # 'fm_dir_func' before changing directory to the selected directory. # 'fm_line_func' for each line in ls output, the line is passed as an argument, including SGR colors. # 'read_man' when reading a man page, selected item passed as an argument. # 'print_array' when printing out .desktop entries # # You may create those functions below. # # For example, to implement a basic history file: # # run_post_func() { # rm -f /tmp/spmenu_entryhist; printf "\$1\n" >> /tmp/spmenu_entryhist # } # # You can use anything POSIX compliant shells and Bash support, as well as programs available on the system. # Miscellanious software options TERMINAL="\${TERMINAL:-st -e}" # Terminal commands are spawned in BROWSER="\${BROWSER:-xdg-open}" # Web browser, for URLs TORRENT="\${TORRENT:-qbittorrent}" # Torrent client, for magnet links PDF_READER="\${PDF_READER:-zathura}" # PDF reader, for file management EDITOR="\${EDITOR:-nvim}" # Editor, used to open documents PLAYER="\${PLAYER:-mpv}" # Player, used to play audio/video GENERIC="\${GENERIC:-\$TERMINAL -e \$EDITOR}" # Generic, used to open unknown files WEB_GREP="$WEB_GREP" # Needs to be in grep -E syntax MAGNET_GREP="$MAGNET_GREP" # Needs to be in grep -E syntax HISTORY="\${XDG_CACHE_HOME:-\$HOME/.cache/}/spmenu/spmenu_run.hist" # History file, spmenu (meaning your user) must have permission to read and write to it. BOOKMARK_FILE="\${BOOKMARK_FILE:-\${XDG_CACHE_HOME:-\$HOME/.cache/}/spmenu/spmenu_run.bookmarks}" # Bookmark file, spmenu_run must have permission to read and write to it. BOOKMARK_PROMPT="$BOOKMARK_PROMPT" # Bookmark prompt (-p) # Run launcher argument options RUNLAUNCHER="\${RUNLAUNCHER:-spmenu}" # Run launcher to use RUNLAUNCHER_RUN_ARGS="$RUNLAUNCHER_RUN_ARGS" # Extra arguments passed to \$RUNLAUNCHER when using the run launcher RUNLAUNCHER_BM_ARGS="$RUNLAUNCHER_BM_ARGS" # Extra arguments passed to \$RUNLAUNCHER when using the bookmark menu RUNLAUNCHER_DESKTOP_ARGS="$RUNLAUNCHER_DESKTOP_ARGS" # Extra rguments passed to \$RUNLAUNCHER when using the .desktop launcher RUNLAUNCHER_FM_ARGS="${RUNLAUCNHER_FM_ARGS}" # Extra arguments passed to \$RUNLAUNCHER when using the file manager RUNLAUNCHER_HELP_ARGS="${RUNLAUNCHER_HELP_ARGS}" # Extra arguments passed to \$RUNLAUNCHER when using the help DMENU_COMPAT="$DMENU_COMPAT" # Enable dmenu compatibility (true/false) # Sorting SORT_BY_NUMBER="$SORT_BY_NUMBER" # Sort by numbers SORT_IN_REVERSE="$SORT_IN_REVERSE" # Sort in reverse SORT_BY_RECENT="$SORT_BY_RECENT" # Sort by recent SORT_ARGS="$SORT_ARGS" # Extra arguments passed to the sort command. # Keywords HIDDEN_KEYWORDS="$HIDDEN_KEYWORDS" # Keywords that will be ignored, needs to be in grep -vE syntax. KEYWORDS="$KEYWORDS" # Keywords that will be matched, needs to be in grep -E syntax. # Miscellanious STDOUT="$STDOUT" # Print to stdout and exit (true/false) DISPLAY_DUPLICATES="$DISPLAY_DUPLICATES" # Display duplicates or not DEFAULT_FEATURE="$DEFAULT_FEATURE" # spmenu_run default feature (run/fm/desktop/help) HELP_COLOR="$HELP_COLOR" # .desktop entry options DESKTOP_DIR="$DESKTOP_DIR" # Directories for .desktop entries ICON_DIR="$ICON_DIR" # Directories for icons defined in the entries HIDDEN_ENTRY_KEYWORDS="\$HIDDEN_KEYWORDS" # Keywords that will be ignored, needs to be in grep -vE syntax. ENTRY_KEYWORDS="\$KEYWORDS" # Keywords that will be matched, needs to be in grep -E syntax. AUTOREFRESH="$AUTOREFRESH" # Refresh (clear) cache if there are more entries available than cached. May cause cache to be cleared every time in some cases. (true/false) MULTISELECT="$MULTISELECT" # Allow handling multiple items, if set to false only the first line/selected item will be used. IMAGE="$IMAGE" # Display images (true/false) DISPLAY_COMMENT="$DISPLAY_COMMENT" # Display comment (true/false) DISPLAY_DESCRIPTION="$DISPLAY_DESCRIPTION" # Display description (true/false)" DESCRIPTION_COLOR="$DESCRIPTION_COLOR" # Description text color DESCRIPTION_SEPARATOR="$DESCRIPTION_SEPARATOR" # Separator between title and description COMMENT_COLOR="$COMMENT_COLOR" # Comment text color COMMENT_SEPARATOR="$COMMENT_SEPARATOR" # Separator between title and comment RESPECT_NODISPLAY="$RESPECT_NODISPLAY" # Respect NoDisplay in .desktop entries. If set to true, entries with 'NoDisplay=true' will not be displayed (true/false) RESPECT_ONLYSHOWIN="$RESPECT_ONLYSHOWIN" # Respect OnlyShowIn in .desktop entries. If set to true, entries wth 'OnlyShowIn' assigned will not be displayed (true/false) PREFERRED_LANGUAGE="$PREFERRED_LANGUAGE" # Preferred language for the title and description. "generic" means the default for that .desktop entry (true/false) LOGFILE="$LOGFILE" # Log file PRINT_LOGS_STDERR="$PRINT_LOGS_STDERR" # Print information (such as logs) to stderr (true/false) TITLEFILE="\${XDG_CACHE_HOME:-\$HOME/.cache}/spmenu/.desktop_title" # File containing the different titles to display. DESCFILE="\${XDG_CACHE_HOME:-\$HOME/.cache}/spmenu/.desktop_desc" # File containing the description to display EXECFILE="\${XDG_CACHE_HOME:-\$HOME/.cache}/spmenu/.desktop_exec" # File containing the different executables to run. ICONFILE="\${XDG_CACHE_HOME:-\$HOME/.cache}/spmenu/.desktop_icon" # File containing the paths to the icons to display. FILEFILE="\${XDG_CACHE_HOME:-\$HOME/.cache}/spmenu/.desktop_file" # File containing the path to the .desktop entries. TERMFILE="\${XDG_CACHE_HOME:-\$HOME/.cache}/spmenu/.desktop_term" # File containing the path to the terminal data ONLYFILE="\${XDG_CACHE_HOME:-\$HOME/.cache}/spmenu/.desktop_only" # File containing the path to the OnlyShowIn data DISPLAYFILE="\${XDG_CACHE_HOME:-\$HOME/.cache}/spmenu/.desktop_display" # File containing the path to the NoDisplay data COMMENTFILE="\${XDG_CACHE_HOME:-\$HOME/.cache}/spmenu/.desktop_comment" # File containing the comment to display # File management DEFAULT_DIRECTORY="\$(pwd)" # Directory to start -fm if none is specified. LS_ARGS="\${LS_ARGS:- --color=always}" # Arguments passed to /bin/ls USE_FULL_PATH="true" # Return full path (true/false) # Function to read the man page in spmenu read_man() { man "\$1" | \\ col -b | \\ \${RUNLAUNCHER:-spmenu} --lines 40 --columns 1 -p "man \$1" } # Function used to print out the .desktop entries print_array() { for i in "\${!it_title[@]}"; do [ "\$RESPECT_ONLYSHOWIN" != "false" ] && [ "\${it_only[i]}" != "false" ] && continue [ "\$RESPECT_NODISPLAY" != "false" ] && [ "\${it_display[i]}" != "true" ] && continue if [ -f "\${it_icon[i]}" ] && [ -n "\${it_title[i]}" ] && [ -n "\${it_exec[i]}" ] && [ "\$IMAGE" != "false" ]; then printf "%s\\t%s" "img://\${it_icon[i]}" "\${it_title[i]}" elif [ -n "\${it_title[i]}" ] && [ -n "\${it_exec[i]}" ]; then printf "%s" "\${it_title[i]}" else continue fi if [ -n "\${it_desc[i]}" ] && [ "\$DISPLAY_DESCRIPTION" = "true" ]; then [ "\$DMENU_COMPAT" != "true" ] && COL='\\033[0;31m' printf -- "\$DESCRIPTION_SEPARATOR%b%s" "\${COL}" "\${it_desc[i]}" fi if [ -n "\${it_comment[i]}" ] && [ "\$DISPLAY_COMMENT" = "true" ]; then [ "\$DMENU_COMPAT" != "true" ] && COL='\\033[0;32m' printf -- "\$COMMENT_SEPARATOR%b%s" "\${COL}" "\${it_comment[i]}" fi printf "\n" done } EOF [ -f "$CONFDIR/spmenu/run/config" ] && . "$CONFDIR/spmenu/run/config" && return } parse() { [ ! -f "$BOOKMARK_FILE" ] && mkdir -p "$(dirname "$BOOKMARK_FILE")" && touch "$BOOKMARK_FILE" if [ "$SHOW_BM" != "true" ]; then path | $RUNLAUNCHER "${rl_run[@]}" > /tmp/spmenu_out else echo > /tmp/spmenu_out fi while read -r sout; do command -v run_single_char_func > /dev/null && run_single_char_func "${sout:0:1}" command -v run_output_func > /dev/null && run_output_func "${sout}" printf "Run launcher output: '%s'\n" "$sout" >> "$LOGFILE" # check for keywords printf "%s" "$sout" | grep -qE "$WEB_GREP" && EXEC=web printf "%s" "$sout" | grep -qE "$MAGNET_GREP" && EXEC=torrent case "$(printf "%s" "$sout" | awk '{ print $1 }')" in "magnet") EXEC=torrent ;; "www") EXEC=web ;; "?") ;; esac # parse case "${sout:0:1}" in "#") EXEC="term" ;; "?") EXEC="man" ;; "@") EXEC="mark" ;; esac [ "$SHOW_BM" = "true" ] && sout="@" case "$sout" in "?") print_help "$@" parse exec_cmd ;; "@") print_bookmarks "$@" ;; "@c") rm -f "$BOOKMARK_FILE" && touch "$BOOKMARK_FILE" parse exec_cmd ;; esac printf "Type: '%s'\n" "$EXEC" >> "$LOGFILE" exec_cmd "$args" [ "$MULTISELECT" != "true" ] && break done < /tmp/spmenu_out } bookmark_path() { while read -r file; do command -v run_file_bookmark_list_func > /dev/null && \ run_file_bookmark_list_func "$file" printf "%s\n" "${file%%:*}" done < "$BOOKMARK_FILE" } print_bookmarks() { bookmark_path | sort "${sort_args[@]}" | $RUNLAUNCHER "${rl_bm[@]}" > /tmp/spmenu_out while read -r sout; do command -v run_pre_bookmark_list_func > /dev/null && run_pre_bookmark_list_func "$sout" command -v run_single_char_pre_bookmark_list_func > /dev/null && run_single_char_pre_bookmark_list_func "${sout:0:1}" printf "Run launcher output: '%s'\n" "$sout" >> "$LOGFILE" sout_e="$(grep -- "$sout" "$BOOKMARK_FILE" | tail -n 1)" printf "%s\n" "${sout_e#*:}" | ${SHELL:-"/bin/sh"} & [ "$MULTISELECT" != "true" ] && break done < /tmp/spmenu_out command -v run_post_bookmark_list_func > /dev/null && run_post_bookmark_list_func exit 0 } exec_cmd() { [ -z "$EXEC" ] && EXEC=shell [ "$STDOUT" != "false" ] && printf "%s\n" "$sout" && exit 1 command -v run_post_func > /dev/null && run_post_func "$sout" # when there's no read_man func because the user is retarded and removed it, this basic function will be called instead read_woman() { $TERMINAL -e man "$1" } # execute it case "$EXEC" in "mark") printf "%s\n" "$sout" | sed -- "s/@//g" >> "$BOOKMARK_FILE"; parse; exit 0 ;; "shell") printf "%s" "$sout" | ${SHELL:-"/bin/sh"} & ;; "term") $TERMINAL -e "$(printf "%s" "$sout" | sed "s/#//g")" & ;; "web") $BROWSER "$(printf "%s" "$sout" | sed "s/www //g")" & ;; "torrent") $TORRENT "$(printf "%s" "$sout" | sed "s/magnet //g")" & ;; "man") exec="$(printf "%s" "$sout" | sed "s/?//g")" [ -x "$(command -v "$exec")" ] || return if [ "$(command -v read_man)" ]; then read_man "$exec" return else read_woman "$exec" return fi ;; esac } remove_arg() { args="$(printf "%s\n" "$args" | sed "s|$1||g")" } read_args() { function="${DEFAULT_FEATURE:-run}" # default functionality dir="${DEFAULT_DIRECTORY:-$(pwd)}" # default directory args="$(printf "%s\n" "$@")" argc="$(printf "%s\n" "$@" | grep -c "")" while true; do i=$((i+1)) arg="$(printf "%s\n" "$args" | sed "${i}q;d")" narg="$(printf "%s\n" "$args" | sed "$((i+1))q;d")" case "$arg" in -x|--run) remove_arg "$arg" && function=run ;; -o|--stdout) remove_arg "$arg" && STDOUT=true ;; -no|--no-stdout) remove_arg "$arg" && STDOUT=false ;; -sb|--show-bookmarks) remove_arg "$arg" && SHOW_BM=true ;; -nsb|--no-show-bookmarks) remove_arg "$arg" && SHOW_BM=false ;; -fm|--fm) remove_arg "$arg" [ -d "$narg" ] && dir="$narg" && remove_arg "$narg" function=fm ;; -cc|--clear-cache) remove_arg "$arg" clearcache="true" ;; -d|--desktop) remove_arg "$arg" function=desktop ;; -p|--full-path) remove_arg "$arg" USE_FULL_PATH="true" ;; -np|--no-full-path) remove_arg "$arg" USE_FULL_PATH="false" ;; -dm|--dmenu) remove_arg "$arg" DMENU_COMPAT="true" ;; -ndm|--no-dmenu) remove_arg "$arg" DMENU_COMPAT="false" ;; -a|--args) remove_arg "$arg" [ -z "$narg" ] && printf "You must specify a list of arguments to pass to %s.\n" "$RUNLAUNCHER" && exit 1 remove_arg "$narg" MARGS="$narg" ;; -h|--help) remove_arg "$arg" && function=help ;; "") : ;; *) printf "spmenu_run: Invalid argument: '%s'. If you meant to pass this to spmenu, use the '%s' option.\n" "$arg" "-a" ;; esac [ "$argc" = "$i" ] && break done args="$(printf "%s\n" "$*")" } exec_file() { [ "$USE_FULL_PATH" != "false" ] && DIR="$(pwd)/" [ "$STDOUT" != "false" ] && printf "%s%s\n" "${DIR}" "$1" && return 0 command -v fm_post_func > /dev/null && fm_post_func "$1" # some default basic parsing case "$1" in *.html|*.htm) $BROWSER "$1" ;; *.pdf) $PDF_READER "$1" ;; *.flac|*.mp3|*.wav|*.ogg) $PLAYER "$1" ;; *.mp4|*.mov|*.mkv) $PLAYER "$1" ;; *.theme) $EDITOR "$1" ;; *) if [ -x "$1" ]; then $TERMINAL -e "$1" else $GENERIC "$1" fi ;; esac } prepare_dirnav() { [ ! -d "$dir" ] && return 1 cd "$dir" || printf "Invalid directory.. somehow\n" # TODO: read line by line so we can append/prepend whatever we want listing() { command -v fm_pre_list_func > /dev/null && fm_pre_list_func ls "${ls_args[@]}" > /tmp/spmenu_ls_list # this allows us SGR colors printf "..\n" >> /tmp/spmenu_ls_list while read -r l; do command -v fm_line_func > /dev/null && fm_line_func "$(pwd)/$l" printf "%s\n" "$l" done < "/tmp/spmenu_ls_list"; rm -f /tmp/spmenu_ls_list command -v fm_post_list_func > /dev/null && fm_post_list_func } command -v fm_pre_func > /dev/null && fm_pre_func listing | $RUNLAUNCHER "${rl_fm[@]}" | sed -e 's/\x1b\[[0-9;]*m//g' > /tmp/spmenu_out while read -r dir; do case "$dir" in *) if [ -d "$dir" ]; then dir="$(pwd)/$dir" command -v fm_dir_func > /dev/null && fm_dir_func "$dir" prepare_dirnav elif [ -f "$dir" ]; then exec_file "$dir" else return 1 fi ;; esac [ "$MULTISELECT" != "true" ] && break done < /tmp/spmenu_out; rm -f /tmp/spmenu_out [ -z "$dir" ] && return 1 } print_desktop_help() { if [ "$DMENU_COMPAT" != "true" ]; then COL='\033[0;31m' RUNLAUNCHER_EX_ARGS="--lines 20 --columns 1 --normal --sgr1 $HELP_COLOR --hide-cursor --hide-caps --no-allow-typing --no-color-items --hide-prompt --hide-powerline --hide-input --hide-right-arrow --hide-left-arrow --hide-mode --hide-match-count" read -ra rl_ex <<< "${RUNLAUNCHER_EX_ARGS}" fi cat << EOF | $RUNLAUNCHER "${rl_help[@]}" "${rl_ex[@]}" > /dev/null Start typing in keywords to list out entries. Press Enter to select an entry. The selected entry will be run through a shell. To set spmenu options, you modify \$RUNLAUNCHER_ARGS in the config. See 'spmenu --help' for a list of valid arguments to add to the variable. To configure spmenu itself, you may copy ${DESTDIR}${PREFIX}/share/spmenu.conf to ~/.config/spmenu/spmenu.conf. By default, spmenu_run will cache entries for speed reasons. You can find these entries in ~/.config/spmenu/run/cache. If you make changes to .desktop files (not new entries, modified old entries), you need to clear the cache for the changes to appear. Simply delete the directory to do this. - Type in '?' to show this help screen at any time. $(printf "%b" "$COL")Note: This may also be displayed if you deleted your spmenu configuration directory. EOF } print_desktop_list() { # should we use cached files? if [ -f "$TITLEFILE" ] && [ -f "$ICONFILE" ] && [ -f "$EXECFILE" ] && [ -f "$FILEFILE" ] && [ -f "$DESCFILE" ] && [ -f "$DISPLAYFILE" ] && [ -f "$ONLYFILE" ] && [ -f "$COMMENTFILE" ]; then cfiles=true else cfiles=false fi read -ra uniq_args <<< "${UNIQ_ARGS}" it_title=() it_desc=() it_icon=() it_exec=() it_file=() it_term=() it_display=() it_comment=() it_only=() # autorefreshing if [ "$AUTOREFRESH" = "true" ] && [ "$1" != "noc" ]; then entry_c="$(find "${desktopdir[@]}" -type f -name '*.desktop' 2>/dev/null | grep -c "")" [ -f "$FILEFILE" ] && cached_c="$(grep -c "" < "$FILEFILE")" || cached_c="0" printf "%s: %d\n%s: %d\n" "Cached" "$cached_c" "Entries" "$entry_c" >> "$LOGFILE" [ "$cfiles" = "true" ] && [ "$entry_c" != "$cached_c" ] && cfiles=false fi # print data from entries if [ "$cfiles" = "false" ]; then command -v desktop_pre_caching_func > /dev/null && desktop_pre_caching_func printf "Writing cache files because none exist.\nTitle file: '%s'\nDescription file: '%s'\nIcon file: '%s'\nExec file: '%s'\nFile file: '%s'\nTerm file: '%s'\nDisplay file: '%s'\nOnly file: '%s'\nComment file: '%s'\n" "$TITLEFILE" "$DESCFILE" "$ICONFILE" "$EXECFILE" "$FILEFILE" "$TERMFILE" "$DISPLAYFILE" "$ONLYFILE" "$COMMENTFILE" >> "$LOGFILE" [ "$PRINT_LOGS_STDERR" = "true" ] && printf "spmenu_run: Updating .desktop entries.\n" >> /dev/stderr icons="$(find "${icondir[@]}" -type f 2>/dev/null)" [ -z "$entry" ] && entry="$(find "${desktopdir[@]}" -type f -name '*.desktop' 2>/dev/null)" [ -z "$entry_c" ] && entry_c="$(printf "%s\n" "$entry" | grep -c "")" rm -f "$TITLEFILE" "$ICONFILE" "$DESCFILE" "$EXECFILE" "$FILEFILE" "$TERMFILE" "$DISPLAYFILE" "$ONLYFILE" "$COMMENTFILE" for i in $(seq "$entry_c"); do command -v desktop_file_caching_func > /dev/null && desktop_file_caching_func "$cur_file" cur_file="$(printf "%s" "$entry" | sed "${i}q;d")" [ ! -f "$cur_file" ] && printf "No desktop entries found." && return icon_name="$(grep "Icon=" "$cur_file" | head -n 1 | sed "s/Icon=//g")" # get details to display it_title[i]="$(sed '/\[Desktop Action/q' "$cur_file" | grep "Name=" | grep -v Generic | head -n 1 | sed "s/Name=//g")" it_desc[i]="$(sed '/\[Desktop Action/q' "$cur_file" | grep "GenericName=" | sed "s/GenericName=//g")" it_comment[i]="$(sed '/\[Desktop Action/q' "$cur_file" | grep "Comment=" | sed "s/Comment=//g")" it_icon[i]="$(printf "%s" "$icons" | grep "/${icon_name}[.]" | head -n 1)" it_exec[i]="$(sed '/\[Desktop Action/q' "$cur_file" | grep -v "TryExec" | grep -m1 "Exec=" | sed "s/Exec=//g; s/%U//g; s/%F//g; s/%u//g; s/%f//g")" it_file[i]="$cur_file" it_term[i]="false" it_only[i]="false" it_display[i]="true" if [ "$PREFERRED_LANGUAGE" != "generic" ]; then t_title="$(sed '/\[Desktop Action/q' "$cur_file" | grep "Name\[$PREFERRED_LANGUAGE\]=" | grep -v Generic | head -n 1 | sed "s/Name\[$PREFERRED_LANGUAGE\]=//g")" t_desc="$(sed '/\[Desktop Action/q' "$cur_file" | grep "GenericName\[$PREFERRED_LANGUAGE\]=" | sed "s/GenericName\[$PREFERRED_LANGUAGE\]=//g")" t_comment="$(sed '/\[Desktop Action/q' "$cur_file" | grep "Comment\[$PREFERRED_LANGUAGE\]=" | sed "s/Comment\[$PREFERRED_LANGUAGE\]=//g")" [ -n "$t_title" ] && it_title[i]="$t_title" [ -n "$t_desc" ] && it_desc[i]="$t_desc" [ -n "$t_comment" ] && it_comment[i]="$t_comment" fi grep -q "Terminal=true" "$cur_file" && it_term[i]="true" grep -q "NoDisplay=true" "$cur_file" && it_display[i]="false" grep -q "OnlyShowIn=" "$cur_file" && it_only[i]="true" # write files printf "%s\n" "${it_title[i]}" >> "$TITLEFILE" printf "%s\n" "${it_icon[i]}" >> "$ICONFILE" printf "%s\n" "${it_desc[i]}" >> "$DESCFILE" printf "%s\n" "${it_exec[i]}" >> "$EXECFILE" printf "%s\n" "${it_file[i]}" >> "$FILEFILE" printf "%s\n" "${it_term[i]}" >> "$TERMFILE" printf "%s\n" "${it_display[i]}" >> "$DISPLAYFILE" printf "%s\n" "${it_comment[i]}" >> "$COMMENTFILE" printf "%s\n" "${it_only[i]}" >> "$ONLYFILE" # log it all printf "%d. Title - %s\n" "${i}" "${it_title[i]}" >> "$LOGFILE" printf "%d. Description - %s\n" "${i}" "${it_desc[i]}" >> "$LOGFILE" printf "%d. Executable - %s\n" "${i}" "${it_exec[i]}" >> "$LOGFILE" printf "%d. Icon - %s\n" "${i}" "${it_icon[i]}" >> "$LOGFILE" printf "%d. File - %s\n" "${i}" "${it_file[i]}" >> "$LOGFILE" printf "%d. Term - %s\n" "${i}" "${it_term[i]}" >> "$LOGFILE" printf "%d. Display - %s\n" "${i}" "${it_display[i]}" >> "$LOGFILE" printf "%d. Only - %s\n" "${i}" "${it_only[i]}" >> "$LOGFILE" printf "%d. Comment - %s\n" "${i}" "${it_comment[i]}" >> "$LOGFILE" done command -v desktop_post_caching_func > /dev/null && desktop_post_caching_func print_desktop_list "noc" else # we have entries, let's populate the arrays command -v desktop_pre_func > /dev/null && desktop_pre_func mapfile -t it_title < "$TITLEFILE" mapfile -t it_icon < "$ICONFILE" mapfile -t it_exec < "$EXECFILE" mapfile -t it_file < "$FILEFILE" mapfile -t it_desc < "$DESCFILE" mapfile -t it_display < "$DISPLAYFILE" mapfile -t it_only < "$ONLYFILE" mapfile -t it_comment < "$COMMENTFILE" d_print_array fi } d_print_array() { command -v print_array > /dev/null && print_array && return for i in "${!it_title[@]}"; do [ "$RESPECT_ONLYSHOWIN" != "false" ] && [ "${it_only[i]}" != "false" ] && continue [ "$RESPECT_NODISPLAY" != "false" ] && [ "${it_display[i]}" != "true" ] && continue if [ -f "${it_icon[i]}" ] && [ -n "${it_title[i]}" ] && [ -n "${it_exec[i]}" ] && [ "$IMAGE" != "false" ]; then printf "%s\t%s" "img://${it_icon[i]}" "${it_title[i]}" elif [ -n "${it_title[i]}" ] && [ -n "${it_exec[i]}" ]; then printf "%s" "${it_title[i]}" else continue fi if [ -n "${it_desc[i]}" ] && [ "$DISPLAY_DESCRIPTION" = "true" ]; then [ "$DMENU_COMPAT" != "true" ] && COL='\033[0;31m' printf -- "$DESCRIPTION_SEPARATOR%b%s" "${COL}" "${it_desc[i]}" fi if [ -n "${it_comment[i]}" ] && [ "$DISPLAY_COMMENT" = "true" ]; then [ "$DMENU_COMPAT" != "true" ] && COL='\033[0;32m' printf -- "$COMMENT_SEPARATOR%b%s" "${COL}" "${it_comment[i]}" fi printf "\n" done } exec_program() { [ ! -f "$TITLEFILE" ] || [ ! -f "$EXECFILE" ] || [ ! -f "$TERMFILE" ] && exit 1 mapfile -t it_title < "$TITLEFILE" mapfile -t it_exec < "$EXECFILE" mapfile -t it_term < "$TERMFILE" # set exec [ -z "${it_exec[1]}" ] && printf "Executable array is empty.\n" >> "$LOGFILE" for i in "${!it_title[@]}"; do if [ "${it_title[i]}" = "$menusel" ]; then exec="${it_exec[i]}" term="${it_term[i]}" printf "Executable %s is: '%s'\n" "$i" "${it_exec[i]}" >> "$LOGFILE" printf "Executable %s term status: '%s'\n" "$i" "${it_term[i]}" >> "$LOGFILE" break; else printf "Executable %s is: '%s'\n" "$i" "${it_exec[i]}" >> "$LOGFILE" printf "Executable %s term status: '%s'\n" "$i" "${it_term[i]}" >> "$LOGFILE" fi done # finally run the program if [ -n "$exec" ] && [ "$term" = "false" ]; then ${SHELL:-/bin/sh} -c "$exec" & elif [ -n "$exec" ] && [ "$term" = "true" ]; then ${TERMINAL} -e "$exec" & else printf "No executable found. Try clearing cache." >> "$LOGFILE" fi } print_desktop_menu() { check_desktop HIDDEN_ENTRY_KEYWORDS="${HIDDEN_ENTRY_KEYWORDS:-NULL_ENTRY}" print_desktop_list "" | uniq "${uniq_args[@]}" | sort "${sort_args[@]}" | grep -vE "$HIDDEN_ENTRY_KEYWORDS" | grep -E "$ENTRY_KEYWORDS" | $RUNLAUNCHER "${rl_desktop[@]}" > /tmp/spmenu_out while read -r menusel; do [ "$menusel" = "?" ] && print_desktop_help && print_desktop_menu command -v desktop_post_func > /dev/null && desktop_post_func "$menusel" [ -z "$menusel" ] && return 1 \ || printf "User input: %s\n" "$menusel" >> "$LOGFILE" # this must now be the title only menusel="$(printf "%s" "$menusel" | sed "s/ -.*//")" exec_program "$@" [ "$MULTISELECT" != "true" ] && break done < /tmp/spmenu_out; rm -f /tmp/spmenu_out } prep_desktop() { printf "spmenu_run log (%s)\nFunction: Desktop\n" "$(date "+%D %T")" > "$LOGFILE" } clear_cache() { rm -f "${TITLEFILE}" "${FILEFILE}" "${EXECFILE}" "${ICONFILE}" "${DESCFILE}" "${TERMFILE}" "${DISPLAYFILE}" "${ONLYFILE}" "${COMMENTFILE}" } main() { rm -f "$LOGFILE" print_config read_args "$@" check "$args" if [ ! -f "$HISTORY" ]; then mkdir -p "$(dirname "$HISTORY")" touch "$HISTORY" && HIST_ARG="--hist-file $HISTORY" || printf "spmenu_run: Failed to write history file" >> /dev/stderr else HIST_ARG="--hist-file $HISTORY" fi # some run launcher args RUNLAUNCHER_FM_ARGS="--insert $HIST_ARG $RUNLAUNCHER_FM_ARGS $MARGS" RUNLAUNCHER_RUN_ARGS="--insert $HIST_ARG $RUNLAUNCHER_RUN_ARGS $MARGS" RUNLAUNCHER_BM_ARGS="--insert $HIST_ARG --prompt Bookmarks $RUNLAUNCHER_BM_ARGS $MARGS" RUNLAUNCHER_DESKTOP_ARGS="--sgr1 $DESCRIPTION_COLOR --sgr2 $COMMENT_COLOR --lines 20 --columns 1 --image-size 100 --image-gaps 20 --display-icons $RUNLAUNCHER_DESKTOP_ARGS $MARGS" RUNLAUNCHER_HELP_ARGS="--insert $HIST_ARG $RUNLAUNCHER_HELP_ARGS $MARGS" # dmenu compatibility DMENU_FM_ARGS="-l 20 $MARGS" DMENU_RUN_ARGS="$MARGS" DMENU_BM_ARGS="$MARGS -p Bookmarks" DMENU_DESKTOP_ARGS="-l 20 $MARGS" DMENU_HELP_ARGS="-l 20 $MARGS" COMPAT_LS_ARGS="--color=never" if [ "$DMENU_COMPAT" != "false" ]; then IMAGE="false" RUNLAUNCHER="dmenu" RUNLAUNCHER_FM_ARGS="$DMENU_FM_ARGS" RUNLAUNCHER_RUN_ARGS="$DMENU_RUN_ARGS" RUNLAUNCHER_DESKTOP_ARGS="$DMENU_DESKTOP_ARGS" RUNLAUNCHER_HELP_ARGS="$DMENU_HELP_ARGS" RUNLAUNCHER_BM_ARGS="$DMENU_BM_ARGS" LS_ARGS="$COMPAT_LS_ARGS" fi # read to arrays read -ra rl_fm <<< "${RUNLAUNCHER_FM_ARGS}" read -ra rl_run <<< "${RUNLAUNCHER_RUN_ARGS}" read -ra rl_bm <<< "${RUNLAUNCHER_BM_ARGS}" read -ra rl_desktop <<< "${RUNLAUNCHER_DESKTOP_ARGS}" read -ra rl_help <<< "${RUNLAUNCHER_HELP_ARGS}" read -ra ls_args <<< "${LS_ARGS}" read -ra desktopdir <<< "${DESKTOP_DIR}" read -ra icondir <<< "${ICON_DIR}" # clear cache [ "$clearcache" = "true" ] && clear_cache # $PATH listing case "$function" in "run") parse "$args" ;; "fm") prepare_dirnav "$args" && exit 0 || exit 1 ;; "desktop") prep_desktop print_desktop_menu "$args" ;; "help") print_cli_help exit 0 ;; *) printf "Undefined function: '%s'\n" "$function" exit 1 ;; esac } # shellcheck disable=SC2031 main "$@"