i3status-rust: the bars as a module, claiming node-bar (hq ADR 0208)
Owns both bars and their icons, the bar blocks as an i3 drop-in; the battery, GPU and headset blocks leave (hardware and a person's devices), the weather needs no key; the update count and the bar watchdog become module code, the watchdog started once per session. Go tools reload, blocks, block-run and themes.
This commit is contained in:
+64
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env bash
|
||||
# i3bar-watchdog [session-pid] (module i3status-rust, novox/hq ADR 0208): respawns a bar that died.
|
||||
#
|
||||
# i3 starts one i3bar per `bar { }` block and never restarts one that exits, and a reload does not
|
||||
# either. Changing the monitor setup reliably kills the bar that owns the tray. This brings back the
|
||||
# one missing i3bar, without restarting i3, and logs the outputs at that moment: the evidence for the
|
||||
# cause, which is i3bar's.
|
||||
#
|
||||
# Started once per session from the session's start, with the session's own pid; it ends when that
|
||||
# process does. Adopted from the predecessor's i3-bar-watchdog user unit of 2026-10-04.
|
||||
set -uo pipefail
|
||||
|
||||
session_pid="${1:-}"
|
||||
interval="${I3_BAR_WATCHDOG_INTERVAL:-5}"
|
||||
# Two misses in a row before acting: an i3 restart tears every bar down and starts them again.
|
||||
confirm="${I3_BAR_WATCHDOG_CONFIRM:-2}"
|
||||
# Never respawn the same bar more often than this, so a bar that dies at once is not a tight loop.
|
||||
cooldown="${I3_BAR_WATCHDOG_COOLDOWN:-30}"
|
||||
|
||||
log() { printf 'i3bar-watchdog: %s\n' "$*" >&2; }
|
||||
|
||||
for tool in i3-msg i3bar pgrep; do
|
||||
command -v "$tool" >/dev/null 2>&1 || {
|
||||
log "$tool is missing; not watching"
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
|
||||
declare -A missed=() fixed=()
|
||||
|
||||
bar_ids() { i3-msg -t get_bar_config 2>/dev/null | grep -oE '"[^"]+"' | tr -d '"'; }
|
||||
outputs() { xrandr --listmonitors 2>/dev/null | tail -n +2 | awk '{print $2" "$3}' | tr '\n' ' '; }
|
||||
session_alive() { [ -z "$session_pid" ] || kill -0 "$session_pid" 2>/dev/null; }
|
||||
|
||||
while session_alive; do
|
||||
sleep "$interval"
|
||||
socket="$(i3 --get-socketpath 2>/dev/null)"
|
||||
[ -n "$socket" ] && [ -S "$socket" ] || continue
|
||||
ids="$(bar_ids)"
|
||||
[ -n "$ids" ] || continue
|
||||
while read -r id; do
|
||||
[ -n "$id" ] || continue
|
||||
if pgrep -u "$EUID" -f -- "i3bar --bar_id=$id" >/dev/null 2>&1; then
|
||||
missed[$id]=0
|
||||
continue
|
||||
fi
|
||||
missed[$id]=$((${missed[$id]:-0} + 1))
|
||||
[ "${missed[$id]}" -ge "$confirm" ] || continue
|
||||
now="$(date +%s)"
|
||||
if [ $((now - ${fixed[$id]:-0})) -lt "$cooldown" ]; then
|
||||
continue
|
||||
fi
|
||||
log "$id is gone; respawning it. Outputs now: $(outputs)"
|
||||
nohup i3bar --bar_id="$id" --socket="$socket" >/dev/null 2>&1 &
|
||||
disown 2>/dev/null || true
|
||||
fixed[$id]="$now"
|
||||
sleep 2
|
||||
if pgrep -u "$EUID" -f -- "i3bar --bar_id=$id" >/dev/null 2>&1; then
|
||||
missed[$id]=0
|
||||
else
|
||||
log "$id exited within 2s of respawning; check its status_command"
|
||||
fi
|
||||
done <<<"$ids"
|
||||
done
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
# i3status-updates [threshold] (module i3status-rust, novox/hq ADR 0208): the pending updates, for
|
||||
# the bottom bar's custom block (json = true). Emits one JSON line.
|
||||
#
|
||||
# Adopted from the operator's pkg-updates of 2026-10-04. The native `packages` block showed a raw
|
||||
# red error on every transient network failure, which on a roaming laptop is often. This keeps the
|
||||
# last good result through a failure and shows an error only after `threshold` failures in a row
|
||||
# (default 10). Official packages through checkupdates (pacman-contrib, a temporary database, no
|
||||
# root); AUR packages through paru when it is installed, and none counted when it is not.
|
||||
set -uo pipefail
|
||||
|
||||
threshold="${1:-10}"
|
||||
icon="update"
|
||||
state_dir="${XDG_RUNTIME_DIR:-/tmp}/i3status-updates"
|
||||
mkdir -p "$state_dir"
|
||||
last="$state_dir/last_good.json"
|
||||
failures="$state_dir/failures"
|
||||
|
||||
json() { printf '{"icon":"%s","state":"%s","text":"%s","short_text":"%s"}' "$icon" "$1" "$2" "$3"; }
|
||||
count() { [ -n "$1" ] && printf '%s\n' "$1" | grep -c . || echo 0; }
|
||||
|
||||
official="$(checkupdates 2>/dev/null)"
|
||||
official_rc=$?
|
||||
aur="" aur_rc=0
|
||||
if command -v paru >/dev/null 2>&1; then
|
||||
aur="$(paru -Qua 2>/dev/null)"
|
||||
aur_rc=$?
|
||||
fi
|
||||
|
||||
# checkupdates: 0 updates listed, 2 none pending; anything else is a failure.
|
||||
if { [ "$official_rc" -eq 0 ] || [ "$official_rc" -eq 2 ]; } && [ "$aur_rc" -eq 0 ]; then
|
||||
o="$(count "$official")"
|
||||
a="$(count "$aur")"
|
||||
total=$((o + a))
|
||||
if [ "$total" -eq 0 ]; then
|
||||
payload="$(json Good "up to date" "")"
|
||||
else
|
||||
# A kernel update wants a reboot: the one worth not putting off.
|
||||
state=Info
|
||||
printf '%s\n' "$official" | grep -qE '^(linux|linux-lts|linux-zen) ' && state=Warning
|
||||
label="$o + $a = $total updates"
|
||||
[ "$a" -eq 0 ] && label="$total updates"
|
||||
[ "$total" -eq 1 ] && label="1 update"
|
||||
payload="$(json "$state" "$label" "$total")"
|
||||
fi
|
||||
printf '%s' "$payload" >"$last"
|
||||
printf '0' >"$failures"
|
||||
printf '%s\n' "$payload"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
n=$(($(cat "$failures" 2>/dev/null || echo 0) + 1))
|
||||
printf '%s' "$n" >"$failures"
|
||||
if [ "$n" -ge "$threshold" ]; then
|
||||
json Critical "update check failing (${n}x)" "!"
|
||||
echo
|
||||
elif [ -s "$last" ]; then
|
||||
cat "$last"
|
||||
echo
|
||||
else
|
||||
json Idle "" ""
|
||||
echo
|
||||
fi
|
||||
Reference in New Issue
Block a user