Troubleshooters.Com®, Linux Library
and Dmenu Productivity Primer Present:

Dmenu Best Practices

CONTENTS:

Introduction

The dmenu system fills two niches in this world:

  1. The menu system for the dwm window manager.
  2. A massively productivity boosting keyboard driven tool for all window managers and desktop environments.

This document is concerned with niche #2, exclusively. The productivity boost bestowed by dmenu, to the touch typist or intensive computer user, cannot be overstated.

Like all other software, dmenu isn't perfect. This document's best practices minimize its disadvantages, which follow:

The dmenu Executable

Dmenu ships with three programs:

I very strongly believe that dmenu_path and dmenu_run are unnecessary, and in fact often problematic, when running dmenu outside the confines of the dwm window manager. So this section confines its discussion with dmenu, the binary dmenu executable.

dmenu is an incredibly simple program. As input it takes a list of words or phrases, one word or phrase on each line, coming in via stdin. It displays several of those lines with words or phases on the screen. As you type letters, all phrases not containing a string of the letters you typed are eliminated. In many cases, the list of conforming words or phrases can be narrowed to just one by the time three or four letters are typed.

Note:

Although the list isn't required to be sorted, sorting the list helps with human comprehension so I recommend it. Also, dmenu becomes almost unusable unless each line is unique, which requires sorting:

phrase=`cat whatever.list | sort -ui | dmenu -i -l 20`

However long the list, there's a highlight that covers just one word/phrase. It can be moved up and down by the arrow keys (cursor keys) on the keyboard. When the highlight rests on the right word/phrase, either because there's only one left or because you've navigated to it with the arrow keys, if you press the Enter key two things occur:

  1. The word/phrase is echoed on stdout.
  2. dmenu exits with a return code of 0.

On the other hand, if you decide you don't want anything on the list, you can press the Escape key, in which case nothing is echoed to the screen, and dmenu exits with a return code of 1. The following short shellscript showcases dmenu by enabling one to select one service name from the names in /etc/services:

#!/bin/sh

phrase=`cat /etc/services |  \
  grep -v ^\s*# |  \
  grep -v ^\s*$ | \
  sed -e's/\s.*//' | \
  sort -ui | \
  dmenu -i -l 20`

rtrn=$?

if test "$rtrn" = "0"; then
  echo "You chose servicename $phrase"
else
  echo "You aborted."
fi

In the preceding shellscript, the cat, the two greps and the sed create a list of not-commented service names, and only not-commented service names. The sort command puts the servicenames in alpha-order and removes duplicates. The dmenu command lists the servicenames, accepts user keystrokes, and if the user chooses with the Enter key, prints his choice and exits with code 0. If the user presses Escape to abort the process, it prints nothing and exits with exit code 1. Either way, variable $phrase contains the chosen service name or nothing if aborted. The line rtrn=$? captures the return code: 0 if the user pressed Enter, 1 if the user pressed Escape.

The rest of the program just showcases the logic for handling the exit code and the chosen phrase if exit code 0.

General Purpose Tool

As previous parts of this section show, dmenu is a very general tool that can be used for menus of all types. In fact, it can be nicely used in building menu interfaces in Command Line Interface (CLI) applications. With suitable directory-tree recursion techniques, it can be used to build a CLI filepicker interface.

Although dmenu is a great general purpose tool and a spectacular example of code reuse, this document mostly concerns itself with using dmenu to quickly and easily run programs from your keyboard.

Hotkeying to Dmenu

Dmenu would be useless if you needed to type in "dmenu" or click something every time you want to use it. Dmenu becomes a timesaver only when it's run by an easy to type hotkey. Most window managers and desktop environments include ways to assign hotkeys to programs you can run, so what you do is tell your window manager or desktop environment to associate an easy hotkey to run the shellscript by which you invoke dmenu. Because hotkey assignment is window manager dependent, it's not included in this document. That being said, the vast majority of window managers and desktop environments can associate a hotkey with dmenu.

Personally, I bring up dmenu with Ctrl+Shift+semicolon. This combo is unlikely to be used by any other programs, and is quick and easy to type on a standard keyboard.

Dmenu as a Program Runner

The way you use dmenu to run programs is to feed it a list of executable programs on the computer's path ($PATH). The user then types keys to select the program to be run, presses Enter, and the program is run. This section describes how to make the shellscripts to run dmenu optimally: In other word, everything you need to know except assigning a hotkey to your Dmenu setup.

The Main Shellscript

The following is the main shellscript to invoke dmenu optimally. It is the shellscript that you'll assign your hotkey to:

#!/bin/sh

mycache=~/executables.cache

choice=`cat $mycache | \
  dmenu -i -l 32 \
  -fn 12x24 \
  -nf yellow -nb black -sf black -sb white`

[ "$?" = "0" ] && exec $choice

list_executables_on_path.sh > $mycache

People will argue vehemently that the preceding shellscript is no good, because it updates the cache after running dmenu. Every other document I've seen, every implementation of dmenu_run, has the cache being checked and updated before running dmenu, so that dmenu works on an up to date cache. My method is better because it gets rid of all perceptible latency in every situation, whereas theirs has a 1/8 second gap between pressing the hotkey and seeing the menu. That 1/8 second doesn't sound like much, but when you're operating in full keyboard mode, that 1/8 second gap can be an unconscious annoyance. Or at least unconcious until you've worked without it. The only time post-dmenu cache update is a disadvantage is when either you've added new programs, or the cache file has been deleted. Either way, running the preceding shellscript one more time cures the problem.

You probably noticed that the preceding shellscript uses list_executables_on_path.sh to update the cache. There are many ways to write a program to list all executable-permissioned files on $PATH. One excellent idea would be to write a C program to do it. Doing it in Python would be slower, but that matters little if you're doing post-dmenu cache update. What I did was use a bash builtin command called compgen with option -c, which for reasons I can't fathom from the bash manpage lists all executable files on $PATH. Using compgen -c, the following is how I implemented list_executables_on_path.sh:

#!/bin/bash
compgen -c | sort -ui

Warning:

The preceding shellscript works only with bash, because only bash has compgen as a builtin command. I'm not a fan of running shellscripts with bash, but in this case it's necessary.

Working With and Around Dmenu's Biggest Landmine

For a touch-typist, dmenu is almost perfect. For the touch typist who never makes mistakes, you can remove the word "almost" from the preceding sentence. The problem is, if you start certain Command Line Interface (CLI) programs from dmenu, in certain circumstances all hell can break loose. It doesn't happen all the time, and some installations can't even reproduce it, but if it happens to you, you're not going to like it.

The symptom is this: All of a sudden many of your currently running GUI programs become unresponsive to the keyboard or the mouse, and their video updates crazily or not at all. This happens immediately after you use dmenu to start certain CLI programs. dmenu should never be used to start CLI programs: Use it only to start GUI programs.

From my research, this symptom typically happens with CLI programs that insist on receiving input from the keyboard rather than just STDIN: Mostly Curses programs; expect many of your GUI programs to go unresponsive and video-crazy if you run any Curses (nCurses) related program. But it's not limited to Curses-based programs: Regular programs and shellscripts can do it too. The following shellscript, which I call procstopper.sh, when run from dmenu, messes up a lot of the currently running GUI programs:

#!/bin/bash

stty cbreak </dev/tty >/dev/tty 2>&1
while /bin/true
    IFS= read -r -n1 mychar
    do
       echo  "$mychar"
       if test "$mychar" = "q"; then
	       exit 0
       fi
    done
stty -cbreak </dev/tty >/dev/tty 2>&1

You may be able to reproduce the symptom by running the preceding shellscript from dmenu, or by starting elinks with dmenu.

Note:

My preliminary research tells me this symptom isn't universal. I've run across others who couldn't produce this symptom. My suspicion at this point is that your ability to produce this symptom depends on your window manager. I'm using Openbox, so I'd expect if you're using Openbox or LXDE or LXQt, both of which are built on top of Openbox, you'll probably be able to reproduce this symptom. But like I said, this is all preliminary: Not enough hard information yet.

When this symptom occurs, it's very disconcerting, and it can seem like only a complete shutdown of X can cure the problem. But that's not true, as a look at the output of ps axjf reveals. A lot of the GUI programs have gotten themselves into a state where they're waiting for keyboard input, as indicated by the uppercase T in their STAT column.

The trick is to first kill the programs that got your GUI programs into this state (including the CLI program that was started in dmenu). Then you revive the non-responsive GUI programs. These two tasks can be done manually by somebody who knows what they're doing and is handy with the ps and kill commands, or you can just run the following shellscript, called clear_dmenu.sh:

#!/bin/sh
rm -f ~/danger.sh
if test -e ~/danger.sh; then
	echo Cowardly refusal to continue because ~/danger.sh exists.
	echo Delete ~/danger.sh manually.
	exit 1
fi
ps axj | /d/bats/clear_dmenu.awk | sort | sed -e"s/..//" > ~/danger.sh
chmod a+x ~/danger.sh
~/danger.sh

The preceding shellscript depends on the following clear_dmenu.awk:

#!/usr/bin/gawk -We

{
	ppid=$1
	pid=$2
	stat=$7
	cmd=$10
	#print stat, pid
	}

cmd ~ /chromium/{stat = "Tl"}
stat == "Tl"{
	print "Z kill -s CONT " pid     "  #  "    $10 $11 $12 $13
}

(stat == "T") && (ppid == "1") {
	print "A kill -s CONT " pid     "  #  "  $10 $11 $12 $13
}

(stat == "T") && (ppid != "1") {
	print "A kill -s KILL " pid     "  #  "  $10 $11 $12 $13
}

Security Warning:

The preceding shellscript creates then runs another shellscript. This is insecure and should only be done in low-security environments. If your environment requires more security, you'll need to have the AWK program actually run the commands instead of putting them in a shellscript file. I like the shellscript file technique for debugging and investigation, but I work in a relatively low security environment single user environment.

Most of the time, you can run the clear_dmenu.sh shellscript, and for the most part the bogus processes will be killed while the needed processes will be unstuck by the kill -s CONT command. But sometimes, either because that command doesn't work or because you absolutely cannot afford to lose data in case a program aborts, you must clear the jam manually...

Clearing the Jam Manually

The ps axj  command gives a bunch of columns. The 7th column is called STAT. The only processes of interest while clearing the jam are those whose STAT value begins with an upper case T. You can quickly make a list of all of those, but with some other processes thrown in (false positives), using the following command:

ps axj | grep \\sT

The reason for the double backslash is because the shell itself needs two backslashes to interpret as one, that one being passed on to grep.

Note:

You can get an exact list of such processes, without the false positives bestowed by the grep version, with an AWK one-liner. If that sounds better to you, I leave that as an exercise for the reader.

Under normal circumstances, you should have no processes whose STAT value starts with an uppercase T, and every one of them should either be gotten rid of or resurrected with the CONT signal. Your job is to know which is which, and do the job. The uppercase T means "Stopped, either by a job control signal or because it is being traced." When it's followed with a lower case l that means it's multi-threaded, which would be indicative (but not proof) that the process is a genuine application and not scaffolding like span.code

To see an example of a system jammed up by an instance of elinks started by dmenu, click here.

Any process with an uppercase T as the first character of its STAT is operating incorrectly. Either it is waiting on a keyboard input that will never come (because it's in the background) or it's unresponsive because although it's in the foreground, some background process is waiting. The solution is two part:

  1. Kill (destroy) all background process waiting on keyboard input. This is typically done with kill -s KILL <pid>.
  2. Continue all foreground processes (usually GUI) with kill -s CONT -<ppid>.

The reason the second command works is because, typically, all the jammed up GUI programs have the same parent PID (PPID). The kill command can operate on all children of a given parent by using a minus sign before the PPID, instead of the typical PID without the minus sign.

Wrapup

For me, the Dmenu program has become indispensable, on all my computers, on any window manager. It enables me to work much faster, with much less thought. It gives me a way to run programs if my mouse fails. Because it greatly reduces window manager level dependency on the mouse, dmenu makes it practical to operate without a panel (taskbar) and without a large section of the desktop reserved for clicking and unavailable for applications. Dmenu makes it practical to use your entire screen to run applications, and in a world where many folks jump through all sorts of hoops to use multiple monitors, it's obvious screen real estate is at a premium.

When used as a program-runner, Dmenu is really just a keyboard-constrained, alpha-sorted menu of the computer's programs. It therefore has all the same needs as any menu:

As shipped from Suckless Tools or your favorite Distro, Dmenu already has #1 and #2. As shipped, it fails #3 miserably for anyone whose vision doesn't match that of a hawk, but configuring away this disadvantage is trivial, and explained in the Dmenu as a Program Runner section of this document.

If you type over 30 words per minute and care about productivity, try Dmenu and set it up as described in this document. You'll soon consider it indispensable too.