#!/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