In the huge world of ricing, there are plenty of options when it comes to bars and panels, particularly for unix-like systems: polybar, dzen, xfce-panel, tint2, etc. In particular, lemonbar is one of the most minimal ones, since it only requires text to be piped into it in order to display a bar. Thus getting useful information, like CPU usage or the current time, is left at the hands of the user - scripting time!

However, due to lack of proper documentation on how to actually use the bar, I thought making an article describing my workflow and what I've learned from reading others’ messy scripts, and exploring two different approaches.

Basics

The simplest posible setting can be simply achieved by:

$ echo "hello world" | lemonbar

However, you may notice that nothing appears - this happens because, by default, lemonbar quits once you stop piping text into it. Although you could use the flag -p to make it permanent and not exit once the input stops, this wouldn't be very useful if you wanted to update, say, the date or the network status, battery info, etc. Thus, a better approach involves constantly piping into the bar:

while true
do
    echo $(date)
    sleep 1
done | lemonbar

Now you get a small bar that's updating the time once per second, neat! From here on you can start tinkering with it, giving it different flags to set colors, fonts, position, width, etc. You can find lots of info with man lemonbar, and some examples in the ArchWiki.

A simple approach

I'll show some very simple functions to get some basic info that I'd like to show on my bar - in particular, I'm interested in the current date, which desktop I'm in, current network, and battery usage. For such, I'll only need the following programs:

  • date
  • xdotool (to get data about the current WM)
  • iwgetid (part of wireless_tools in Void Linux)

Now, the functions:

function get_date() {
    date=`date +"%A %d %B"`
    time=`date +"%R"`
    echo ${date}, ${time}
}

function get_bat() {
    batFull=""
    echo "$batFull $(< /sys/class/power_supply/BAT0/capacity)%"
}

function get_net() {
    name=$(iwgetid -r)
    if [ $? -eq 0 ]; then
        echo "$name"
    else
        echo "%{F$color01}睊%{F-}"
    fi
}

function get_tag() {
    total=$(xdotool get_num_desktops)
    current=$(xdotool get_desktop)
    desktops=""

    for i in $(seq 0 $(($total - 1))); do
        if [ $i == $current ]; then
            desktops+=" ■"
        else
            desktops+=" □"
        fi
    done

    echo $desktops
}

Now, you have to call each and pipe the results to lemonbar:

# Main loop that constantly pipes into lemonbar
while true
do
    printf "%s %s %s%s\n" \
            "%{l} $(get_tag)" \
            "%{c} $(get_date)" \
            "%{r} $(get_net)  $(get_bat) " \
            "%{B- F-}" # Cleanup to prevent colors from mixing up
    sleep 0.3
done | \

lemonbar -d \
         -f "$font" \
         -g ${width}x${height}+${x}+${y} \
         -n $name \
         -F $color07

That's pretty much it! However, you may notice that if you want to do some things a bit more complex, like checking for updates or an action that takes a lot more then 0.3s to execute, you'd have to increase the update time - this would make switching to another desktop take a long time to update, for instance. It also brings the question of resource usage: is it really necessary to check the date so often? What if you only wanted to update it once a minute?

Now I'll show two variants of a more powerful way of tackling this problem.

A FIFO approach

The core idea is to spawn many little processes, one per function, and have them constantly write into a file (or files), and in the main loop, only read the values that were written. This allow each process to update the file whenever it finds it fitting.

A single FIFO

You can create a single file, constantly write into it from all the processes, and only read it once something is written. This has the big advantage that the main loop will only run whenever the FIFO is writen, but also the downside that you still need to verify exactly what you're reading on each ocasion. To aid this, you could add a single letter at the beginning of the output of each function.

For the previously mentioned functions, you'd have to refactor them like this:

BAR_FIFO=/tmp/bar-fifo

# Delete in case it already exists
rm $BAR_FIFO

mkfifo $BAR_FIFO

## Write into it
# Date
while true; do
    date=`date +"%A %d %B"`
    time=`date +"%R"`
    echo 'D' ${date}, ${time}
    sleep 59
done > $BAR_FIFO &

# Battery
while true; do
    echo 'B' "$(< /sys/class/power_supply/BAT0/capacity)%"
    sleep 30
done > $BAR_FIFO &

# Network
while true; do
    name=$(iwgetid -r)
    if [ $? -eq 0 ]; then
        echo 'N' "$name"
    else
        echo 'N' "%{F$color01}睊%{F-}"
    fi
    sleep 10
done > $BAR_FIFO &

# Desktops
while true; do
    total=$(xdotool get_num_desktops)
    current=$(xdotool get_desktop)
    desktops=""

    for i in $(seq 0 $(($total - 1))); do
        if [ $i == $current ]; then
            desktops+=" ■"
        else
            desktops+=" □"
        fi
    done

    echo 'W' $desktops
    sleep 0.1
done > $BAR_FIFO &

So far so simple, now to the tricky part, which involves reading from the FIFO on the main bar loop:

# Constantly read it, processing each line
while read -r line < $BAR_FIFO; do
    case $line in
        # Date
        D*)
            curr_date="${line#?}";;

        # Network
        N*)
            net="${line#?}";;

        # Desktops
        W*)
            wm="${line#?}";;

        # Battery
        B*)
            bat="${line#?}";;
        esac
        printf "%s\n" "%{l}${wm}%{c}${curr_date}%{r}${net}${bat}"
done | lemonbar

Multiple FIFOs

Another option is to have each function write into a single separate file, and from the main loop, only read the contents of each. Although it involves making more files, it removes the need to have to identify which process is currently writing to the FIFO.

Modularity

Since many of these scripts end up being quite long, I suggest splitting the it into a few smaller ones:

.
├── bar.sh
├── bar_config.sh
└── bar_functions.sh

This allows you to keep different settings on different files while keeping the same functions in one place, in case you like experimenting and want to preserve older ones. For example, in bar_config.sh I have the following:

#!/usr/bin/env bash

# Bar
name="bar"
x=18
y=6
height=20
read width foo <<< $(xdotool getdisplaygeometry) 
width=$(($width - 2 * $x))

# Fonts
font1="FuraMono Nerd Font Mono:pixelsize=13"
font2="FuraMono Nerd Font Mono:pixelsize=11"

At the beginning of my main script, I just source the other files

#!/usr/bin/env bash
dir=$(dirname $0)

source $dir/bar_functions.sh
source $dir/bar_config.sh

The first line is just a little helper to allow me to run the script and find the other files from any directory. You can find my whole setup in my dotfiles.

Tips

  • Some window managers may not let you dock the bar, mostly when they are not EWMH compliant. Use the flag -d to force docking in these cases - I've found that, in dwm, this is needed even with the EWMH patches.
  • If your font constains icons, you'll likely need the lemonbar-xft fork for such to work properly.
  • Although you can add colors on your own, I suggest sourcing them from the current base16-shell theme in case you've got one.
  • If you're very serious about performance or minimalism, you can write most of the functions and the main loop in a smaller shell like dash, which will prove a lot faster than bash.