#!/bin/bash # Run a command, or search the web for it. # # Enter run it if it is an executable, otherwise search for it # Alt+Enter always search, even if it looks like a command # # The second one exists because deciding by "is the first word a binary?" gets # it wrong for ordinary phrases: test, who, which, sort, time and file are all # real executables. Alt+Enter is bound to fuzzel's custom-1, which is dmenu-only # and exits with code 10 (see [key-bindings] in fuzzel.ini). browser_app_id="google-chrome" search_url="https://duckduckgo.com/?q=" # Executables in PATH. Deliberately not `compgen -c`: that also lists shell # builtins and keywords (cd, alias, if, ...), which `command -v` reports as # found but `exec` cannot run. list_path_bins() { local IFS=: dir file for dir in $PATH; do [[ -d $dir ]] || continue for file in "$dir"/*; do [[ -f $file && -x $file ]] && printf '%s\n' "${file##*/}" done done | sort -u } search_web() { local query query=$(jq -rn --arg q "$1" '$q|@uri') xdg-open "$search_url$query" & # Wait for the browser window instead of guessing at a fixed sleep: a cold # start takes well over half a second, while an already-running Chrome is # ready almost immediately. local _ for _ in $(seq 40); do if swaymsg -t get_tree | jq -e --arg id "$browser_app_id" \ 'recurse(.nodes[]?, .floating_nodes[]?) | select(.app_id == $id)' >/dev/null; then swaymsg "[app_id=\"$browser_app_id\"] focus" >/dev/null return 0 fi sleep 0.05 done } result=$(list_path_bins | fuzzel --dmenu) status=$? [[ -z $result ]] && exit 0 case $status in # Alt+Enter: search regardless of what the text looks like. 10) search_web "$result" ;; # Enter: run it if it really is an executable. type -P looks only at PATH, # so builtins and keywords fall through to the search instead of failing # silently in exec. 0) if type -P "${result%% *}" >/dev/null 2>&1; then exec $result fi search_web "$result" ;; # Escape, or fuzzel failed to start. *) exit 0 ;; esac