From 2ef59a7720b7f8c50b212ee1ac97cebd1bd4489d Mon Sep 17 00:00:00 2001
From: ItsDrike <itsdrike@protonmail.com>
Date: Sat, 13 Aug 2022 13:41:11 +0200
Subject: [PATCH] Add lemonbar implementation for bspwm

---
 home/.config/lemonbar/applet-battery    |  33 ++++
 home/.config/lemonbar/applet-cmd        |  18 ++
 home/.config/lemonbar/applet-pulse      |  38 ++++
 home/.config/lemonbar/applet-storage    |  29 +++
 home/.config/lemonbar/applet-window     |  41 +++++
 home/.config/lemonbar/applet-workspaces |  84 +++++++++
 home/.config/lemonbar/lemonbar-launch   | 229 ++++++++++++++++++++++++
 7 files changed, 472 insertions(+)
 create mode 100755 home/.config/lemonbar/applet-battery
 create mode 100755 home/.config/lemonbar/applet-cmd
 create mode 100755 home/.config/lemonbar/applet-pulse
 create mode 100755 home/.config/lemonbar/applet-storage
 create mode 100755 home/.config/lemonbar/applet-window
 create mode 100755 home/.config/lemonbar/applet-workspaces
 create mode 100755 home/.config/lemonbar/lemonbar-launch

diff --git a/home/.config/lemonbar/applet-battery b/home/.config/lemonbar/applet-battery
new file mode 100755
index 0000000..8611022
--- /dev/null
+++ b/home/.config/lemonbar/applet-battery
@@ -0,0 +1,33 @@
+#!/bin/bash
+
+PANEL_FIFO="$1"
+PREFIX="$2"
+DELAY="$3"
+readarray -td";" ICONS <<< "$4"; declare -p ICONS >/dev/null
+BATTERY_NAME="${5-BAT0}"
+
+exec 5>"$PANEL_FIFO"
+
+main() {
+    local capacity="$(cat /sys/class/power_supply/$BATTERY_NAME/capacity)"
+    local icon=""
+    if [ "$capacity" -lt 20 ]; then
+        icon="${ICONS[0]}"
+    elif [ "$capacity" -lt 40 ]; then
+        icon="${ICONS[1]}"
+    elif [ "$capacity" -lt 60 ]; then
+        icon="${ICONS[2]}"
+    elif [ "$capacity" -lt 80 ]; then
+        icon="${ICONS[3]}"
+    else
+        icon="${ICONS[4]}"
+    fi
+
+    REPLY="$icon $capacity%"
+}
+
+while :; do
+    main
+    echo "$PREFIX $REPLY" >&5
+    sleep "$DELAY"
+done
diff --git a/home/.config/lemonbar/applet-cmd b/home/.config/lemonbar/applet-cmd
new file mode 100755
index 0000000..51e6987
--- /dev/null
+++ b/home/.config/lemonbar/applet-cmd
@@ -0,0 +1,18 @@
+#!/usr/bin/env zsh
+
+PANEL_FIFO="$1"
+PREFIX="$2"
+DELAY="$3"
+CMD="$4"
+
+exec 5>"$PANEL_FIFO"
+
+main() {
+    REPLY="$($SHELL -c "$CMD")"
+}
+
+while :; do
+    main
+    echo "$PREFIX $REPLY" >&5
+    sleep "$DELAY"
+done
diff --git a/home/.config/lemonbar/applet-pulse b/home/.config/lemonbar/applet-pulse
new file mode 100755
index 0000000..330d2e2
--- /dev/null
+++ b/home/.config/lemonbar/applet-pulse
@@ -0,0 +1,38 @@
+#!/usr/bin/env zsh
+
+PANEL_FIFO="$1"
+PREFIX="$2"
+ICONS=($(echo "$3" | tr ";" "\n"))
+
+exec 5>"$PANEL_FIFO"
+
+main() {
+    local cmd=( $(amixer -M get Master) )
+    local playback_level="${cmd[-2][2,-3]}"
+    local icon=""
+
+    if [[ "${cmd[-1]}" == "[off]" ]]; then
+        icon="${ICONS[1]}"
+    else
+        if [[ "$playback_level" -eq 0 ]]; then
+            icon="${ICONS[2]}"
+        elif [[ "$playback_level" -lt 30 ]]; then
+            icon="${ICONS[3]}"
+        elif [[ "$playback_level" -lt 60 ]]; then
+            icon="${ICONS[4]}"
+        elif [[ "$playback_level" -lt 100 ]]; then
+            icon="${ICONS[5]}"
+        else
+            icon="${ICONS[6]}"
+        fi
+    fi
+
+    REPLY="$icon $playback_level%"
+}
+
+{print init; stdbuf -oL alsactl monitor pulse} | while read line; do
+    case ${line} in
+        *Master\ Playback*|init ) main; print "$PREFIX $REPLY" >&5 ;;
+        * ) continue ;;
+    esac
+done
diff --git a/home/.config/lemonbar/applet-storage b/home/.config/lemonbar/applet-storage
new file mode 100755
index 0000000..2cc5706
--- /dev/null
+++ b/home/.config/lemonbar/applet-storage
@@ -0,0 +1,29 @@
+#!/usr/bin/env zsh
+
+PANEL_FIFO="$1"
+PREFIX="$2"
+DELAY="$3"
+MOUNTPOINTS=($(echo "$4" | tr ";" "\n"))
+ICONS=($(echo "$5" | tr ";" "\n"))
+typeset -A ICON_MAP=(${MOUNTPOINTS:^ICONS})
+
+exec 5>"$PANEL_FIFO"
+
+main() {
+    local data=$(df -H)
+
+    RESULT=()
+    for mountpoint in $MOUNTPOINTS; do
+        local icon="${ICON_MAP[$mountpoint]}"
+        local size="$(echo "$data" | awk "\$6 == \"$mountpoint\" {print \$4}")"
+        RESULT+=("$icon $size")
+    done
+
+    REPLY="$(printf "%s " "${RESULT[@]}")"
+}
+
+while :; do
+    main
+    echo "$PREFIX $REPLY" >&5
+    sleep "$DELAY"
+done
diff --git a/home/.config/lemonbar/applet-window b/home/.config/lemonbar/applet-window
new file mode 100755
index 0000000..87a4810
--- /dev/null
+++ b/home/.config/lemonbar/applet-window
@@ -0,0 +1,41 @@
+#!/usr/bin/env zsh
+
+PANEL_FIFO="$1"
+PREFIX="$2"
+DELAY="$3"
+MAX_LEN="$(("$4"-3))"
+
+exec 5>"$PANEL_FIFO"
+
+main() {
+    local win_id="$(bspc query -N -n focused)"
+    local win_name="$(xprop -id "$win_id" | grep "^WM_NAME" | cut -d' ' -f3- | tr -d '"')"
+    local trimmed_win_name="${win_name:0:$MAX_LEN}"
+    if [[ "$win_name" != "$trimmed_win_name" ]]; then
+        win_name="${trimmed_win_name}..."
+    fi
+
+    REPLY="$trimmed_win_name"
+}
+
+# Run 2 loops here, one for instant updates once other window is focused
+# in bspwm, other for timed updates, in case the window changes it's title
+# on it's own (such as when we switch a tab in firefox)
+
+{print init; stdbuf -oL bspc subscribe node} | while read line; do
+    case ${line} in
+        node_focus* ) main ;;
+        init ) main ;;
+        * ) continue ;;
+    esac
+
+    echo "$PREFIX $REPLY" >&5
+done &
+
+while :; do
+    main
+    echo "$PREFIX $REPLY" >&5
+    sleep "$DELAY"
+done
+
+wait $!
diff --git a/home/.config/lemonbar/applet-workspaces b/home/.config/lemonbar/applet-workspaces
new file mode 100755
index 0000000..e39932f
--- /dev/null
+++ b/home/.config/lemonbar/applet-workspaces
@@ -0,0 +1,84 @@
+#!/usr/bin/env zsh
+
+PANEL_FIFO="$1"
+PREFIX="$2"
+
+ACTIVE_PREFIX="%{F#98BE65}%{U#98BE65}%{+u}"
+ACTIVE_SUFFIX="%{F-}%{U-}%{-u}"
+VISIBLE_PREFIX="%{F#98BE65}"
+VISIBLE_SUFFIX="%{F-}"
+URGENT_PREFIX="%{F#C45500}!"
+URGENT_SUFFIX="!%{F-}"
+EMPTY_PREFIX="%{F#C792EA}"
+EMPTY_SUFFIX="%{F-}"
+OCCUPIED_PREFIX="%{F#82AAFF}"
+OCCUPIED_SUFFIX="%{F-}"
+WS_SEPARATOR=" "
+typeset -A MAP
+MAP[1]="dev"
+MAP[2]="www"
+MAP[3]="sys"
+MAP[4]="chat"
+MAP[5]="mus"
+MAP[6]="vid"
+MAP[7]="doc"
+MAP[8]="virt"
+MAP[9]="etc"
+MAP[10]="scr"
+
+exec 5>"$PANEL_FIFO"
+
+main() {
+    REPORT="$1"
+
+    local -a desktops
+    local prefix suffix name
+
+    for item in ${(s.:.)REPORT}; do
+        name=${item[2,-1]}
+        case $item in
+            f* ) # free|empty unfocused
+                prefix="${EMPTY_PREFIX}"
+                suffix="${EMPTY_SUFFIX}"
+                ;;
+            o* ) # occupied unfocused
+                >&2 echo "unfocused occupied! $name"
+                prefix="${OCCUPIED_PREFIX}"
+                suffix="${OCCUPIED_SUFFIX}"
+                ;;
+            u* ) # urgent unfocused
+                prefix="${URGENT_PREFIX}"
+                suffix="${URGENT_SUFFIX}"
+                ;;
+            [FOU]* ) # visible maybe focused, maybe occupied, maybe urgent
+                if bspc query -D -d "$name".focused >/dev/null 2>&1; then
+                    prefix="${ACTIVE_PREFIX}"
+                    suffix="${ACTIVE_SUFFIX}"
+                else
+                    prefix="${VISIBLE_PREFIX}"
+                    suffix="${VISIBLE_SUFFIX}"
+                fi
+                ;;
+            * ) continue ;;
+        esac
+
+        if [[ "$name" == "0" ]]; then
+            name=10
+        fi
+
+        desktops[$name]="${prefix}${MAP[$name]}${suffix}"
+    done
+
+    OUTPUT=()
+    for wstxt in $desktops; do
+        OUTPUT+=("$wstxt" "$WS_SEPARATOR")
+    done
+
+
+    RESULT="$(printf "%s" "${OUTPUT[@]}")"
+}
+
+bspc subscribe report | while read -r line; do
+    main "$line"
+    print "$PREFIX $RESULT" >&5
+done
diff --git a/home/.config/lemonbar/lemonbar-launch b/home/.config/lemonbar/lemonbar-launch
new file mode 100755
index 0000000..065df64
--- /dev/null
+++ b/home/.config/lemonbar/lemonbar-launch
@@ -0,0 +1,229 @@
+#!/usr/bin/env zsh
+
+# ---- PASSED ARGUMENTS ----
+
+MONITOR="$1"
+
+
+# ---- CONFIGURATION VARIABLES ----
+
+# Geometry
+BAR_HEIGHT=28
+BAR_TOP_PADDING=3
+BAR_BOTTOM_PADDING=0
+BAR_LEFT_PADDING=3
+BAR_RIGHT_PADDING=3
+
+# Fonts
+FONT_NAMES=("JetBrainsMonoMedium:pixelsize=6" "Hack Nerd Font:size=10")
+FONT_OFFSETS=(4 4 2)
+
+# Colors (#AARRGGBB)
+BACKGROUND_COLOR="#FF222222"
+FOREGROUND_COLOR="#FFFFFFFF"
+UNDERLINE_COLOR="#FF268BD2"
+
+# Modules
+SEPARATOR=("%{F#50FFFFFF} | %{F-}")
+
+LEFT_MODULES=("title" "workspaces" "windowname")
+CENTER_MODULES=()
+RIGHT_MODULES=("kernel" "battery" "memory" "storage" "cpu" "volume" "uptime" "bitcoin" "time")
+
+typeset -A CMDS
+CMDS[volup]="pulsemixer --change-volume +3"
+CMDS[voldown]="pulsemixer --change-volume -3"
+
+typeset -A PREFIX
+typeset -A SUFFIX
+PREFIX[bitcoin]="%{F#EFCB10}ﴑ "
+SUFFIX[bitcoin]="%{F-}"
+PREFIX[time]="%{F#46D9FF} "
+SUFFIX[time]="%{F-}"
+PREFIX[uptime]="%{F#98be65} "
+SUFFIX[uptime]="%{F-}"
+PREFIX[volume]="%{A4:volup:}%{A5:voldown:}%{F#ECBE7B}"
+SUFFIX[volume]="%{F-}%{A}%{A}"
+PREFIX[cpu]="%{F#78DB32} "
+SUFFIX[cpu]="%{F-}"
+PREFIX[storage]="%{F#51AFEF}"
+SUFFIX[storage]="%{F-}"
+PREFIX[memory]="%{F#FF6C6B} "
+SUFFIX[memory]="%{F-}"
+PREFIX[battery]="%{F#9CE996}"
+SUFFIX[battery]="%{F-}"
+PREFIX[kernel]="%{F#B3AFC2} "
+SUFFIX[kernel]="%{F-}"
+PREFIX[windowname]="%{F#B3AFC2}"
+SUFFIX[windowname]="%{F-}"
+#PREFIX[title]="%{F#FF7080}"
+#SUFFIX[title]="%{F-}"
+
+# Don't escape % signs in these modules (they define their own colors)
+NO_ESCAPE=("workspaces")
+
+
+# Unique WM_NAME of the panel
+PANEL_NAME="lemonbar-${DISPLAY[2,-1]}-$MONITOR"
+
+# Path to FIFO pipe file over which applets send their results
+PANEL_FIFO="${XDG_RUNTIME_DIR}/${PANEL_NAME}"
+
+
+# ---- MODULES ----
+
+# --- FILEDESCRIPTOR ---
+
+# Make the FIFO pipe file, to which the defined applets below will be
+# sending their outputs, which will then be read in the event handler
+[[ -p "$PANEL_FIFO" ]] && rm "$PANEL_FIFO"
+mkfifo -m 600 "$PANEL_FIFO"
+exec 5<>"$PANEL_FIFO"
+
+# --- APPLETS ---
+
+run_applet() {
+    TYPE="$1"
+    shift;
+
+    $HOME/.config/lemonbar/"applet-$TYPE" "$PANEL_FIFO" $@ &
+}
+
+typeset -A mods
+mods[title]=""
+
+_uptime_cmd="uptime -p | sed -e 's/^up //' -e 's/ years\\?,\\?/y/' -e 's/ months\\?,\\?/m/' -e 's/ weeks\\?,\\?/w/' -e 's/ days\\?,\\?/d/' -e 's/ hours\\?,\\?/h/' -e 's/ minutes\\?,\\?/m/' -e 's/ seconds\\?,\\?/s/' | cut -d' ' -f-2"
+_cpu_cmd="awk '{u=\$2+\$4; t=\$2+\$4+\$5; if (NR==1){u1=u; t1=t;} else printf \"%.0f%%\\n\", (\$2+\$4-u1) * 100 / (t-t1); }' <(grep 'cpu ' /proc/stat) <(sleep 1;grep 'cpu ' /proc/stat)"
+_mem_cmd="free -h --si | grep 'Mem' | awk '{printf \"%s \", \$3}' && free | grep 'Mem' | awk '{printf \"(%.0f%%)\\n\", (\$3 / \$2) * 100 }'"
+
+run_applet "cmd" "time" 1 "date +'%H:%M %b %d %Y'"
+run_applet "cmd" "bitcoin" 600 "bitcoin"
+run_applet "cmd" "kernel" infinity "uname -r"
+run_applet "cmd" "uptime" 60 "$_uptime_cmd"
+run_applet "cmd" "cpu" 5 "$_cpu_cmd"
+run_applet "cmd" "memory" 5 "$_mem_cmd"
+run_applet "pulse" "volume" "婢;ﱝ;;;墳;"
+run_applet "storage" "storage" 60 "/;/home" ";"
+run_applet "battery" "battery" 5 ";;;;"
+run_applet "window" "windowname" 5 65
+run_applet "workspaces" "workspaces"
+
+# ---- DYNAMIC CONFIGURATION ----
+
+# Kill all pannels that already exist with this name 
+xdo kill -a "$PANEL_NAME" >/dev/null 2>&1
+
+# This variable will hold all of the arguments to lemonbar
+# which will get set later as the config is parsed
+LEMONBAR_ARGS=()
+
+# --- PANEL NAME ---
+
+# Set the pannel's WM_NAME to the generated unique name
+LEMONBAR_ARGS+=("-n" "$PANEL_NAME")
+
+# --- GEOMETRY ---
+
+geometry_keys=(x y width height)
+geometry_values=($(bspc query -T -m "$MONITOR" | jq '.rectangle[]'))
+typeset -A geometry=(${geometry_keys:^geometry_values})
+
+((geometry[x] += BAR_LEFT_PADDING))
+((geometry[y] += BAR_TOP_PADDING))
+((geometry[width] -= BAR_LEFT_PADDING + BAR_RIGHT_PADDING))
+((geometry[x] += BAR_LEFT_PADDING))
+((geometry[height] = BAR_HEIGHT))
+
+# Configure BSPWM monitor to use a top_padding that ignores our bar
+total_top_padding=$((geometry[height] + bar_bottom_padding + bar_top_padding))
+bspc config -m "$MONITOR" top_padding "$total_top_padding"
+
+# Force docking - don't set EWMH bindings, we control BSPWM's padding manually,
+# so that it respects our bottom padding too
+LEMONBAR_ARGS+=("-d")
+
+# Pass the computed rectangle geometry
+LEMONBAR_ARGS+=("-g" "${geometry[width]}x${geometry[height]}+${geometry[x]}x${geometry[y]}")
+
+# --- FONTS ---
+
+typeset -A fonts=(${FONT_NAMES:^FONT_OFFSETS})
+for key val in "${(@kv)fonts}"; do
+    LEMONBAR_ARGS+=("-o" "$val" "-f" "$key")
+done
+
+# --- COLORS ---
+
+LEMONBAR_ARGS+=("-F" "$FOREGROUND_COLOR")
+LEMONBAR_ARGS+=("-B" "$BACKGROUND_COLOR")
+LEMONBAR_ARGS+=("-U" "$UNDERLINE_COLOR")
+
+
+# ---- EVENT HANDLER ----
+
+get_module_output() {
+    module="$1"
+    txt="$(echo "${mods[$module]}")"
+
+    if (($NO_ESCAPE[(I)$module])); then
+        :
+    else
+        txt="$(echo "$txt" | sed -r 's/%/%%/g')"
+    fi
+
+    txt="${PREFIX[$module]}$txt${SUFFIX[$module]}"
+    echo "$txt"
+}
+
+while read -r cmd <&5; do
+    # Get the key (prefix) and the actual text (output) from the received cmd
+    prefix="$(echo $cmd | cut -d " " -f1)"
+    output="$(echo $cmd | cut -d " " -f2-)"
+
+    if [[ "$prefix" == "cmd" ]]; then
+        cmd="${CMDS[$output]}"
+        >&2 echo "CMD: $output ($cmd)"
+        eval $cmd
+        continue
+    fi
+
+    # Update mods dict to hold the received output under this prefix
+    >&2 echo "$prefix -> $output"
+    mods[$prefix]="$output"
+
+    # Generate the output string with separators that gets passed
+    # into lemonbar
+    OUTPUT=("%{l}")
+    for module in $LEFT_MODULES; do
+        txt="$(get_module_output "$module")"
+        OUTPUT+=("$txt" "$SEPARATOR")
+    done
+    unset 'OUTPUT[-1]'
+
+    OUTPUT+=("%{c}")
+    for module in $CENTER_MODULES; do
+        txt="$(get_module_output "$module")"
+        OUTPUT+=("$txt" "$SEPARATOR")
+    done
+    unset 'OUTPUT[-1]'
+
+    OUTPUT+=("%{r}")
+    for module in $RIGHT_MODULES; do
+        txt="$(get_module_output "$module")"
+        OUTPUT+=("$txt" "$SEPARATOR")
+    done
+    unset 'OUTPUT[-1]'
+
+    # Print out the final output text
+    printf "%s" "${OUTPUT[@]}"
+done | lemonbar "${LEMONBAR_ARGS[@]}" | xargs -I_ echo cmd _ >&5 &
+
+# Wait until lemonbar starts (we run it detached)
+until _=$(xdo id -a "$PANEL_NAME"); do
+    sleep 0.1
+done
+
+# Keep the panel abve bspwm root window, but below everything else
+xdo above -t "$(xdo id -N Bspwm -n root | sort | head -n 1)" "$(xdo id -a $PANEL_NAME)"
+
+wait $!