FreeBSD
February 6, 2020

Ricing your *nix desktop | Panel bars II

After fetching all the required info from the system now it's time to give some personality to the panel bar and some performance under the hood.

We want to pass the data using a pipe and not a file since we don't want our bar to affect our computer's performance.

Create a FIFO for the panel

—To be exact we are going to be using a named pipe (FIFO) which is a special file similar to a pipe except that it is accessed as part of the filesystem. It does nothing until some process reads and writes to it. It doesn't take any space on the hard disk, it doesn't use system memory and it can be opened by multiple processes for reading or writing.

In our bar script let's create one. Define a variable for our named pipe:

# ~/.scripts/status_panel
PANEL_FIFO=/tmp/panel-fifo

Now let's verify that we don't have any file named panel-fifo before starting our script and if we do, we are removing it and creating it from zero:

# ~/.scripts/status_panel
[ -e "$PANEL_FIFO" ] && rm "$PANEL_FIFO"
mkfifo "$PANEL_FIFO"

Once we have our named pipe created we can pass all our data-harvesting functions to it using redirection.

# ~/.scripts/status_panel
info_CPU > "$PANEL_FIFO" &
info_RAM > "$PANEL_FIFO" &
info_DriveSpace > "$PANEL_FIFO" &
info_TimeDate > "$PANEL_FIFO" &
info_NetworkStatus > "$PANEL_FIFO" &
info_Volume > "$PANEL_FIFO" &
info_Battery > "$PANEL_FIFO" &

Our named pipe needs to write the data somewhere. Let's encapsulate our final while loop into a function so we can get our panel-fifo's data through it:

# ~/.scripts/status_panel
panel_bar()
{
       while true; do
               BAR_INPUT="%{l} CPU: $(info_CPU) RAM: $(info_RAM) HDD: $(info_DriveSpace) %{c}$(info_TimeDate) %{r} N: $(info_NetworkStatus) V: $(info_Volume) B: $(info_Battery)"
               printf "%s\\n" "$BAR_INPUT"
       sleep 1
       done
}

Now we can get our code executed adding the following line at the bottom:

# ~/.scripts/status_panel
panel_bar < "$PANEL_FIFO" | lemonbar &

If you try to execute your script after saving it you should see it running as before.

$ .scripts/status_panel.sh

Maybe you notice that after killing the terminal emulator instance that executed the script, it's still running. When we create temporary files in a script they are cleaned when the script exits successfully. But if we interrupt the script (like killing a terminal emulator instance running it) it may happen that the temp file isn't cleaned.

We can fix it using a really cool shell utility named trap. It captures interruptions in the script and cleans things up when interruption signals are caught.

# ~/.scripts/status_panel
trap 'trap - TERM; kill 0' INT TERM QUIT EXIT

This way we're good to go with our panel info.

Custom colors and fonts

An important part of ricing is giving personal style to our work. Lemonbar can be filled with custom fonts and colors.

In terms of fonts, the standard Lemonbar version only supports bitmap fonts so if you want to use TTF fonts you need to get a modified port and build it. There's nothing wrong with bitmap fonts except from the scaling side. They render fast and they are pretty.

Before adding custom fonts to our script we need to get the system ready to read our bitmap fonts. In this example we're going to use Tamsyn from Scott Fial. Download the .tar.gz file from the link and extract it on your ~/.fonts directory.

After doing so we have to update the font cache and tell the X server bout our new font:

$ fc-cache -vf
$ xset +fp ~/.fonts
$ xset fp rehash

Chances are that after typing the xset command you're given an error instead of a successful operation. In this case let's make sure we have a font index in our directory.

$ cd .fonts
$ mkfontscale
$ mkfontdir

And re type the xset instructions again.

Bitmap fonts aren't declared the same way as TTF fonts. To make our life easier there's a tool named xfontsel that can help us getting the value for the desired font (you may need to install it).

In our script status_panel let's create a variable for our custom font:

# ~/.scripts/status_panel
PANEL_FONT_0="-misc-tamsyn-medium-r-normal-*-20-145-100-100-c-100-iso8859-1"

The long name has been taken from the xfontsel tool.

Another cool things in ricing are icons. There are both xft and bitmap icon fonts. Siji is a bitmap icon font that suits well into lemonbar.

Siji font's preview

There is a tricky part with icon fonts: you need to print an unicode glyph in order to get the icon visible. sh doesn't interpret raw unicode sequences so we have to use another shell like bash for this task.

Using xfd we can select siji's icons and look for the unicode that represents them.

$ xfd -fa wuncon\ siji

After knowing which icon unicodes we want, we have to pass them out through the script.

# this should print a bold square clock icon:
printf "\ue017"  

The same way we added the previous font to our status_panel script, let's add the icon one:

# ~/.scripts/status_panel
PANEL_FONT_1="-wuncon-siji-medium-r-normal-*-10-100-75-75-c-80-iso10646-1"

All the custom parts that we may add to our status panel are given to lemonbar at the execution time. Add the font at the last line we have like this:

# ~/.scripts/status_panel
panel_bar < "$PANEL_FIFO" | lemonbar -f $PANEL_FONT_0 -f $PANEL_FONT_1 &

Now you should see your custom font when launching the script again.

Summing up

Adding the instruction to launch the script at our bspwmrc file or our .xinit file will make it run since we start an X server. Further exploring is to add colors depending each state and icons using icon bitmap fonts. Check this repository by Tecate to look at a good bitmap fonts collection. Right now here's our final script:

# ~/.scripts/status_panel

#!/bin/sh
PANEL_FONT_0="-misc-tamsyn-medium-r-normal-*-20-145-100-100-c-100-iso8859-1"
PANEL_FIFO=/tmp/panel-fifo

trap 'trap - TERM; kill 0' INT TERM QUIT EXIT

[ -e "$PANEL_FIFO" ] && rm "$PANEL_FIFO"
mkfifo "$PANEL_FIFO"

info_Battery()
{
       STATE="$(sysctl hw.acpi.battery.state | awk '{ print $2 }')"
       CHARGE="$(sysctl hw.acpi.battery.life | awk '{ print $2 }')%"

       case $STATE in
               1)
                       OUTPUT="discharging $CHARGE"
                       ;;
               2)
                       OUTPUT="charging $CHARGE"
                       ;;
               7)
                       OUTPUT="no battery"
                       ;;
               *)
                       OUTPUT="ERR"
                       ;;
       esac

       printf "%s\\n" "$OUTPUT"
}

info_NetworkStatus()
{
       WIFI_INFO=$(ifconfig wlan0)
       WIFI_STATUS=$(printf "%s\\n" "$WIFI_INFO" | grep -w "status:" | awk '{ print $2 }')
       SSID=$(printf "%s\\n" "$WIFI_INFO" | grep -w "ssid" | awk '{ print $2 }')

       ETH_INFO=$(ifconfig em0)
       ETH_STATUS=$(printf "%s\\n" "$ETH_INFO" | grep -w "status:" | awk '{ print $2 }')

       if [ "$WIFI_STATUS" = "associated" -a "$ETH_STATUS" = "no" ]
       then
               printf "%s\\n" "${SSID}"
       elif [ "$WIFI_STATUS" = "associated" -a "$ETH_STATUS" = "active" ]
       then
               printf "%s\\n" "Wired"
       else
               printf "%s\\n" "Down"
       fi     
}

info_Volume()
{
       GETVOL="$(mixer | grep vol | awk '{ print $7 }' | grep -o '[^:]*')"
       printf "%s\\n" "${GETVOL}%"
}

info_TimeDate()
{
       TTIME=$(date +"%H:%M")
       TDATE=$(date +"%m-%d-%Y")
       
       printf "%s\\n" "$TTIME | $TDATE"
}

info_CPU()
{
       USEDCPU=$(top -n | grep -w "CPU" | awk '{ print $2+$4+$6+$8 }')
       printf "%s\\n" "${USEDCPU}%"
}

info_RAM()
{
       USEDRAM=$(top -n | grep -w "Mem" | awk '{ print $2+$4+$6+$8 }')
       TOTALRAM=$(dmesg | grep -E '^avail memory' | cut -d'(' -f2 | cut -d')' -f1 | awk '{ print $1 }')
       PRCNTUSED=$(awk -v u=$USEDRAM -v t=$TOTALRAM 'BEGIN{print 100 * u / t}' | awk -F. '{ print $1"."substr($2,1,2) }')

       printf "%s\\n" "${PRCNTUSED}%"
}


info_DriveSpace()
{
       AVAIL=$(df -H / | grep -w "default" | awk '{ print $4 }' | awk -F'[A-Z]' '{print $1}')
       TOTAL=$(df -H / | grep -w "default" | awk '{ print $2 }' | awk -F'[A-Z]' '{print $1}')
       
       printf "%s\\n" "${AVAIL}GB"
}

info_CPU > "$PANEL_FIFO" &
info_RAM > "$PANEL_FIFO" &
info_DriveSpace > "$PANEL_FIFO" &
info_TimeDate > "$PANEL_FIFO" &
info_NetworkStatus > "$PANEL_FIFO" &
info_Volume > "$PANEL_FIFO" &
info_Battery > "$PANEL_FIFO" &


panel_bar()
{
       while true; do
               BAR_INPUT="%{l} CPU: $(info_CPU) RAM: $(info_RAM) HDD: $(info_DriveSpace) %{c}$(info_TimeDate) %{r} N: $(info_NetworkStatus) V: $(info_Volume) B: $(info_Battery)"
               printf "%s\\n" "$BAR_INPUT"
       sleep 1
       done
}

panel_bar < "$PANEL_FIFO" | lemonbar -f $PANEL_FONT_0 -f $PANEL_FONT_1 &

wait

(: