#!/bin/sh # monitor-activity, monitor-bell and monitor-silence: both the # alert-activity, alert-bell and alert-silence hooks and the winlink alert # flags (window_activity_flag, window_bell_flag, window_silence_flag and # the #, !, ~ characters in window_flags). The sessions are detached so # alert flags are set even on the current window; the *-action options # still decide whether the hooks fire. Panes run cat: activity is # generated by the tty echo of send-keys and a bell by sending a BEL and # newline for cat to write back. PATH=/bin:/usr/bin TERM=screen LC_ALL=C.UTF-8 LANG=C.UTF-8 export TERM LC_ALL LANG [ -z "$TEST_TMUX" ] && TEST_TMUX=$(readlink -f ../tmux) OUT=$(mktemp -d) TMUX_TMPDIR="$OUT" export TMUX_TMPDIR TMUX="$TEST_TMUX -LtestA$$ -f/dev/null" fail() { echo "$*" >&2 $TMUX kill-server 2>/dev/null || true rm -rf "$OUT" exit 1 } cleanup() { $TMUX kill-server 2>/dev/null || true rm -rf "$OUT" } trap cleanup EXIT wait_for() { option=$1 expected=$2 i=0 while [ $i -lt 30 ]; do value=$($TMUX show -gqv "$option" 2>/dev/null || true) [ "$value" = "$expected" ] && return 0 i=$((i + 1)) sleep 0.5 done fail "expected $option to be '$expected' but got '$value'" } assert_unchanged() { option=$1 expected=$2 i=0 while [ $i -lt 10 ]; do value=$($TMUX show -gqv "$option" 2>/dev/null || true) [ "$value" = "$expected" ] || \ fail "expected $option to remain '$expected' but got '$value'" i=$((i + 1)) sleep 0.5 done } wait_for_fmt() { target=$1 fmt=$2 expected=$3 i=0 while [ $i -lt 30 ]; do value=$($TMUX display -pt "$target" "$fmt" 2>/dev/null || true) [ "$value" = "$expected" ] && return 0 i=$((i + 1)) sleep 0.5 done fail "expected $fmt for $target to be '$expected' but got '$value'" } assert_fmt_unchanged() { target=$1 fmt=$2 expected=$3 i=0 while [ $i -lt 10 ]; do value=$($TMUX display -pt "$target" "$fmt" 2>/dev/null || true) [ "$value" = "$expected" ] || \ fail "expected $fmt for $target to remain '$expected' but got '$value'" i=$((i + 1)) sleep 0.5 done } flags_have() { target=$1 char=$2 flags=$($TMUX display -pt "$target" '#{window_flags}') || fail "display window_flags failed" case "$flags" in *"$char"*) ;; *) fail "expected '$char' in window_flags for $target: '$flags'" ;; esac } flags_lack() { target=$1 char=$2 flags=$($TMUX display -pt "$target" '#{window_flags}') || fail "display window_flags failed" case "$flags" in *"$char"*) fail "unexpected '$char' in window_flags for $target: '$flags'" ;; esac } bell() { $TMUX send-keys -t "$1" -H 07 0a || fail "send-keys bell failed" } activity() { $TMUX send-keys -t "$1" -l x || fail "send-keys activity failed" } $TMUX new -d -s mon -n w0 cat || fail "new-session mon failed" $TMUX set -g @log '' || fail "set @log failed" $TMUX set-hook -g alert-bell \ 'set -gF @log "#{@log}|alert-bell:#{hook_session_name}:#{hook_window_name}"' || fail "set-hook alert-bell failed" $TMUX set-hook -g alert-activity \ 'set -gF @log "#{@log}|alert-activity:#{hook_session_name}:#{hook_window_name}"' || fail "set-hook alert-activity failed" $TMUX set-hook -g alert-silence \ 'set -gF @log "#{@log}|alert-silence:#{hook_session_name}:#{hook_window_name}"' || fail "set-hook alert-silence failed" # A bell in a non-current window (monitor-bell defaults to on) fires # alert-bell and sets the bell flag, shown as ! in window_flags. $TMUX neww -d -t mon: -n bellw cat || fail "new-window bellw failed" assert_fmt_unchanged mon:bellw '#{window_bell_flag}' 0 flags_lack mon:bellw '!' bell mon:bellw wait_for @log '|alert-bell:mon:bellw' wait_for_fmt mon:bellw '#{window_bell_flag}' 1 flags_have mon:bellw '!' # Bells are not deduplicated: a second bell fires the hook again even # though the flag is still set. bell mon:bellw wait_for @log '|alert-bell:mon:bellw|alert-bell:mon:bellw' # Selecting the window clears the alert flags. $TMUX selectw -t mon:bellw || fail "select-window bellw failed" wait_for_fmt mon:bellw '#{window_bell_flag}' 0 flags_lack mon:bellw '!' $TMUX selectw -t mon:w0 || fail "select-window w0 failed" # A bell in the current window of a detached session still sets the flag # and bell-action any (the default) fires the hook for the current window. $TMUX set -g @log '' || fail "reset @log failed" bell mon:w0 wait_for @log '|alert-bell:mon:w0' wait_for_fmt mon:w0 '#{window_bell_flag}' 1 $TMUX selectw -t mon:bellw || fail "select-window bellw failed" $TMUX selectw -t mon:w0 || fail "select-window w0 failed" wait_for_fmt mon:w0 '#{window_bell_flag}' 0 # bell-action none: the flag is still set but the hook does not fire. $TMUX set -t mon bell-action none || fail "set bell-action none failed" $TMUX set -g @log '' || fail "reset @log failed" bell mon:bellw wait_for_fmt mon:bellw '#{window_bell_flag}' 1 assert_unchanged @log '' $TMUX set -ut mon bell-action || fail "unset bell-action failed" $TMUX selectw -t mon:bellw || fail "select-window bellw failed" $TMUX selectw -t mon:w0 || fail "select-window w0 failed" # monitor-bell off: neither the flag nor the hook. $TMUX set -wt mon:bellw monitor-bell off || fail "set monitor-bell failed" $TMUX set -g @log '' || fail "reset @log failed" bell mon:bellw assert_fmt_unchanged mon:bellw '#{window_bell_flag}' 0 assert_unchanged @log '' # monitor-activity defaults to off: output sets neither flag nor hook. $TMUX neww -d -t mon: -n actw cat || fail "new-window actw failed" $TMUX set -g @log '' || fail "reset @log failed" activity mon:actw assert_fmt_unchanged mon:actw '#{window_activity_flag}' 0 assert_unchanged @log '' # With monitor-activity on, output fires alert-activity and sets the # activity flag, shown as # in window_flags. $TMUX set -wt mon:actw monitor-activity on || fail "set monitor-activity failed" activity mon:actw wait_for @log '|alert-activity:mon:actw' wait_for_fmt mon:actw '#{window_activity_flag}' 1 flags_have mon:actw '#' # While the flag is set further activity does not fire the hook again. $TMUX set -g @log '' || fail "reset @log failed" activity mon:actw assert_unchanged @log '' # On a detached session selecting the window does not clear the activity # flag: session_set_current treats the selection itself as activity and # the flag is raised again at once because the session is not attached. # No hook fires as activity-action other does not apply to the current # window. $TMUX selectw -t mon:actw || fail "select-window actw failed" assert_fmt_unchanged mon:actw '#{window_activity_flag}' 1 $TMUX selectw -t mon:w0 || fail "select-window w0 failed" assert_fmt_unchanged mon:actw '#{window_activity_flag}' 1 assert_unchanged @log '' # With a client attached, selecting the window does clear the activity # flag and it stays clear for the current window. mkfifo "$OUT/fifo" || fail "mkfifo failed" $TMUX -C attach -t mon <"$OUT/fifo" >"$OUT/control.out" 2>&1 & exec 3>"$OUT/fifo" wait_for_fmt mon: '#{session_attached}' 1 $TMUX selectw -t mon:actw || fail "select-window actw failed" wait_for_fmt mon:actw '#{window_activity_flag}' 0 flags_lack mon:actw '#' $TMUX selectw -t mon:w0 || fail "select-window w0 failed" exec 3>&- wait_for_fmt mon: '#{session_attached}' 0 # Once the flag is clear the alert is re-armed. activity mon:actw wait_for @log '|alert-activity:mon:actw' # Activity in the current window of a detached session sets the flag but # activity-action other (the default) does not fire the hook. $TMUX set -wt mon:w0 monitor-activity on || fail "set monitor-activity w0 failed" $TMUX set -g @log '' || fail "reset @log failed" activity mon:w0 wait_for_fmt mon:w0 '#{window_activity_flag}' 1 assert_unchanged @log '' $TMUX set -wut mon:w0 monitor-activity || fail "unset monitor-activity w0 failed" # Creating a non-current window counts as activity when monitor-activity is # enabled in the window defaults. $TMUX set -wg monitor-activity on || fail "set global monitor-activity failed" $TMUX set -g @log '' || fail "reset @log failed" $TMUX neww -d -t mon: -n newact cat || fail "new-window newact failed" wait_for @log '|alert-activity:mon:newact' wait_for_fmt mon:newact '#{window_activity_flag}' 1 flags_have mon:newact '#' $TMUX set -wug monitor-activity || fail "unset global monitor-activity failed" $TMUX selectw -t mon:newact || fail "select-window newact failed" $TMUX selectw -t mon:w0 || fail "select-window w0 failed" # monitor-silence: a quiet window fires alert-silence after the interval # and sets the silence flag, shown as ~ in window_flags. The hook fires # only once while the flag remains set even though the timer keeps # running. $TMUX neww -d -t mon: -n silw cat || fail "new-window silw failed" assert_fmt_unchanged mon:silw '#{window_silence_flag}' 0 $TMUX set -g @log '' || fail "reset @log failed" $TMUX set -wt mon:silw monitor-silence 1 || fail "set monitor-silence failed" wait_for @log '|alert-silence:mon:silw' wait_for_fmt mon:silw '#{window_silence_flag}' 1 flags_have mon:silw '~' assert_unchanged @log '|alert-silence:mon:silw' $TMUX set -wt mon:silw monitor-silence 0 || fail "reset monitor-silence failed" $TMUX selectw -t mon:silw || fail "select-window silw failed" wait_for_fmt mon:silw '#{window_silence_flag}' 0 flags_lack mon:silw '~' $TMUX selectw -t mon:w0 || fail "select-window w0 failed" # Activity resets the silence timer: output before the original deadline # prevents an alert until a full quiet interval has passed afterwards. $TMUX neww -d -t mon: -n silreset cat || fail "new-window silreset failed" $TMUX set -g @log '' || fail "reset @log failed" $TMUX set -wt mon:silreset monitor-silence 10 || fail "set monitor-silence silreset failed" sleep 3 activity mon:silreset assert_unchanged @log '' wait_for @log '|alert-silence:mon:silreset' wait_for_fmt mon:silreset '#{window_silence_flag}' 1 $TMUX set -wt mon:silreset monitor-silence 0 || fail "reset monitor-silence silreset failed" $TMUX selectw -t mon:silreset || fail "select-window silreset failed" $TMUX selectw -t mon:w0 || fail "select-window w0 failed" # A window linked into two sessions: the hook fires once per winlink and # the flag is set in both sessions. Selecting the window in one session # clears the flags on every winlink. $TMUX new -d -s mon2 || fail "new-session mon2 failed" $TMUX neww -d -t mon: -n shw cat || fail "new-window shw failed" $TMUX link-window -d -s mon:shw -t mon2:5 || fail "link-window failed" $TMUX set -g @log '' || fail "reset @log failed" bell mon:shw wait_for @log '|alert-bell:mon:shw|alert-bell:mon2:shw' wait_for_fmt mon:shw '#{window_bell_flag}' 1 wait_for_fmt mon2:5 '#{window_bell_flag}' 1 $TMUX selectw -t mon2:5 || fail "select-window mon2:5 failed" wait_for_fmt mon2:5 '#{window_bell_flag}' 0 wait_for_fmt mon:shw '#{window_bell_flag}' 0 exit 0