How to Lemonbar
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, indwm
, 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 thanbash
.