Enhancing dmenu_run: A Customizable Application Launcher for i3

I wrote a script a while ago that extends the functionality of dmenu_run by allowing me to include custom commands and dynamically generated entries from executable scripts.

First of all what is dmenu?

Dmenu expects newline-separated items on stdin, shows these items on the top of the screen (configurable) where you can search for the items, then and outputs the selected item to stdout. It's so simple and still so powerful, very similar to fzf in the shell. A common usecase is to use it as an application launcher, like in the i3 window manager. For example, you can bind it to a key combination and when you press it, you can start typing the name of the application you want to start like firefox, and it will show you the matching applications. Pressing enter will start the firefox.

The motivation

There is a dmen_run helper script shipped with dmenu. It lists all the applications, executables available for the user (included in it's $PATH), so it achieves the application launcher usecase. However I wanted to extend it a bit. For example I wanted to be able to manage the network settings using ncmli, like turn off wifi, switch wifi networks or to start a VPN connection, create different tmux layouts, etc.

The solution

My shell script reads two environment variables.

DMENU_RUN_COMMANDS points to a file, that contains static command, one command per line. Examples are nmcli radio wifi on, nmcli radio wifi off, systemctl suspend or systemctl poweroff. I can easily find them by typing wifi on, wifi off, poweroff or suspend.

DMENU_RUN_BIN points to a directory where I have other shell scripts which output are the commands I want to run. For example to be able to connect or disconnect to the the available wifi and vpn connections I have in netwrokmanager I use the following shell script:

#!/bin/sh

nmcli connection show | awk 'BEGIN { FS="  +" } NR > 1 {
  printf("nmcli connection up \"%s\"\n", $1)
  printf("nmcli connection down \"%s\"\n", $1)
}'

I place it in a file under the DMENU_RUN_BIN directory and I can easily connect or disconnect to the available connections.

The features

  • The ability to add frequently used commands without modifying system-wide configurations.
  • Integration of dynamic scripts that generate entries on demand.
  • Seamless support for both X11 (dmenu) and Wayland (bemenu).

The script

I posted the script as gist on github, but I will also include it here:

#!/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. The file should contain one command per line.
#
#   DMENU_RUN_BIN: Path to a directory containing executables.
#                  The executables should echo the commands to be included in
#                  the dmenu.
#
# 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
#
# 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.

if [ -z "$DMENU_RUN_BIN" ] || [ ! -d "$DMENU_RUN_BIN" ];then
  echo "DMENU_RUN_BIN is not set or is not a directory" >&2
fi

if [ -z "$DMENU_RUN_COMMANDS" ] || [ ! -f "$DMENU_RUN_COMMANDS" ];then
  echo "DMENU_RUN_COMMANDS is not set or is not a file" >&2
fi

{
  # always include dmenu_path
  dmenu_path;

  # 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
} | dmenu "$@" | ${SHELL:-"/usr/bin/env sh"} &

exit 0;

Usage

  1. Save the script to a file, for example dmenu_run2.
  2. Make the script executable by running chmod +x dmenu_run2.
  3. Create a keybinding in your window manager to run the script:
    • bindsym $mod+d exec DMENU_RUN_BIN="$HOME/path/to/dmen/scripts/dir" DMENU_RUN_COMMANDS="$HOME/path/to/dmenu/commands/file" /path/to/dmenu_run2 -p 'Run:'
    • see man dmenu for more options, the script will pass all the arguments to dmenu
  4. Press mod+d to launch the dmenu.
  5. Have fun!

Hozzászóláshoz a Disqus szolgáltatását használom, korábbi vélemények elovlasásához és új hozzászólás írásához engedélyezd a Disqus-tól származó JavaScripteteket.