Files
config-backup/i3/battery_status.sh
T
ION606 1fc598c6a0 merge
2026-01-03 12:55:26 -08:00

142 lines
4.1 KiB
Bash

#!/usr/bin/env bash
# prints a battery icon + either percentage or time remaining
# depends (optional): upower or acpi; otherwise falls back to /sys
# config: icons from nerd fonts / font awesome
icon_empty=""; # very low
icon_low=""; # low
icon_mid=""; # medium
icon_high=""; # high
icon_full=""; # full
icon_charge=""; # charging bolt
icon_plug=""; # plugged/full
icon_alert=""; # critical
# config: low/critical thresholds
low_threshold=20;
crit_threshold=10;
state_file="${XDG_CACHE_HOME:-$HOME/.cache}/polybar_battery_mode";
mode="percent";
if [[ -f "$state_file" ]]; then
mode="$(cat "$state_file" 2>/dev/null | tr -d '\n' )";
fi
bat_path="$(ls -d /sys/class/power_supply/BAT* 2>/dev/null | head -n1)";
if [[ -z "$bat_path" ]]; then
echo "${icon_alert} no battery";
exit 0;
fi
# helpers
pick_icon() {
local pct="$1";
local status="$2";
local base;
if (( pct == 100 )); then base="^~^"
elif (( pct >= 95 )); then base="$icon_full";
elif (( pct >= 75 )); then base="$icon_high";
elif (( pct >= 50 )); then base="$icon_mid";
elif (( pct >= 25 )); then base="$icon_low";
else base="$icon_empty";
fi
# overlay/override for charging or full on ac power
if [[ "$status" == "Charging" ]]; then
echo "$icon_charge $base";
elif [[ "$status" == "Full" || "$status" == "Not charging" ]]; then
echo "$icon_plug $base";
else
echo "$base";
fi
}
fmt_time() {
# normalize various formats (e.g., "1.5 hours", "02:41:13")
local raw="$1";
if [[ "$raw" =~ ^[0-9]+:[0-9]{2}:[0-9]{2}$ ]]; then
# hh:mm:ss -> XhYm
IFS=':' read -r h m s <<<"$raw";
printf "%sh%sm" "$h" "$m";
elif [[ "$raw" =~ ^([0-9]+(\.[0-9]+)?)\ hours?$ ]]; then
# upower: "1.4 hours"
local hours="${raw% hours}";
hours="${hours% hour}";
# convert decimal hours to h m
local total_min;
total_min="$(python - <<'PY' "$hours"
import math,sys
arg = sys.argv[1] if len(sys.argv) > 1 else "0"
h = float(arg)
m = round((h - int(h)) * 60)
print(f"{int(h)}h{m}m")
PY
)";
printf "%s" "$total_min";
else
printf "%s" "$raw";
fi
}
percentage="";
status="";
eta=""; # time to empty or to full
# try upower first (dbus-based, used by many desktops)
if command -v upower >/dev/null 2>&1; then
dev="$(upower -e | grep -m1 BAT)";
if [[ -n "$dev" ]]; then
info="$(upower -i "$dev" 2>/dev/null)";
status="$(awk -F': *' '/^\s*state:/{print $2}' <<<"$info")";
percentage="$(awk -F': *' '/^\s*percentage:/{print $2}' <<<"$info" | tr -d '% ')";
if [[ "$status" == "charging" ]]; then
eta="$(awk -F': *' '/time to full/{print $2}' <<<"$info")";
elif [[ "$status" == "discharging" ]]; then
eta="$(awk -F': *' '/time to empty/{print $2}' <<<"$info")";
fi
# normalize capitalization
status="$(tr '[:lower:]' '[:upper:]' <<<"${status:0:1}")${status:1}";
fi
fi
# fallback: acpi (reads /sys or /proc and calculates eta)
if [[ -z "$percentage" || -z "$status" ]] && command -v acpi >/dev/null 2>&1; then
line="$(acpi -b 2>/dev/null | head -n1)";
# examples: "Battery 0: Discharging, 93%, 02:41:13 remaining"
# "Battery 0: Charging, 80%, 00:20:00 until charged"
status="$(awk -F', *' -v OFS=',' '{split($1,a,": "); print a[2]}' <<<"$line")";
percentage="$(awk -F', *' '{gsub("%","",$2); print $2}' <<<"$line")";
eta="$(awk -F', *' '{print $3}' <<<"$line" | sed -E 's/(remaining|until charged)//g' | xargs)";
fi
# final fallback: sysfs (percentage + status only)
if [[ -z "$percentage" || -z "$status" ]]; then
if [[ -r "$bat_path/capacity" && -r "$bat_path/status" ]]; then
percentage="$(cat "$bat_path/capacity" 2>/dev/null)";
status="$(cat "$bat_path/status" 2>/dev/null)";
fi
fi
# guard rails
if [[ -z "$percentage" ]]; then
echo "${icon_alert} n/a";
exit 0;
fi
icon="$(pick_icon "$percentage" "$status")";
# add critical marker if really low and discharging
if (( percentage <= crit_threshold )) && [[ "$status" == "Discharging" ]]; then
icon="${icon_alert} ${icon}";
fi
# output based on mode
if [[ "$mode" == "time" && -n "$eta" ]]; then
echo "${icon} $(fmt_time "$eta")";
else
echo "${icon} ${percentage}%";
fi