tmux: add pressure-based activity tracker
Add heartbeat.sh that tracks session active time using three native tmux signals: client input (+5), window output (+2), and copy mode (+2), with -1 decay per tick. Score drives a block bar indicator and accumulated active time counter. Resets on tmux server restart. Status bar updates: - Heartbeat pill with pressure bar right of clock - Responsive breakpoints: hide network/battery <120 cols, date <80 - Click clock/date to toggle condensed view - Click heartbeat to instant-lock session - Static colon in clock (replaces shell-based blinker) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
114
home/.tmux/heartbeat.sh
Executable file
114
home/.tmux/heartbeat.sh
Executable file
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env bash
|
||||
# Heartbeat / liveness indicator for tmux status bar.
|
||||
#
|
||||
# Pressure-based activity tracking using native tmux signals:
|
||||
# Input (keystroke/mouse): +5 per event
|
||||
# Output (command output): +2 per tick
|
||||
# Copy mode (reading): +2 per tick
|
||||
# Decay: -1 per tick
|
||||
# Score capped at 100, floored at 0.
|
||||
#
|
||||
# Counter ticks while score > 0. Resets on tmux restart.
|
||||
#
|
||||
# Visual (pill with pressure bar):
|
||||
# 23m ▆ — teal time + teal bar (active, score > 0)
|
||||
# 5m — gray time, no bar (paused, score = 0)
|
||||
# 10s ▃ — amber time + amber bar (returning from away)
|
||||
|
||||
SCORE_CAP=100
|
||||
|
||||
state="${TMPDIR:-/tmp}/.tmux_heartbeat"
|
||||
read client_act win_act in_mode tmux_pid <<< \
|
||||
"$(tmux display-message -p '#{client_activity} #{window_activity} #{pane_in_mode} #{pid}')"
|
||||
now=$(date +%s)
|
||||
|
||||
# Load previous state
|
||||
prev_client="" prev_win="" score=0 active_total=0 srv_pid=""
|
||||
return_at="" return_show=0
|
||||
[[ -f "$state" ]] && source "$state"
|
||||
|
||||
# Reset if tmux server restarted
|
||||
if [[ "$srv_pid" != "$tmux_pid" ]]; then
|
||||
score=0 active_total=0 return_at="" return_show=0
|
||||
fi
|
||||
|
||||
# Detect screensaver/lock gaps (script wasn't running for >10s)
|
||||
if [[ -f "$state" ]]; then
|
||||
last_run=$(stat -f%m "$state" 2>/dev/null || stat -c%Y "$state" 2>/dev/null || echo "$now")
|
||||
gap=$(( now - last_run ))
|
||||
if (( gap > 10 )); then
|
||||
return_at=$now
|
||||
return_show=$gap
|
||||
score=0
|
||||
fi
|
||||
fi
|
||||
|
||||
# Score: check activity signals
|
||||
gaining=0
|
||||
|
||||
if [[ -n "$prev_client" && "$client_act" != "$prev_client" ]]; then
|
||||
(( score += 5 )); gaining=1
|
||||
fi
|
||||
|
||||
if [[ -n "$prev_win" && "$win_act" != "$prev_win" ]]; then
|
||||
(( score += 2 )); gaining=1
|
||||
fi
|
||||
|
||||
if [[ "$in_mode" == "1" ]]; then
|
||||
(( score += 2 )); gaining=1
|
||||
fi
|
||||
|
||||
(( score-- ))
|
||||
(( score > SCORE_CAP )) && score=$SCORE_CAP
|
||||
(( score < 0 )) && score=0
|
||||
|
||||
(( score > 0 )) && (( active_total++ ))
|
||||
|
||||
# Clear return flash after 4s
|
||||
if [[ -n "$return_at" ]] && (( now - return_at >= 4 )); then
|
||||
return_at="" return_show=0
|
||||
fi
|
||||
|
||||
# Persist
|
||||
printf 'prev_client=%s\nprev_win=%s\nscore=%s\nactive_total=%s\nsrv_pid=%s\nreturn_at=%s\nreturn_show=%s\n' \
|
||||
"$client_act" "$win_act" "$score" "$active_total" "$tmux_pid" "$return_at" "$return_show" > "$state"
|
||||
|
||||
# Format seconds to ≤3 chars
|
||||
fmt() {
|
||||
local s=$1
|
||||
if (( s < 60 )); then printf "%ds" "$s"
|
||||
elif (( s < 3600 )); then printf "%dm" $(( s / 60 ))
|
||||
elif (( s < 86400 )); then printf "%dh" $(( s / 3600 ))
|
||||
else printf "%dd" $(( s / 86400 )); fi
|
||||
}
|
||||
|
||||
# Pressure bar: block fill (8 levels)
|
||||
bar() {
|
||||
local s=$1
|
||||
if (( s >= 88 )); then printf '█'
|
||||
elif (( s >= 75 )); then printf '▇'
|
||||
elif (( s >= 63 )); then printf '▆'
|
||||
elif (( s >= 50 )); then printf '▅'
|
||||
elif (( s >= 38 )); then printf '▄'
|
||||
elif (( s >= 25 )); then printf '▃'
|
||||
elif (( s >= 13 )); then printf '▂'
|
||||
elif (( s >= 1 )); then printf '▁'
|
||||
else printf ' '; fi
|
||||
}
|
||||
|
||||
# Render: pill with margin (space + bg pill + space)
|
||||
render() {
|
||||
local bar_fg=$1 b=$2 time_fg=$3 txt=$4 len=${#4}
|
||||
local rpad=""
|
||||
(( len < 3 )) && printf -v rpad "%$(( 3 - len ))s" ""
|
||||
printf " #[bg=#1A1A1A,fg=%s] %s%s #[fg=%s]%s#[default] " "$time_fg" "$txt" "$rpad" "$bar_fg" "$b"
|
||||
}
|
||||
|
||||
b=$(bar $score)
|
||||
if [[ -n "$return_at" ]]; then
|
||||
render "colour208" "$b" "colour208" "$(fmt $return_show)"
|
||||
elif (( score > 0 )); then
|
||||
render "#2CB494" "$b" "#2CB494" "$(fmt $active_total)"
|
||||
else
|
||||
render "#808080" " " "#808080" "$(fmt $active_total)"
|
||||
fi
|
||||
Reference in New Issue
Block a user