Getting started with Rofi
Rofi is a Linux app that is a:
[..] window switcher, application launcher and dmenu replacement
I've been using Rofi for about a year but didn't really understand how to write my own scripts for it until recently.
A key to my lack of apprecaition for Rofi's feature set was that I didn't understand why it billed itself as a "dmenu replacement". What the heck is
dmenu is a fast and lightweight dynamic menu for X. It reads arbitrary text from stdin, and creates a menu with one item for each line. The user can then select an item, through the arrow keys or typing a part of the name, and the line is printed to stdout. (emphasis mine)
dmenu lets you create and present your own menu and then act once a menu item is selected by the user. One of my favorite applications of all time is Alfred, the infinitely customizable launcher utility for macOS. The best part about Alfred was Workflows which (like dmenu) allow you to create custom menus and actions as part of the main launcher interface. How do we create a menu though?
dmenu will display whatever comes in on
STDIN as the menu, e.g.
echo "foo\nbar" | dmenu" (if you have a Linux install available install
dmenu and give that command a try). Each new line in
STDIN becomes a new line in the
dmenu menu. The previous command creates a menu with two options:
bar. Once the user hits enter on a menu item the value of the selected item gets sent to
STDOUT. That's it.
dmenu reads in from
STDIN and prints out selections to
dmenu is super handy to add to workflows and scripts to give them a bit of UI. You can squeeze
dmenu anywhere you need feedback from the user, e.g.:
echo "Hello $(echo 'World\nPeople' | dmenu)". This prints a hello world message after the users chooses "World" or "People" from
Rofi + dmenu
dmenu capability plus lots of other things like selecting open windows, running ssh commands etc. To enable
dmenu mode in Rofi pass it as an option
-dmenu. Let's port our previous example code snippet to Rofi:
echo "foo\nbar" | rofi -dmenu. Easy enough! With the
dmenu option Rofi becomes just as useful as Alfred and makes me think Alfred should gain a command line option to pop open Alfred from within scripts like dmenu or Rofi.
My first script
The motivation to write this post came a couple weeks ago when I wrote my first real Rofi script. I use Pop_OS! as my Linux distribution of choice for work and the defaults are really good. However when I want to change my audio output (headphones or external speakers) I've grown annoyed by having to manually mouse up to the menubar and select the desired output. I set out to write a Rofi script to handle changing the audio output. On Linux you can control everything related to the audio system via the
pactl utility. The pieces of data I needed for my script were:
- A list of all available outputs to generate my Rofi menu
- A way to change to the selected output
pactl list sinks displays all active "sinks" or available outputs ("Sinks" is the Linux audio system term for outputs). We can customize this a bit further wih
pactl list short sinks which will cram relevant sink information into one line each instead of multiple lines per sink. This makes parsing the output much easier.
Now that I had a list of available speakers/headphones on the system I needed to change to that output. This turned out to be a two step process because of how the audio system works:
pactl move-sink-input INPUT SINKmoves anything currently playing to the specified speaker/headphone.
pactl set-default-sink SINKsets the specified speaker/headphones as the default output going forward, e.g. launching a video call or playing a video.
To accomplish #1 I needed to get the currently playing input which can be done via:
pactl list sink-inputs. Again we can specify
short to get the relevant information on a single line:
pactl list short sink-inputs.
The finished script looks like this:
#!/bin/bash source="$(pactl list short sinks | cut -f 2 | rofi -dpi 1 -dmenu -p "Change audio:")"; inputs="$(pactl list sink-inputs short | cut -f 1)"; for input in $inputs; do pactl move-sink-input "$input" "$source"; done pactl set-default-sink "$source";
Upon invoking the script, Rofi presents me with a menu that contains all the available outputs. Once I select one the audio changes instantly.
I find Rofi so useful that I have a system-wide hotkey that invokes Rofi as a general launcher like this:
rofi -combi-modi run,window,drun -show combi -modi combi -dpi 1. The flags I'm using are documented under "Combi settings" in the Rofi manpage.
Rofi transforms otherwise vanilla shell scripts into poweful transient workflows that allow me to control aspects of my machine without leaving whatever I'm currently working on. It's become an essential part of my toolbox.