tmux only raises an activity flag for windows that are not currently
selected, so a long-running command in the window you left selected --
the common case, since you switch away by moving your Hyprland focus
elsewhere rather than by selecting another tmux window -- finished
silently and the indicator never lit.
Windows now also count as waiting when they are selected in an attached
client, every client showing them is unfocused, and their window_activity
is newer than an @omarchy_unfocused_activity watermark. The watermark is
stamped by a new `track` subcommand wired to client-focus-in/out and the
existing select-window hooks, so it records where attention last was.
The hooks pass #{window_id} and #{window_activity} as arguments, which
tmux expands when the hook fires; run-shell -b would otherwise let output
arriving during the handoff be swallowed by the new watermark. The focus
hooks take index 100 to leave a user's own bindings alone.
That state has no hook of its own, so it needs polling to be noticed.
The probe therefore moves out of the indicator and into a service plugin,
alongside nightlight and battery. A bar surface exists per monitor and
each one instantiates every indicator twice, once per block, so a timer
on the indicator meant a shell-out per instance per tick -- four probe
processes every three seconds on a two-monitor machine, forever. One
service polls for the whole shell instead.
Sharing the state also fixes what per-instance polling would have papered
over: each indicator used to own its own count, so a timer-driven update
only refreshed the bar it ran on and left the other monitors stale.
Refreshes still arrive over the existing indicator broadcast, which now
coalesces into a single run no matter how many bars relay it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018CC9kSQv8ZEogaxDoeKBzL
141 lines
4.0 KiB
Bash
Executable File
141 lines
4.0 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# omarchy:summary=Show or jump to tmux windows waiting for attention
|
|
# omarchy:args=<show [--json]|focus>
|
|
# omarchy:examples=omarchy tmux alert show | omarchy tmux alert focus
|
|
|
|
set -e
|
|
|
|
# Session names cannot contain colons, and window names can, so they always
|
|
# come last when parsed.
|
|
WINDOW_FORMAT='#{window_id}:#{window_bell_flag}#{window_activity_flag}#{window_silence_flag}:#{window_activity}:#{@omarchy_unfocused_activity}:#{session_name}:#{window_index}:#{window_name}'
|
|
CLIENT_FORMAT='#{client_flags}:#{window_id}'
|
|
|
|
usage() {
|
|
echo "Usage: omarchy-tmux-alert <show [--json]|focus>" >&2
|
|
exit 1
|
|
}
|
|
|
|
# Include tmux alert flags plus output newer than the last focus transition for
|
|
# selected windows whose attached terminals are all unfocused.
|
|
alerted_windows() {
|
|
local client_flags window_id flags activity seen_activity session index name
|
|
local waiting
|
|
local -A attached_windows=()
|
|
local -A focused_windows=()
|
|
|
|
while IFS=':' read -r client_flags window_id; do
|
|
[[ -n $window_id ]] || continue
|
|
attached_windows[$window_id]=1
|
|
if [[ ,$client_flags, == *",focused,"* ]]; then
|
|
focused_windows[$window_id]=1
|
|
fi
|
|
done < <(tmux list-clients -F "$CLIENT_FORMAT" 2>/dev/null)
|
|
|
|
while IFS=':' read -r window_id flags activity seen_activity session index name; do
|
|
waiting=0
|
|
[[ $flags == *1* ]] && waiting=1
|
|
|
|
if [[ ${attached_windows[$window_id]:-} == "1" &&
|
|
${focused_windows[$window_id]:-} != "1" &&
|
|
$activity =~ ^[0-9]+$ &&
|
|
$seen_activity =~ ^[0-9]+$ ]] && ((activity > seen_activity)); then
|
|
waiting=1
|
|
fi
|
|
|
|
((waiting)) && printf '%s:%s:%s\n' "$session" "$index" "$name"
|
|
done < <(tmux list-windows -a -F "$WINDOW_FORMAT" 2>/dev/null)
|
|
}
|
|
|
|
show() {
|
|
local windows=() window session index name description=""
|
|
|
|
readarray -t windows < <(alerted_windows)
|
|
|
|
for window in "${windows[@]}"; do
|
|
IFS=':' read -r session index name <<<"$window"
|
|
[[ -n $description ]] && description+=", "
|
|
description+="$name ($session:$index)"
|
|
done
|
|
|
|
if [[ ${1:-} == "--json" ]]; then
|
|
jq -nc --argjson count "${#windows[@]}" --arg tooltip "$description" '{count: $count, tooltip: $tooltip}'
|
|
elif [[ -n $description ]]; then
|
|
echo "$description"
|
|
fi
|
|
}
|
|
|
|
focus_terminal_window() {
|
|
local pid=$1 address="" window_pid window_address
|
|
local -A windows=()
|
|
|
|
while read -r window_pid window_address; do
|
|
windows[$window_pid]=$window_address
|
|
done < <(hyprctl clients -j | jq -r '.[] | "\(.pid) \(.address)"')
|
|
|
|
# The tmux client runs somewhere below the terminal that owns the window.
|
|
while [[ -n $pid ]] && (( pid > 1 )); do
|
|
if [[ -n ${windows[$pid]:-} ]]; then
|
|
address=${windows[$pid]}
|
|
break
|
|
fi
|
|
pid=$(awk '/^PPid:/ { print $2 }' "/proc/$pid/status" 2>/dev/null)
|
|
done
|
|
|
|
[[ -n $address ]] || return 1
|
|
|
|
hyprctl dispatch "hl.dsp.focus({ window = \"address:$address\" })" >/dev/null 2>&1 ||
|
|
hyprctl dispatch focuswindow "address:$address" >/dev/null
|
|
}
|
|
|
|
most_recent_client() {
|
|
tmux list-clients "$@" -F '#{client_activity}:#{client_tty}:#{client_pid}' 2>/dev/null | sort -rn | head -n1
|
|
}
|
|
|
|
track() {
|
|
local window=${1:-} activity=${2:-}
|
|
|
|
[[ $window =~ ^@[0-9]+$ && $activity =~ ^[0-9]+$ ]] || return 0
|
|
tmux set-option -wq -t "$window" @omarchy_unfocused_activity "$activity" 2>/dev/null || return 0
|
|
omarchy-shell -q omarchy.indicators refresh
|
|
}
|
|
|
|
focus() {
|
|
local target session index client tty pid
|
|
|
|
target=$(alerted_windows | head -n1)
|
|
[[ -n $target ]] || return 0
|
|
|
|
IFS=':' read -r session index _ <<<"$target"
|
|
|
|
client=$(most_recent_client -t "$session")
|
|
[[ -n $client ]] || client=$(most_recent_client)
|
|
|
|
if [[ -z $client ]]; then
|
|
tmux select-window -t "$session:$index"
|
|
exec omarchy-launch-terminal tmux attach -t "$session"
|
|
fi
|
|
|
|
IFS=':' read -r _ tty pid <<<"$client"
|
|
tmux switch-client -c "$tty" -t "$session:$index"
|
|
omarchy-shell -q omarchy.indicators refresh
|
|
focus_terminal_window "$pid"
|
|
}
|
|
|
|
case "${1:-}" in
|
|
show)
|
|
shift
|
|
show "$@"
|
|
;;
|
|
focus)
|
|
focus
|
|
;;
|
|
track)
|
|
shift
|
|
track "$@"
|
|
;;
|
|
*)
|
|
usage
|
|
;;
|
|
esac
|