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 $!