2022-12-18
My dmenu wrapper script and what it will invoke for me
Dmenu needs a wrapper script in order to do something useful, because all it does by itself is read autocompletion entries from standard input and write your selection to standard output. The traditional basic wrapper script runs a command found in your $PATH, or perhaps does some other single purpose action like passing a URL you entered to a browser. My wrapper script has always been a little more complicated, and has evolved to primarily do three different things: run commands (using a custom $PATH for dmenu), pass 'URLs' to my primary Firefox session, and start terminal sessions to local hosts, possibly using alternate usernames.
(As part of starting a terminal session, I also add the host as a button to my pyhosts instance, so that if I want another connection to the machine I can just click the button instead of having to enter the name.)
Customizing and broadening what I can invoke through dmenu (both through the wrapper script itself and by writing little support scripts) has given me a single 'invoke more or less anything from the keyboard' interface. I don't do everything through the keyboard with dmenu in my desktop setup, but in practice I've wound up doing a great deal that way. I could do more, but one constraint of doing everything through a single dmenu instance is that there are only so many good short abbreviations that I can keep straight.
How my wrapper script tells these cases apart is not entirely straightforward. A local host is a hostname without a dot in it, or with a few local suffixes (such as .sandbox), and a hostname is something that resolves to an IP address. An URL is something with one of the known schemes, but also anything that matches patterns like *.*/*, *:*/*, about:*, or even */, and for convenience even things with IP addresses if they have a dot in them (so I can enter 'example.org' in dmenu and open it as a URL instead of trying to ssh to it). And all of this matching is done on the first word of dmenu's output, which allows me to add both command arguments (which is crucial for many of my special custom scripts for dmenu) and special additional suffixes on hostnames that my 'start a terminal session' script uses for special behavior (such as using urxvt as the terminal instead of xterm).
(Some commands also have special behavior associated with them, such as redirecting their output to something other than the default sink for dmenu output as a whole.)
All of this is implemented in a disorganized clutter of Bourne shell
'if' and 'elif' clauses, aided by three supporting shell functions,
'ishost
', 'iscmd
', and 'match
', which sees if the command
matches a wildcard. This results in clauses like:
[...] elif match 'about:*'; then exec mozurl "$cmd" elif [....]
The entire thing is somewhat messy, partly because that's shell scripting for you (and partly because I'm intermixing pattern matching with the results of calling functions; pure pattern matching would be easier to write and follow).
The autocompletion data I feed into dmenu on its standard input is carefully set up to make it convenient to autocomplete my most commonly used things, and in the right order where there are potentially clashing initial prefixes. The completion data includes a few commonly used programs (which I explicitly list at the front), the programs found in one of my dmenu commands directories, a data file with a curated list of completions of various sorts, and an automatically generated list of many of our local machines. If I find myself having to type out a full version of something a lot, I figure out how to add it to my autocompletions, either automatically or just by putting it in my data file.
(I've made a few customizations to dmenu that make its autocompletion behavior more useful to me; the source code is available as dmenu-cks.)
One of the things in my data file of completions is various URLs (as my wrapper script will accept them), such as 'about:addons' and 'golang.org/'. I list URLs without the scheme because that's far more convenient for autocompletion; this way I can invoke dmenu with a key tap, type 'go', and immediately golang.org/ is the leading completion. (I could just hit return to accept it, but usually I reflexively first hit tab to finish autocompleting it.)
(This sort of elaborates on a Fediverse reply I made, although it may not answer all of the questions people have.)
PS: Originally I had my wrapper script try to execute anything it didn't understand on the general principle that I couldn't do anything else. It's turned out to be much more useful to put up a notification that the wrapper script couldn't figure out what to do about my weird input, using notify-send. Among other things, this gives me definite feedback that I mis-typed something, instead of forcing me to infer it from silence (which could always have other causes).