Lemonbar

about | blog | config | notes | github

Lemonbar (XFT Fork) is a lightweight bar based on XCB. Its pretty neat tool that you just pipe output into to display the bar of your design. Unfortunately, it seems like the repo is archived so it is unmaintained. It will likely break at some point so I should look for alternatives.

1. Configuration

1.1. Panel Refresh

This snippet simply kill the existing lemonbar process and restarts it. This is necessary when we refresh the window manager configuration so let's add this to the xinit user-level scripts.

pkill -x lemonbar
$HOME/.config/lemonbar/lemonbar &

1.2. Panel Launcher

Load config and logger.

. $HOME/.config/lemonbar/config.default
LOGGER=$(get_logger lemonbar)

Prevent multiple panels from starting up (really bad).

if [ $(pgrep -cx lemonbar) -gt 1 ]; then
    $LOGGER -s "The panel is already running"
    exit 1
fi
$LOGGER "Starting lemonbar init script"

Using trap, we can change the behaviour of our shell script.

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

Now it's time to make each of the panel FIFOs and start the modules.

$HOME/.config/lemonbar/utils/make_fifos

Tail command to ensure that fifos never close.

TAIL="tail --lines=+1 --follow"

Init modules for the top fifo.

$LOGGER "Setting up top fifo modules"
$HOME/.config/lemonbar/modules/date         > $PANEL_TOP_FIFO &
$HOME/.config/lemonbar/modules/network      > $PANEL_TOP_FIFO &
$HOME/.config/lemonbar/modules/time         > $PANEL_TOP_FIFO &
$HOME/.config/lemonbar/modules/whoami       > $PANEL_TOP_FIFO &
$HOME/.config/lemonbar/modules/battery      > $PANEL_TOP_FIFO &
$TAIL $EVENT_BKL_FIFO \
    | $HOME/.config/lemonbar/modules/backlight > $PANEL_TOP_FIFO &

Init modules to the bot fifo. I've disabled the xwin module as I don't really use it.

$LOGGER "Setting up bot fifo modules"
$HOME/.config/lemonbar/modules/cpu          > $PANEL_BOT_FIFO &
$HOME/.config/lemonbar/modules/filesystems  > $PANEL_BOT_FIFO &
$HOME/.config/lemonbar/modules/memory       > $PANEL_BOT_FIFO &
$HOME/.config/lemonbar/modules/spotify      > $PANEL_BOT_FIFO &
$HOME/.config/lemonbar/modules/volume_const > $PANEL_BOT_FIFO &
# $HOME/etc/lemonbar/modules/windowinfo   < $EVENT_WIN_FIFO > $PANEL_BOT_FIFO &
$TAIL $EVENT_WSP_FIFO \
    | $HOME/.config/lemonbar/modules/workspaces   > $PANEL_BOT_FIFO &
$TAIL $EVENT_VOL_FIFO \
    | $HOME/.config/lemonbar/modules/volume_event > $PANEL_BOT_FIFO &

Setup the argument string to pass send to lemonbar. Uncomment fonts as needed.

Other fonts:

  • -f dina:size=12
  • -f xos4Terminus:style=Bold:size=12
  • -f Iosevka\ Term:size=12
  • -f PowerlineSymbols:style=Medium:size=12
  • -f -wuncon-siji-medium-r-normal--10-100-75-75-c-80-iso10646-1
OPTS="-g x20
-F ${XBACKGROUND}
-B ${XBACKGROUND}
-f Iosevka\ Term:size=12
-f FontAwesome5Free:style=Regular:size=14
-f FontAwesome5Free:style=Solid:size=14
-f FontAwesome5Brands:style=Regular:size=14
-u 3"

$LOGGER "Lemonbar defined with the following opts : $OPTS"

Actually launch our panel loops (while reading from the fifos) and pipe them into lemonbar. We should also pipe the output from lemonbar to a shell process in-case there are modules that want to run specific commands.

$LOGGER "Initializing lemonbar using top fifo"
$HOME/.config/lemonbar/utils/loop TOP < $PANEL_TOP_FIFO | lemonbar $OPTS | sh &

$LOGGER "Initializing lemonbar using bot fifo"
$HOME/.config/lemonbar/utils/loop BOT < $PANEL_BOT_FIFO | lemonbar $OPTS -b | sh &

$LOGGER "Lemonbar finished."
wait

1.3. Default Configuration

The default configuration is our source for all variables and functions that lemonbar and its modules will need to use. So we also source our utils files and other lib files here.

. $HOME/org/config/lib/shell/logger
. $HOME/org/config/lib/shell/xrdb_colors
. $HOME/.config/lemonbar/utils/wrappers
. $HOME/.config/lemonbar/utils/multi_monitor_support
. $HOME/.config/lemonbar/utils/where_fifos

Source local system config in case there are system specific settings.

. $HOME/.config/lemonbar/config

Choose the output wrapper.

MSG_WRAPPER=icon_wrapper

Anchors are an important concept with regards to FIFOs. They inform the main loop which module wrote which string.

ANCHOR_BACKLIGHT='L'
ANCHOR_BATTERY='B'
ANCHOR_CPU='C'
ANCHOR_DATE='D'
ANCHOR_FILESYSTEMS='F'
ANCHOR_MEMORY='M'
ANCHOR_NETWORK='N'
ANCHOR_SWAP='S'
ANCHOR_SPOTIFY='J'
ANCHOR_TIME='T'
ANCHOR_WHOAMI='U'
ANCHOR_WININFO='X'
ANCHOR_WORKSPACES='W'
ANCHOR_VOLUME='V'

Setup various refresh timings for various modules

REFRESH_BATTERY=2
REFRESH_CPU=3
REFRESH_DATE=86400
REFRESH_FILESYSTEMS=10
REFRESH_MEMORY=3
REFRESH_NETWORK=2
REFRESH_SWAP=3
REFRESH_TIME=1
REFRESH_VOLUME=2

I played around with fonts in lemonbar, but honestly I'm not really a fan. If I want to enable it at any point, just need to set LEMONBAR_ENABLE_ICONS in the local config to be a non-empty string. But this aren't actually used EVER in the modules anymore as I'm using powerline icons now. I should probably delete this and remove this segment of code. The unicode characters are taken form Font Awesome.

if [ -n "$LEMONBAR_ENABLE_ICONS" ]; then
    ICON_BACKLIGHT=$(env printf "\uf042 ")
    ICON_BATTERY_AC=$(env printf "\uf1e6 ")
    ICON_BATTERY_EMPTY=$(env printf "\uf244 ")
    ICON_BATTERY_NEAR_EMPTY=$(env printf "\uf243 ")
    ICON_BATTERY_HALF=$(env printf "\uf242 ")
    ICON_BATTERY_NEAR_FULL=$(env printf "\uf241 ")
    ICON_BATTERY_FULL=$(env printf "\uf240 ")
    ICON_BATTYER_CHARGING=$ICON_BATTERY_AC
    ICON_CPU=$(env printf "\uf24d ")
    ICON_DATE=$(env printf "\uf073 ")
    ICON_FILESYSTEMS=$(env printf "\uf0a0 ")
    ICON_MEMORY=$(env printf "\uf538 ")
    ICON_MUSIC=$(env printf "\uf001 ")
    ICON_NETWORK_ETHERNET=$(env printf "\uf796 ")
    ICON_NETWORK_OFFLINE=$(env printf "\uef6ff ")
    ICON_NETWORK_WIFI=$(env printf "\uf1eb ")
    ICON_SWAP=$(env printf "\uf1c0 ")
    ICON_TIME=$(env printf "\uf017 ")
    ICON_WHOAMI=$(env printf "\uf007 ")
    ICON_LINUX=$(env printf "\uf120 ")
    ICON_WORKSPACES=$(env printf "\uf108 ")
    ICON_VOLUME=$(env printf "\uf025 ")
else
    # Some icons are better left as blank so not all are defined
    ICON_BACKLIGHT="bL: "
    ICON_BATTERY_EMPTY="bat: "
    ICON_BATTERY_NEAR_EMPTY=$ICON_BATTERY_EMPTY
    ICON_BATTERY_HALF=$ICON_BATTERY_EMPTY
    ICON_BATTERY_NEAR_FULL=$ICON_BATTERY_EMPTY
    ICON_BATTERY_FULL=$ICON_BATTERY_EMPTY
    ICON_BATTERY_CHARGING="a/c: "
    ICON_CPU="cpu: "
    ICON_MEMORY="mem: "
    ICON_SWAP="swap: "
    ICON_VOLUME="vol: "
fi

There define the powerline icons (to use these, the module strings have to specially confiugred to use them).

PL_R=$(env printf "\ue0b0")
PL_Rb=$(env printf "\ue0b1")
PL_L=$(env printf "\ue0b2")
PL_Lb=$(env printf "\ue0b3")

Setup network interfaces and filesystem paths. These are dummy values and should be overriden with the locally generated config.

WIFI_INTERFACE=${WIFI_INTERFACE:-"wlan0"}
ETHERNET_INTERFACE=${ETHERNET_INTERFACE:-"eth0"}
FILESYSTEMS=${FILESYSTEMS:-"/dev/sda4"}

1.4. Utilities

1.4.1. Main Loop

Source the main config, setup the loggers and figure out which mode this loop belows to (TOP or BOTTOM).

. $HOME/.config/lemonbar/config.default
MODE="$1"
LOGGER=$(get_logger "lemonbar.loop.$MODE")

$LOGGER "Configuring the $MODE loop"

This setup below is pretty confusing but it was done because I have two different monitor setups. A triple monitor setup and standalone laptop mode. Depending on the mode we were in, the panel output should be split over three monitors or all should appear on the same one. The variables below simply help in using the same looping function instead of rewriting a different one for each monitor setup. Refer to the lemonbar documentation to understand how monitor specification works.

$LOGGER "Checking for multi monitor support"
if [ -f $TRIPLE_MONITOR_TOGGLE ]; then

    MONITOR_1="%{S0}"
    MONITOR_2="%{S1}"
    MONITOR_3="%{S2}"

    M1L="%{l}" M1C="%{c}" M1R="%{r}"
    M2L="%{l}" M2C="%{c}" M2R="%{r}"
    M3L="%{l}" M3C="%{c}" M3R="%{r}"

else

    MONITOR_1="%{S0}%{l}"
    MONITOR_2="%{S0}%{c}"
    MONITOR_3="%{S0}%{r}"

    M1L="" M1C="" M1R=""
    M2L="" M2C="" M2R=""
    M3L="" M3C="" M3R=""

fi

Below is the main loop for lemonbar. It uses the anchors we defined in the config to capture each modules output into module specific variables. We can then print it out to the top and bottom FIFOs.

$LOGGER "Initializing loop to construct lemonbar output"
while read -r line; do
    # echo "Current line: $line" >&2
    case $line in
        $ANCHOR_BACKLIGHT*)     bkl="${line#?}" ;;
        $ANCHOR_BATTERY*)       bat="${line#?}" ;;
        $ANCHOR_CPU*)           cpu="${line#?}" ;;
        $ANCHOR_DATE*)          cal="${line#?}" ;;
        $ANCHOR_FILESYSTEMS*)   fsf="${line#?}" ;;
        $ANCHOR_MEMORY*)        mem="${line#?}" ;;
        $ANCHOR_NETWORK*)       net="${line#?}" ;;
        $ANCHOR_SPOTIFY*)       mus="${line#?}" ;;
        $ANCHOR_TIME*)          clk="${line#?}" ;;
        $ANCHOR_WHOAMI*)        usr="${line#?}" ;;
        $ANCHOR_WININFO*)       win="${line#?}" ;;
        $ANCHOR_WORKSPACES*)    wsp="${line#?}" ;;
        $ANCHOR_VOLUME*)        vol="${line#?}" ;;
    esac

    # Print the contents of our modules based on the mode
    if [ "$MODE" = "TOP" ]; then
        m1="${MONITOR_1}${M1L}${M1C}$usr${M1R}"
        m2="${MONITOR_2}${M2L}${M2C}$cal$clk${M2R}"
        m3="${MONITOR_3}${M3L}$bkl${M3C}$net${M3R}$bat"
        printf "%s\n" "$m1$m2$m3"

    elif [ "$MODE" = "BOT" ]; then
        m1="${MONITOR_1}${M1L}$vol$mus${M1C}${M1R}"
        m2="${MONITOR_2}${M2L}${M2C}$win$wsp${M2R}"
        m3="${MONITOR_3}${M3L}$fsf$mem${M3C}${M3R}$cpu"
        printf "%s\n" "$m1$m2$m3"

    else
        printf "Panel loop given incorrect mode!\n" >&2
    fi
done

1.4.2. Make FIFOs

We use a seperate script to make the FIFOs so that other processes can start buffering without having to wait for lemonbar to finish starting. Therefore, this needs to be called before spawning lemonbar.

Like with all other lemonbar components, setup the logger and get the necessary variables

. $HOME/org/config/lib/shell/logger
. $HOME/.config/lemonbar/utils/where_fifos
LOGGER=$(get_logger lemonbar.make_fifos)

Initialize the actualy panel FIFOs. This is what lemonbar will actually end up reading after all the module output is formatted properly.

$LOGGER "Initializing panel fifo files"
[ -e "$PANEL_TOP_FIFO" ] && rm $PANEL_TOP_FIFO
[ -e "$PANEL_BOT_FIFO" ] && rm $PANEL_BOT_FIFO
mkfifo $PANEL_TOP_FIFO
mkfifo $PANEL_BOT_FIFO

In order to make lemonbar as computationally effecient as possible, we also make use of FIFOs that are used in conjunction with event hooks from external sources. This enables us to update specific modules without the use of the sleep command. Also note to self, there isn't a need to make the $EVENT_WIN_FIFO as the module that will use is disabled…

$LOGGER "Initializing event fifos"
[ -e "$EVENT_BKL_FIFO" ] && rm $EVENT_BKL_FIFO
[ -e "$EVENT_VOL_FIFO" ] && rm $EVENT_VOL_FIFO
[ -e "$EVENT_WSP_FIFO" ] && rm $EVENT_WSP_FIFO
[ -e "$EVENT_WIN_FIFO" ] && rm $EVENT_WIN_FIFO
mkfifo $EVENT_BKL_FIFO
mkfifo $EVENT_VOL_FIFO
mkfifo $EVENT_WSP_FIFO
mkfifo $EVENT_WIN_FIFO

Finally, we need to initialize these FIFOs. I'm pretty confident this has to happen because I was dealing with some issues where the FIFOs would close and lemonbar wasn't able to read their output anymore.

$LOGGER "Starting init writers for event fifos"
echo "backlight fifo init" > $EVENT_BKL_FIFO &
echo "workspace fifo init" > $EVENT_WSP_FIFO &
# echo "window fifo init"    > $EVENT_WIN_FIFO &
echo "volume fifo init"    > $EVENT_VOL_FIFO &

1.4.3. Trigger FIFOs

We use a sepeate script to send signals to the fifos that an event has occured since we don't want oother processes to really know the locations of these fifos in case they change. It also just serves to create a simpler interface lol.

We don't need the config here but we do need to source the locations.

. $HOME/.config/lemonbar/utils/where_fifos

Basically, depending the argument, we send a signal to a specific FIFO. Here are the accepted signals (bkl, wsp, win, vol).

TRIGGER_FIFO=$1

if [ $TRIGGER_FIFO = "bkl" ]; then
    echo "." > $EVENT_BKL_FIFO
elif [ $TRIGGER_FIFO = "wsp" ]; then
    echo "." > $EVENT_WSP_FIFO
elif [ $TRIGGER_FIFO = "win" ]; then
    echo "." > $EVENT_WSP_FIFO
elif [ $TRIGGER_FIFO = "vol" ]; then
    echo "." > $EVENT_VOL_FIFO
fi

1.4.4. Where FIFOs

Not much to be said here. This is just a seperate config file that keeps track of where we make the FIFOs in case I decide to change them.

PANEL_TOP_FIFO=/tmp/.lemonbar.panel_top.fifo
PANEL_BOT_FIFO=/tmp/.lemonbar.panel_bot.fifo

EVENT_BKL_FIFO=/tmp/.lemonbar.backlight_event.fifo
EVENT_VOL_FIFO=/tmp/.lemonbar.volume_event.fifo
EVENT_WSP_FIFO=/tmp/.lemonbar.workspace_event.fifo
EVENT_WIN_FIFO=/tmp/.lemonbar.wininfo_event.fifo

1.4.5. Output Wrappers

Wrappers are basically used by the modules so that we don't have to rewrite output code code for each module. Ironically I ended up doing that anyway since I decide to setup a powerline look for them. In any case, there are still here in case I want to use them in the future. And maybe, it might be possible to setup a more complicated wrapper that supports powerline icons.

The minimal wrapper just displays text in square brackets for each module and its also colored as well.

legacy_wrapper() {
    icon=$1 && shift && color=$1 && shift && msg=$@
    printf "[$icon %{F$color}$msg%{F-}]"
}

The icon wrapper on the other hand is meant for displaying the module with a font icon (this is configured through the global config file) and the default icons are the SIJI icon pack. This expects that a unicode icon is passed as the first argument (like siji).

icon_wrapper() {
    icon=$1 && shift && color=$1 && shift && msg=$@
    printf " $icon %%{F$color}$msg%%{F-} "
}

The debug wrapper is primarily used when debugging a module since all the formatting can sometimes make it hard to tell what the panel is really outputting.

debug_wrapper() {
    icon=$1 && shift && color=$1 && shift && msg=$@
    echo -ne "[DEBUG $icon $color $msg]"
}

1.4.6. Multi-Monitor Support

This might be pointless to keep as a seperate script but as (not anymore) was another script that needed the location of where to trigger the monitor toggle (whether I am in a multi monitor environment or not), this was necessary. Keep it setup for backwards compatibility.

TRIPLE_MONITOR_TOGGLE=$HOME/.config/lemonbar/enable_triple_monitor

1.5. Modules

1.5.1. Backlight

. $HOME/.config/lemonbar/config.default
LOGGER=$(get_logger lemonbar.backlight)

$LOGGER "Initializing backlight module"

while read -r backlight_event; do
    msg="$(xbacklight -get | sed -e 's/\.[0-9]*//g')"
    if [ -z "$msg" ]; then
        echo "$ANCHOR_BACKLIGHT%{F$BLACK}${PL_L}%{F-}"
    else
        msg="$msg%"
        msg="%{B$BLACK}%{F$WHITE} ${ICON_BACKLIGHT}$msg %{F-}%{B-}"
        msg="%{F$BLACK}${PL_L}%{F-}$msg"
        msg="$msg%{B$BLACK}%{F$GREEN}${PL_Lb}%{F-}%{B-}"
        echo "$ANCHOR_BACKLIGHT$msg"
    fi
done

1.5.2. Battery

. $HOME/.config/lemonbar/config.default
LOGGER=$(get_logger lemonbar.battery)

$LOGGER "Initializing battery module"

# TODO: Dynamically set this based on status
bg_color=$GREEN
fg_color=$XBACKGROUND
oscillator=0
refresh_rate=$REFRESH_BATTERY

while true; do
    batloc="/sys/class/power_supply/"
    batlist=$(ls $batloc | grep "BAT")
    out=""
    icon=""

    if [ -n "${batlist}" ]; then
        for bat in $batlist; do
            cap="$(cat ${batloc}${bat}/capacity)"
            stat="$(cat ${batloc}${bat}/status)"
            batid=$(echo $bat | tr -d 'BAT')

            # Uncomment this if I use a laptop with more than 1 battery again
            #out="${out} ${batid}:${cap}%"
            out="${out}${cap}%"

            if [ $cap -lt 15 ] && [ $stat = "Discharging" ]; then
                refresh_rate="0.2"
                icon=$ICON_BATTERY_EMPTY
                if [ $oscillator -eq 0 ]; then
                    bg_color=$RED
                    fg_color=$XBACKGROUND
                    oscillator=1
                else
                    fg_color=$RED
                    bg_color=$XBACKGROUND
                    oscillator=0
                fi
            else
                bg_color=$GREEN
                fg_color=$XBACKGROUND
                oscillator=0
                refresh_rate=$REFRESH_BATTERY
                icon=$ICON_BATTERY_FULL
            fi

        done
    else
        icon=$ICON_BATTERY_AC
        out="${out}A/C"
    fi

    msg="%{B$bg_color}%{F$fg_color} ${icon}$out %{F-}%{B-}"
    msg="%{F$bg_color}%{B$BLACK}${PL_L}%{B-}%{F-}$msg"

    echo "$ANCHOR_BATTERY$msg"
    sleep $refresh_rate
done

1.5.3. CPU

. $HOME/.config/lemonbar/config.default
LOGGER=$(get_logger lemonbar.cpu)

$LOGGER "Initializing cpu module"

while true; do
    msg=$(cat /proc/loadavg | awk '{print $1}')

    # TODO: Dynamically set this based on cpu load
    color=$MAGENTA

    msg="%{B$color} ${ICON_CPU}$msg %{B-}"
    msg="%{F$color}%{B$BLACK}${PL_L}%{B-}%{F-}$msg"

    echo "$ANCHOR_CPU$msg"
    sleep $REFRESH_CPU
done

1.5.4. Date

. $HOME/.config/lemonbar/config.default
LOGGER=$(get_logger lemonbar.date)

$LOGGER "Initializing date module"

# Set the refresh time to how many seconds till midnight
REFRESH_DATE=$(($(date -d 23:59:59 +%s) - $(date +%s) + 1))

while true; do
    #msg="%{F$GREEN}$(date +'%A, %d %B %Y')%{F-}"
    msg="%{B$BLACK}%{F$CYAN} ${ICON_DATE}$(date +'%a %Y/%m/%d')"
    msg="%{F$BLACK}${PL_L}%{F-}$msg "
    echo "$ANCHOR_DATE$msg"
    sleep $REFRESH_DATE
    REFRESH_DATE=86400 # Update the seconds to total seconds in a day
done

1.5.5. Filesystems

. $HOME/.config/lemonbar/config.default
LOGGER=$(get_logger lemonbar.filesystems)

$LOGGER "Initializing filesystems module"

while true; do

    msg=""
    for FS in $FILESYSTEMS; do
        fs_info=$(df | grep $FS)
        if [ -n "$fs_info" ]; then
            fs_path=$(echo $fs_info | awk '{print $6}' | sed -e 's|/home/zamlz|~|g')
            fs_used=$(echo $fs_info | awk '{print $5}')
            msg="$msg $fs_path:$fs_used"
        fi
    done

    msg="%{B$BLACK}%{F$WHITE} ${ICON_FILESYSTEMS}$msg %{F-}%{B-}"
    msg="%{F$BLACK}${PL_L}%{F-}$msg"

    echo "$ANCHOR_FILESYSTEMS$msg"
    sleep $REFRESH_FILESYSTEMS
done

1.5.6. Memory

. $HOME/.config/lemonbar/config.default
LOGGER=$(get_logger lemonbar.memory)

$LOGGER "Initializing memory module"

while true; do
    memory=$(free -h | grep 'Mem:')
    memory_total=$(echo $memory | awk {'print $2'} | tr -d ' ')
    memory_used=$(echo $memory | awk {'print $3'} | tr -d ' ')
    mem_msg="$memory_used/$memory_total"

    # TODO: Set this dynamically based on used mem
    mem_color=$MAGENTA

    swap=$(free -h | grep 'Swap:')
    swap_total=$(echo $swap | awk {'print $2'} | tr -d ' ')
    swap_used=$(echo $swap | awk {'print $3'} | tr -d ' ')
    swap_msg="$swap_used/$swap_total"

    # TODO: Set this dynamically based on used mem
    swap_color=$MAGENTA

    msg="%{B$BLACK}%{F$MAGENTA}${PL_Lb}%{F-}"
    msg="$msg%{F$mem_color} ${ICON_MEMORY}$mem_msg %{F-}"
    msg="$msg%{F$MAGENTA}${PL_Lb}%{F-}"
    msg="$msg%{F$swap_color} ${ICON_SWAP}$swap_msg %{F-}%{B-}"

    echo "$ANCHOR_MEMORY$msg"
    sleep $REFRESH_MEMORY
done

1.5.7. Network

. $HOME/.config/lemonbar/config.default
LOGGER=$(get_logger lemonbar.network)

$LOGGER "Initializing network module"

# State file for identifying if lemonbar displays ip address or not
ip_toggle="/tmp/.ip_toggle"

while true; do

    wifi_status=$(ip -br addr | grep $WIFI_INTERFACE)
    ethernet_status=$(ip -br addr | grep $ETHERNET_INTERFACE)

    interface=""
    primary_msg=""
    icon=""

    # Check if wifi is online
    if [ "$(echo $wifi_status | awk '{print $2}')" = "UP" ]; then

        ssid=$(iw $WIFI_INTERFACE link \
            | grep 'SSID:' \
            | sed -E "s/.*SSID:(.*)/\\1/")

        signal=$(iw $WIFI_INTERFACE link \
            | grep 'signal' \
            | awk '{print $2}')

        interface=$WIFI_INTERFACE
        primary_msg="$ssid ($signal dBm)"
        ip_addr=$(echo $wifi_status | awk '{print $3}')
        icon=$ICON_NETWORK_WIFI

    # Check if ethernet is online
    elif [ "$(echo $ethernet_status | awk '{print $2}')" = "UP" ]; then
        interface=$ETHERNET_INTERFACE
        primary_msg="Ethernet"
        ip_addr=$(echo $ethernet_status | awk '{print $3}')
        icon=$ICON_NETWORK_ETHERNET

    # State we are offline
    else
        interface="localhost"
        primary_msg="OFFLINE"
        ip_addr="127.0.0.1/8"
        icon=$ICON_NETWORK_OFFLINE
    fi

    # If the ip toggle file exists, print the ip address
    if [ -f "$ip_toggle" ]; then
        msg="%{A:rm $ip_toggle:}${icon}$interface: $ip_addr%{A}"
    else
        msg="%{A:touch $ip_toggle:}${icon}$interface: $primary_msg%{A}"
    fi

    msg="%{B$BLACK}%{F$GREEN} $msg %{F-}%{B-}"
    echo "$ANCHOR_NETWORK$msg"
    sleep $REFRESH_NETWORK
done

1.5.8. Spotify

This makes use of spotify-cli, a simple script I wrote that interfaces with spotify's dbus.

. $HOME/.config/lemonbar/config.default
LOGGER=$(get_logger lemonbar.spotify)

$LOGGER "Initializing spotify module"

# Colors are actually set in date!!!!
while true; do
    # This repo should be cloned or installed
    metadata=$($HOME/src/spotify-cli/spotify-cli)

    if [ -z "$(echo $metadata | grep 'ERROR')"]; then

        # Notice how the first message doesn't have a foreground color open
        # statement but has a close statement. This is because we are capturing
        # the color change from the [volume] module.
        msg="%{B$BLACK}${PL_R}%{B-}%{F-}"

        # Gather info from the spotify cli script
        title=$(echo $metadata | sed -e 's/title: \(.*\) artist:.*/ \1 /')
        artist=$(echo $metadata | sed -e 's/.*artist: \(.*\) album:.*/\1/')
        paused=$(echo $metadata | sed -e 's/.*status: \(.*\)/\1/')

        artist=" ${ICON_MUSIC}${artist} "
        if [ "$paused" != "Paused" ]; then
            status_color=$WHITE
        else
            status_color=$XBACKGROUND
        fi

        msg="$msg%{B$BLACK}%{F$CYAN}$artist"
        msg="$msg${PL_Rb}%{F-}%{F$status_color}"
        msg="$msg$title%{B-}%{F$BLACK}${PL_R}%{F-}"
    else

        # IMPORTANT: look at comment above
        msg="%{B$XBACKGROUND}${PL_R}%{B-}%{F-}"
    fi

    echo "$ANCHOR_SPOTIFY$msg"
    sleep $REFRESH_TIME
done

1.5.9. Time

. $HOME/.config/lemonbar/config.default
LOGGER=$(get_logger lemonbar.time)

$LOGGER "Initializing time module"

# Colors are actually set in date!!!!
while true; do
    msg="$(date +'%l:%M:%S %p') %{F-}%{B-}"
    msg="  %{F$CYAN}${ICON_TIME}$msg%{F$BLACK}${PL_R}%{F-}"
    echo "$ANCHOR_TIME$msg"
    sleep $REFRESH_TIME
done

1.5.10. Who am I

. $HOME/.config/lemonbar/config.default
LOGGER=$(get_logger lemonbar.whoami)

$LOGGER "Initializing whoami module"

wmsg="${ICON_WHOAMI}$(whoami)@$(hostname)"

version=$(uname -r | sed -e 's/-.*//g')
distro=$(grep ^ID= /etc/os-release | sed -e 's/^ID=//g' | tr -d '"')
distro=$(echo $distro | sed 's/.*/\u&/')
umsg="${ICON_LINUX}$distro $(uname -o) $version $(uname -m)"

prm_color='#256568'
alt_color=$BLACK

msg="%{B$prm_color} $wmsg %{B-}"
msg="$msg%{F$prm_color}%{B$alt_color}${PL_R}%{F-}"
msg="$msg %{F$prm_color}$umsg%{F-} %{B-}"
msg="$msg%{F$alt_color}${PL_R}%{F-}"

echo "$ANCHOR_WHOAMI$msg"

1.5.11. Volume

The volume module is a bit different from the other modules so far. We need to update it whenever we trigger a volume change (this is done by capturing the volume events via the media keys. Herbstluftwm will then pass a message to the volume event FIFO). However this is not enough as the volume can be changed by the software as well. So ontop of this, we also have a periodic update as well.

Here is the primary update logic:

if [ $(pulsemixer --get-mute) -eq 0 ]; then
    msg="$(pulsemixer --get-volume | awk '{print $1}')%"
    color=$CYAN
else
    msg="MUTE"
    color=$RED
fi
msg="%{B$color} ${ICON_VOLUME}$msg %{B-}%{F$color}"
echo "$ANCHOR_VOLUME$msg"

Where is the periodic version of the volume module:

. $HOME/.config/lemonbar/config.default
LOGGER=$(get_logger lemonbar.volume.const)

$LOGGER "Initializing volume module [constant]"

while true; do
    if [ $(pulsemixer --get-mute) -eq 0 ]; then
        msg="$(pulsemixer --get-volume | awk '{print $1}')%"
        color=$CYAN
    else
        msg="MUTE"
        color=$RED
    fi
    msg="%{B$color} ${ICON_VOLUME}$msg %{B-}%{F$color}"
    echo "$ANCHOR_VOLUME$msg"
    sleep $REFRESH_VOLUME
done

Here is the event triggered version of the volume module:

. $HOME/.config/lemonbar/config.default
LOGGER=$(get_logger lemonbar.volume.event)

$LOGGER "Initializing volume module [event driven]"

while read -r volume_event; do
    (
        if [ $(pulsemixer --get-mute) -eq 0 ]; then
            msg="$(pulsemixer --get-volume | awk '{print $1}')%"
            color=$CYAN
        else
            msg="MUTE"
            color=$RED
        fi
        msg="%{B$color} ${ICON_VOLUME}$msg %{B-}%{F$color}"
        echo "$ANCHOR_VOLUME$msg"
    ) &
done

1.5.12. Window Info

I don't actually use this module anymore, but its here in case I ever do again.

. $HOME/.config/lemonbar/config.default
LOGGER=$(get_logger lemonbar.windowinfo)

$LOGGER "Initializing windowinfo module"

while read -r window_event; do
    win_active=$(xdotool getactivewindow)
    win_class=$(xprop -id $win_active \
        | grep WM_CLASS \
        | awk '{print $NF}' \
        | tr -d '"' )

    if [ -z "$win_class" ]; then
        msg="<$(xprop -root _NET_WM_NAME \
            | cut -d " " -f3- \
            | tr -d '"')>"
    else
        msg="$win_class ($win_active)"
    fi
    msg=$($MSG_WRAPPER $ICON_WININFO $BLUE $msg)
    echo "$ANCHOR_WININFO$msg"
    sleep $REFRESH_WININFO
done

1.5.13. Workspaces

. $HOME/.config/lemonbar/config.default
LOGGER=$(get_logger lemonbar.workspaces)

$LOGGER "Initializing workspaces module [herbstluftwm]"

hclm() {
    herbstclient list_monitors
}

while read -r workspace_event; do
    # For whatever happens here so we can speed up the updates even further
    # commented for now, but uncomment to make it faster
    (

    # convert the current space id to index-1 format
    current_space_id=$(xprop -root _NET_CURRENT_DESKTOP \
        | awk '{print $3}')
    total_spaces=$(xprop -root _NET_NUMBER_OF_DESKTOPS \
        | awk '{print $3}')
    all_space_names=$(xprop -root _NET_DESKTOP_NAMES \
        | cut -d " " -f3- \
        | tr -d ',"')
    active_windows=$(xprop -root _NET_CLIENT_LIST \
        | cut -d " " -f5- \
        | tr -d ',')
    active_spaces=$(for window in $active_windows; do \
        xprop -id $window _NET_WM_DESKTOP \
        | cut -d " " -f3- ; done)
    msg=""

    # FIXME Is there to get this info without relying on herbs?
    mon_1=$(hclm | grep '0:' | awk '{print $5}' | tr -d '"')
    mon_2=$(hclm | grep '1:' | awk '{print $5}' | tr -d '"')
    mon_3=$(hclm | grep '2:' | awk '{print $5}' | tr -d '"')

    for space_id in $(seq $total_spaces); do

        # Get the actual name of the workspace
        ws=$(echo $all_space_names | awk -v N=$space_id '{print $N}')

        # Fix the index of the space_id to be zero index
        # (note, awk needs to be in index 1 format)
        space_id=$((space_id - 1))

        # check if the workspace is empty
        if [ "$ws" = "$mon_1" ]; then
            ws="%{F$RED}$ws%{F-}"
        elif [ "$ws" = "$mon_2" ]; then
            ws="%{F$GREEN}$ws%{F-}"
        elif [ "$ws" = "$mon_3" ]; then
            ws="%{F$BLUE}$ws%{F-}"
        elif [ -z "$(echo $active_spaces | grep $space_id)" ]; then
            ws="%{F$XBACKGROUND}$ws%{F-}"
        else
            ws="%{F#585858}$ws%{F-}"
        fi

        # Add the jump to workspace command
        ws="%{A:xdotool set_desktop $space_id:}$ws%{A}"

        # mark output if currently focused
        if [ $space_id -eq $current_space_id ]; then
            msg="$msg %{+u}$ws%{-u}"
        else
            msg="$msg $ws"
        fi

    done;

    l_msg="%{F$BLACK}${PL_L}%{F-}%{B$BLACK}"
    r_msg="%{B-}%{F$BLACK}${PL_R}%{F-}"

    msg="$l_msg %{F$WHITE}${ICON_WORKSPACES}%{F-}$msg  $r_msg"

    echo "$ANCHOR_WORKSPACES$msg"

    ) &
done

Created: 2021-11-13

Emacs 26.1 (Org mode 9.5)