diff --git a/home/.tmux.conf b/home/.tmux.conf index 7c5dea8..e1a2c9b 100644 --- a/home/.tmux.conf +++ b/home/.tmux.conf @@ -24,6 +24,11 @@ set -g display-time 4000 # Mouse set -g mouse on set -g set-clipboard on +set -g @condensed 0 +bind -T root MouseDown1StatusRight if-shell \ + "[ $(( #{mouse_x} )) -ge $(( #{client_width} - 9 )) ]" \ + "lock-session" \ + 'if-shell "[ \"$(tmux show-option -gqv @condensed)\" = 1 ]" "set -g @condensed 0" "set -g @condensed 1"' # Copy mode setw -g mode-keys vi @@ -66,7 +71,7 @@ set -g status-position bottom set -g status-interval 1 set -g status-style "fg=#7290B8,bg=default" set -g status-left "#{?client_prefix,#[fg=colour208]● ,#[fg=#3C3C3C]● }" -set -g status-right "#(~/.tmux/network.sh) #[fg=#3C3C3C] #(~/.tmux/battery.sh) #[fg=#3C3C3C] #[fg=#808080]%a %d %b #[fg=#2CB494,bg=#1A1A1A] %H:%M #[default] " +set -g status-right "#{?@condensed,,#{?#{e|>=|:#{client_width},120},#(~/.tmux/network.sh) #[fg=#3C3C3C] #(~/.tmux/battery.sh) #[fg=#3C3C3C] ,}}#{?@condensed,,#{?#{e|>=|:#{client_width},80},#[fg=#808080]%a %d %b ,}}#{?@condensed,#[fg=#3C3C3C]‹,}#[fg=#2CB494]#[bg=#1A1A1A] %H:%M #[default]#(~/.tmux/heartbeat.sh)" set -g status-right-length 80 set -g window-status-format "#{?window_last_flag, #[fg=#1A7A64]#I#[fg=default] #W , #I #W }" set -g window-status-current-format " #[fg=#085040]#I#[fg=#0A0A0A] #W " diff --git a/home/.tmux/heartbeat.sh b/home/.tmux/heartbeat.sh new file mode 100755 index 0000000..4fdcc50 --- /dev/null +++ b/home/.tmux/heartbeat.sh @@ -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