Notes on Status Bar (for X11)

July 15, 2018

Categories: System Tags: Linux Productivity Software

TL;DR

I would recommend Polybar. But if you are looking for a small and beginner level project to learn *nix, I can recommend building something with lemonbar. You will gain some knowledge on shell scripting, IPC mechanisms and architecture of X11, in addition to gaining an appreciation for more general and commonly held wisdoms such as modular program design (or the so called Unix philosophy).

What is a Status Bar?

Okay it's nothing but the Task Bar we are all so familiar with from our Windows systems (so much so that it's now engraved in our collective psyche). But in *nix lands, you mostly find it being called the "panel", and seldom also "status bar". But well, a bar is really nothing but a persistent space on your screen for displaying useful information. Unless you have begun your journey of "ricing" the system, even on most Linux distros the default looks very meh not unlike Windows and it's hard to realise that you can customise them to actually suit your imagination.

Candidates

In the spectrum of what's worth talking about, on one end you have tint2, it's very featureful and works out of the box, though is kind of cumbersome to configure or extend. Then you have polybar and yabar, reasonably featureful with easy and abstracted interface for extension. Then I guess you have something like wibar or slstatus in the sense that they are customisable but rarely used outside particular WMs (awesome and dwm here). And xmobar bridges that group with the really interesting ones that I want to talk about: dzen2 and lemonbar because these come with no built-in features (outside rendering) at all, except they read user provided data through STDIN and merely displays it in (clickable) bar area. They do provide a markup to fine tune how and what's displayed, but it's entirely up to you to choose "what" to display. Dzen2 is the more heavier of the two, as it has more features (lemonbar doesn't even support xft fonts, you would have to find some fork for that). In the spirit of learning, let's use lemonbar.

Lemonbar

Okay, lemonbar is ridiculously easy to use. Just pipe your data in it.

echo "Hello World" | lemonbar

That's all! But wait, if you run it, you likely won't even see it. It basically ran and exited (got EOF from stream). Spend 30 seconds with the manual, and you can tell it to persist even after the stream ends (-p), let's also dock it to the bottom of the screen (-b) and tell it to not mess with existing WM configs (-d). While at it, let's also use its internal markup system to define colour and alignment of segments of texts:

echo "%{l} $(whoami) %{c} Hello World! %{r} $(date)" | lemonbar -B "#0c1014" -b -p -d

Note that lemonbar automatically calculates a display dimension, but you can also change its geometry further with the -g flag.

Okay, so it now persists. Except the data is frozen? What's the use of bar if it can't even show correct time? So….let's just turn it into a loop?

while true; do
  echo "%{l} $(whoami) %{c} Hello World! %{r} $(date)"
done | lemonbar -B "#0c1014" -b -p -d

And now it works!

….Except it's now burning your cpu cycles as fast as it can (verify in top). You want it to update, but not to make your computer single mindedly expend all its power to just do it imperceptibly fast! Tell it to calm down:

while true; do
  echo "%{l} $(whoami) %{c} Hello World! %{r} $(date)"
  sleep 1
done | lemonbar -B "#0c1014" -b -p -d

And that captures another important dimension. Because the question of "what to show", and "how to obtain what to show" is intertwined with the answer of the question "how often to show" it.

You will find that there are already many specific CLI tools to answer certain specific questions. Like, free for ram consumption, df for disk, acpi for battery, maybe uptime for cpuload. These are just ideas, and you already have powerful string processing tools in your disposal.

For some information, you have to repeatedly poll the source. But in general, it's far better to react to events. Now, you can't exactly talk to programs written in other languages from shell. For example, say MPD is our music player, and we want to change the song title in bar when the currently playing song changes. There exists library interfaces (libmpdclient) but they are not in shell. But the cool part is, someone most likely already used those libraries to create a CLI program for that. For example, for MPD you can use mpc (the exact command would be: mpc idleloop player) to listen to relevant events. Or bspc subscribe for WM events, if you use bspwm. Seach for packages that begin with x, and you will find a plethora of small tools to interact with aspects of X11.

That's not all. You don't really have to use shell. You can pretty much use any language you want, as long as you output to STDIN of lemonbar.

But, at this point you will certainly want to make your program modular, since each subtask can grow to become pretty complex. There are other arguments for breaking down the items. Maybe item A is simple but needs frequent update and item B is computationally expensive but rarely updates. If you lump them together, then your update interval would be dictated by that of item A, which is suboptimal. After you break them down, you would also want them to communicate and this is where IPC beyond normal piping comes into play. There is a gem of a feature I use a lot in my scripts: fifo or named pipes. Using inotify would certainly be a creative way to devise event based mechanism.

All I can say that I had fun setting up lemonbar. But note that the code abstractions you will use are quite general, which means they themselves can be further abstracted! This means there are many "wrapper" libraries that make this simple such as Captain. succade etc. to name a few. In fact Polybar started as a wrapper for lemonbar at first when it was called lemonbuddy. I would also mention i3status for easing the information collecting process.

Polybar

Eventually I moved onto Polybar, which has a cool interface that deals with all the complexities we have talked about till now. Making it look good is easy, it can read your colours from xrdb database and you can use the absolutely glorious pywal to automate further change. It has native support for bspwm and i3, but should work with any EWMH compatible WM (although that module is not really complete, e.g. urgent and occupied states are not implemented yet).

Speaking of it, did you know that X11 already has a system for window notification? It's through the urgency flag of WM_HINTS. I didn't know of this, but now I just use it often over notify-send because it's nice to know which terminal triggered the alarm since I have too many workspaces. Polybar is aware of this (in bspwm and i3 at least), so it works out.

On terminal, the bell actually sets the urgency. On urxvt, you can enable this by putting it in your .Xresources :

URxvt*urgentOnBell: true

And ring the bell with just:

echo -e '\a'

Put that after a command (make an alias or function), and you will be notified when the command is finished. You can do this with wmctrl, or xdotools as well I think. For wmctrl, this should do:

wmctrl -i -r $WINDOWID -b add,demands_attention

Anyway, I also decided to write something not totally trivial for Polybar (that hasn't already been done). I use taskwarrior (and timewarrior) for simple GTD (but mostly Org-mode which is another story). First I made taskwarrior output as simple as I want by creating a custom filter.

I added this to .taskrc :

search.case.sensitive=no
report.mysimple.labels=ID,description
report.mysimple.sort=urgency-
report.mysimple.filter=status:pending
report.mysimple.columns=id,description.desc

And wrote this control script (~/.config/polybar/taskwarrior) to be used from polybar IPC:

#!/usr/bin/env bash

tasklist ()
{
  task mysimple rc.color=off rc.verbose=nothing "[email protected]"
}

show ()
{
  task "$(cat current)" export | jq -r '.[0].description'
}

top_priority ()
{
  [ -f ./locked ] && exit 1
  tasklist limit=1 | awk '{ print $1 }' > current
  show
}

change_task ()
{
  [ -f ./locked ] && exit 1
  tasklist | rofi -dmenu -i | awk '{ print $1 }' | ifne sh -c "cat > current"
  show
}

finished ()
{
  task "$(cat current)" 'done'
  top_priority
  show
}

toggle_work ()
{
  if [ "$(task "$(cat current)" export | jq '.[0].start')" == "null" ];
  then
    task "$(cat current)" start
    touch ./locked
    echo "%{F#f00} $(show) %{F-}"
  else
    task "$(cat current)" stop
    rm ./locked
    show
  fi
}

twdir='/tmp/tw/'

[ -d  "$twdir" ] || mkdir "$twdir"

cd "$twdir" || exit 1

[ -f ./current ] || top_priority

case "$1" in
top_priority)
  top_priority
  ;;
finished)
  finished
  ;;
toggle_work)
  toggle_work
  ;;
change_task)
  change_task
  ;;
show)
  show
  ;;
*)
  top_priority
esac

And finally, here is the Polybar module:

[module/taskwarrior]
type = custom/ipc

hook-0 = ~/.config/polybar/taskwarrior finished
hook-1 = ~/.config/polybar/taskwarrior show
hook-2 = ~/.config/polybar/taskwarrior toggle_work
hook-3 = ~/.config/polybar/taskwarrior change_task
hook-4 = ~/.config/polybar/taskwarrior top_priority

initial = 5

click-middle = polybar-msg -p %pid% hook taskwarrior 1
double-click-right = polybar-msg -p %pid% hook taskwarrior 2
double-click-left = polybar-msg -p %pid% hook taskwarrior 3
click-left = polybar-msg -p %pid% hook taskwarrior 4
click-right = polybar-msg -p %pid% hook taskwarrior 5

And I can see the most important task I should be doing instead of procrastinating by prolonging this post any more! I can start/stop working on it (which triggers timewarrior), or mark it done. Or I can even switch to another task through a Rofi interface.