Instantly share code, notes, and snippets.
Last active
July 16, 2025 13:24
-
Star
0
(0)
You must be signed in to star a gist -
Fork
0
(0)
You must be signed in to fork a gist
-
Save Ajnasz/4f1586775d4c6edd3666fc376d4b3296 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env sh | |
# This script extends the functionality of dmenu_run by allowing the user to | |
# include custom commands and executables. The custom commands are read from a | |
# file specified by the environment variable DMENU_RUN_COMMANDS, and the | |
# executables are read from a directory specified by the environment variable | |
# DMENU_RUN_BIN. | |
# | |
# Environment Variables: | |
# DMENU_RUN_COMMANDS: Path to a file containing custom commands to be included | |
# in the dmenu. | |
# Each line can be a regular command or an alias | |
# | |
# DMENU_RUN_BIN: Path to a directory containing executables. | |
# The executables should echo the commands to be included in | |
# the dmenu. | |
# If executable should print a command or an alias. | |
# | |
# Usage: | |
# Set the environment variables DMENU_RUN_COMMANDS and DMENU_RUN_BIN to point | |
# to your custom commands file and executables directory, respectively. Then, | |
# run the script. The script will add the custom commands and executables to | |
# the dmenu. | |
# | |
# Example: | |
# DMENU_RUN_COMMANDS=/path/to/commands.txt DMENU_RUN_BIN=/path/to/bin \ | |
# ./dmenu_run2 | |
# | |
# Aliases: | |
# If you want to include aliases in the dmenu, you can define them in the following | |
# forrmat: | |
# alias name="command --with-args" | |
# alias name with spaces="command2" | |
# The script will extract the alias name and command from the alias definition | |
# and include them in the dmenu. | |
# | |
# Note: | |
# The script assumes that all files in the DMENU_RUN_BIN directory are | |
# executable and that they will echo the correct commands. Ensure that these | |
# conditions are met to avoid unexpected behavior. | |
# | |
# The script runs in the background, and the selected command from the dmenu | |
# is executed in a subshell. | |
# | |
# FZF environment variable: | |
# The script sets the FZF environment variable to the original dmenu command | |
# This allows you to open dmenu with the same options as the original dmenu_run2 | |
# example script to search for words in the dictionary with dmenu or fzf: | |
# > cat /usr/local/bin/words | |
# #/usr/bin/env sh | |
# | |
# fuzzy_find="${FZF:-fzf}" | |
# $fuzzy_find < /usr/share/dict/words | |
# | |
# If the `words` script called from dmenu_run2 the use dmenu to select a word | |
# otherwise it will use `fzf` | |
# Function to extract alias name from alias definition | |
extract_alias_name() { | |
printf '%s\n' "$1" | sed 's/^alias \([^=]*\)=.*/\1/' | |
} | |
# Function to extract alias command from alias definition | |
extract_alias_command() { | |
printf '%s\n' "$1" | sed 's/^alias [^=]*=//' | sed 's/^"\(.*\)"$/\1/' | |
} | |
# shellcheck disable=SC2317 # Don't warn about unreachable commands in this function | |
cleanup() { | |
rm -f "$DMENU_FIFO" "$DMENU_ALL_COMMANDS" | |
} | |
# Function to process commands and aliases | |
process_custom_commands() { | |
while IFS= read -r line || [ -n "$line" ]; do | |
# Skip comments and empty lines | |
case "$line" in | |
\#*|'') continue ;; | |
alias\ *=*) | |
# Extract and display alias name only | |
extract_alias_name "$line" | |
;; | |
*) | |
# Regular command | |
printf '%s\n' "$line" | |
;; | |
esac | |
done | |
} | |
# Function to resolve alias to its command | |
# shellcheck disable=SC2317 # Don't warn about unreachable commands in this function | |
resolve_alias() { | |
while IFS= read -r selected || [ -n "$selected" ]; do | |
# read $DMENU_RUN_COMMANDS and resolve aliases | |
while IFS= read -r line || [ -n "$line" ]; do | |
case "$line" in | |
alias\ *=*) | |
alias_name=$(extract_alias_name "$line") | |
if [ "$selected" = "$alias_name" ]; then | |
alias_command=$(extract_alias_command "$line") | |
# If selected item is an alias, print its command | |
printf '%s\n' "$alias_command" | |
break | |
fi | |
;; | |
*) | |
# If selected item is a regular command, print it as is | |
if [ "$selected" = "$line" ]; then | |
printf '%s\n' "$line" | |
break | |
fi | |
;; | |
esac | |
done < "$DMENU_ALL_COMMANDS" | |
done | |
} | |
if [ -z "$DMENU_RUN_BIN" ] || [ ! -d "$DMENU_RUN_BIN" ];then | |
echo "Warning: DMENU_RUN_BIN is not set or is not a directory" >&2 | |
fi | |
if [ -z "$DMENU_RUN_COMMANDS" ] || [ ! -f "$DMENU_RUN_COMMANDS" ];then | |
echo "Warning: DMENU_RUN_COMMANDS is not set or is not a file" >&2 | |
fi | |
DMENU="${DMENU:-dmenu}" | |
# DMENU="rofi -dmenu -i -no-fixed-num-lines -no-show-icons" | |
if [ -n "$WAYLAND_DISPLAY" ]; then | |
DMENU="bemenu" | |
fi | |
# Create FIFO for streaming to dmenu | |
DMENU_FIFO="$(mktemp -u -t dmenu_fifo_XXXXXX)" | |
mkfifo "$DMENU_FIFO" | |
# Create temp file for alias resolution (still needed) | |
DMENU_ALL_COMMANDS="$(mktemp -t dmenu_run2_XXXXXX)" | |
trap cleanup EXIT INT TERM | |
# Populate both FIFO and temp file simultaneously | |
{ | |
# include output of executables from DMENU_RUN_BIN | |
if [ -n "$DMENU_RUN_BIN" ] && [ -d "$DMENU_RUN_BIN" ]; then | |
find "$DMENU_RUN_BIN" -maxdepth 1 -type f -executable -exec {} \; | |
fi | |
# include custom commands from DMENU_RUN_COMMANDS | |
if [ -n "$DMENU_RUN_COMMANDS" ] && [ -f "$DMENU_RUN_COMMANDS" ]; then | |
cat "$DMENU_RUN_COMMANDS" | |
fi | |
# always include dmenu_path | |
if [ "$DMENU_NO_PATH" != "true" ]; then | |
dmenu_path; | |
fi | |
} | tee "$DMENU_ALL_COMMANDS" > "$DMENU_FIFO" & | |
TEES_PID=$! | |
# Process and execute | |
if process_custom_commands < "$DMENU_FIFO" | $DMENU "$@" | resolve_alias | env FZF="$DMENU $*" "${SHELL:-"/usr/bin/env sh"}" & | |
then | |
DMENU_PID=$! | |
else | |
echo "Failed to start dmenu process" >&2 | |
kill $TEES_PID 2>/dev/null | |
exit 1 | |
fi | |
wait $TEES_PID | |
TEE_EXIT=$? | |
if [ $TEE_EXIT -ne 0 ]; then | |
echo "Warning: Data population process failed" >&2 | |
fi | |
wait $DMENU_PID | |
DMENU_EXIT=$? | |
# Don't cleanup here since trap will handle it | |
exit $DMENU_EXIT |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/sh | |
# Interactive Dmenu Command Pipeline | |
# | |
# This script accepts an executable file as input and creates an interactive | |
# pipeline where the output of each command execution becomes input for dmenu, | |
# and the dmenu selection becomes input for the next command execution. | |
# | |
# Usage: ./dmenu_script <executable_file> <dmenu_options> | |
# | |
# The executable file should accept an optional argument and output selectable | |
# options. | |
# The loop continues until an empty selection is made or either the script or | |
# dmenu execution failed. | |
# | |
# Example: | |
# ./dmenu_script my_command.sh -l 15 -p "Select an option:" -fn 'monospace-10' -nb '#222222' -nf lightblue -sb black -sf pink | |
# | |
# Example of `my_command.sh`: | |
# #!/usr/bin/env sh | |
# | |
# case "$1" in | |
# "") | |
# printf 'foo\nnon existing command' | |
# ;; | |
# "foo") | |
# echo "bar" | |
# ;; | |
# "bar") | |
# # final command selected, perform some action | |
# exit 0 | |
# ;; | |
# *) | |
# echo "Invalid argument: $1" >&2 | |
# echo "Usage: $0 <foo|bar>" >&2 | |
# exit 1 | |
# esac | |
if [ $# -eq 0 ];then | |
exit 0 | |
fi | |
if [ ! -e "$1" ];then | |
echo "File $1 does not exist." >&2 | |
exit 1 | |
fi | |
if [ ! -x "$1" ];then | |
echo "File $1 is not executable." >&2 | |
exit 1 | |
fi | |
script_name="$1" | |
shift | |
DMENU="${DMENU:-dmenu}" | |
if [ -n "$WAYLAND_DISPLAY" ]; then | |
DMENU="bemenu" | |
fi | |
while true;do | |
if [ -z "$ret" ];then | |
if ! ret="$($script_name)";then | |
echo "Command execution failed." >&2 | |
exit 1 | |
fi | |
else | |
if ! ret="$($script_name "$ret")";then | |
echo "Command execution failed." >&2 | |
exit 1 | |
fi | |
fi | |
if [ -z "$ret" ];then | |
exit 0 | |
fi | |
if ! ret="$(echo "$ret" | $DMENU "$@")";then | |
echo "Dmenu selection failed or was cancelled." >&2 | |
exit 1 | |
fi | |
done |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment