From f8a687cbb855ed4e3aa54128624101a21ba5a8c3 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Tue, 25 Aug 2026 19:19:14 +0200 Subject: hooks: ship three commented examples, install them, document the contract Add hooks/on_start, hooks/on_done and hooks/on_quit. They are named after the hooks themselves so copying one into ~/.config/ttym/hooks/ is the entire installation step, and each carries its argument contract in a header comment. on_start sets the terminal window title to what is running on_done desktop notification plus a sound, both backgrounded, with the first available of paplay/aplay/mpv/ffplay on_quit records abandoned sessions to a TSV under XDG_STATE_HOME and restores the terminal title make install places them in PREFIX/share/doc/ttym/hooks; make uninstall removes them and both directories; make dist ships hooks/ with the executable bits intact. The man page's HOOKS section now documents what the code actually does rather than what could be assumed from the call sites: - $2 differs per hook: requested duration on on_start, full duration on on_done, and time actually elapsed -- excluding time spent paused -- on on_quit. An abandoned 25m countdown reports what really ran. - $3 is always passed, so it is never unset. - hooks block: timer forks and waitpid()s, so on_done delays the completion bell and the alert loop. Background anything slow. - stdin, stdout and stderr all go to /dev/null, which is why printing is pointless and cannot corrupt the redrawn progress line. - execvp(3) means a script needs a #! line. README gains a Hooks section. All three examples pass sh -n and shellcheck, and were tested end to end through the documented flow: make install to a staging prefix, copy into a config dir, run. Claude-Session: https://claude.ai/code/session_01APLBs8RB1FcUaC4viVkzbP --- hooks/on_done | 31 +++++++++++++++++++++++++++++++ hooks/on_quit | 28 ++++++++++++++++++++++++++++ hooks/on_start | 26 ++++++++++++++++++++++++++ 3 files changed, 85 insertions(+) create mode 100755 hooks/on_done create mode 100755 hooks/on_quit create mode 100755 hooks/on_start (limited to 'hooks') diff --git a/hooks/on_done b/hooks/on_done new file mode 100755 index 0000000..955cd13 --- /dev/null +++ b/hooks/on_done @@ -0,0 +1,31 @@ +#!/bin/sh +# ttym on_done -- runs when a countdown reaches zero. +# +# $1 mode always "countdown" (a stopwatch never completes) +# $2 seconds the full duration that elapsed +# $3 comment the -c text, or an empty string +# +# timer waits for this to exit before ringing the bell and entering the +# alert loop, so background anything slow. + +seconds=$2 comment=$3 +mins=$((seconds / 60)) + +# Desktop notification. Gives the base build what the notify patch adds. +if command -v notify-send > /dev/null 2>&1; then + notify-send -u critical "Timer" "${mins}m done${comment:+ -- $comment}" & +fi + +# A sound, first player that exists wins. Backgrounded so it never blocks. +for player in paplay aplay mpv ffplay; do + if command -v "$player" > /dev/null 2>&1; then + sound=/usr/share/sounds/freedesktop/stereo/complete.oga + [ -r "$sound" ] && "$player" "$sound" > /dev/null 2>&1 & + break + fi +done + +# Push to your phone (uncomment and set your topic): +# curl -fsS -d "${mins}m done${comment:+ -- $comment}" ntfy.sh/your-topic & + +wait diff --git a/hooks/on_quit b/hooks/on_quit new file mode 100755 index 0000000..44957e8 --- /dev/null +++ b/hooks/on_quit @@ -0,0 +1,28 @@ +#!/bin/sh +# ttym on_quit -- runs when a session ends early: q, Ctrl+C, SIGTERM, +# SIGQUIT or SIGHUP -- and whenever a stopwatch is stopped, since a +# stopwatch has no natural end. +# +# $1 mode countdown | stopwatch +# $2 seconds time actually elapsed, excluding time spent paused +# $3 comment the -c text, or an empty string +# +# A countdown abandoned after two seconds reports 2, not the duration +# you asked for. That is what makes this useful for tracking follow- +# through rather than intent. + +mode=$1 seconds=$2 comment=$3 + +log=${XDG_STATE_HOME:-$HOME/.local/state}/ttym +mkdir -p "$log" 2>/dev/null || exit 0 + +printf '%s\t%s\t%s\t%s\n' \ + "$(date +%Y-%m-%dT%H:%M:%S)" "$mode" "$seconds" "$comment" \ + >> "$log/abandoned.tsv" + +# Restore the terminal title set by on_start. +case $TERM in +xterm*|rxvt*|tmux*|screen*|alacritty|foot*) + printf '\033]0;%s\007' "${SHELL##*/}" > /dev/tty + ;; +esac diff --git a/hooks/on_start b/hooks/on_start new file mode 100755 index 0000000..441d6f1 --- /dev/null +++ b/hooks/on_start @@ -0,0 +1,26 @@ +#!/bin/sh +# ttym on_start -- runs when a session begins. +# +# $1 mode countdown | stopwatch +# $2 seconds requested duration for a countdown, 0 for a stopwatch +# $3 comment the -c text, or an empty string +# +# stdin, stdout and stderr are /dev/null: printing here goes nowhere and +# cannot corrupt the redrawn progress line. timer waits for this to exit, +# so keep it quick or background the slow part with &. + +mode=$1 seconds=$2 comment=$3 + +# Name the terminal window after what is running. +case $TERM in +xterm*|rxvt*|tmux*|screen*|alacritty|foot*) + if [ "$mode" = countdown ]; then + printf '\033]0;timer %sm %s\007' "$((seconds / 60))" "$comment" > /dev/tty + else + printf '\033]0;stopwatch %s\007' "$comment" > /dev/tty + fi + ;; +esac + +# Pause music while a countdown runs (uncomment to use): +# [ "$mode" = countdown ] && playerctl pause 2>/dev/null -- cgit v1.3