diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-25 19:19:14 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-25 19:19:14 +0200 |
| commit | f8a687cbb855ed4e3aa54128624101a21ba5a8c3 (patch) | |
| tree | 14f708cf389fa406d2d873997953fb8869d4e2b7 | |
| parent | 168361ef112c61391a615d8b1f0048b79a2b7237 (diff) | |
| download | ttym-f8a687cbb855ed4e3aa54128624101a21ba5a8c3.tar.gz ttym-f8a687cbb855ed4e3aa54128624101a21ba5a8c3.zip | |
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
| -rw-r--r-- | Makefile | 13 | ||||
| -rw-r--r-- | README.md | 22 | ||||
| -rwxr-xr-x | hooks/on_done | 31 | ||||
| -rwxr-xr-x | hooks/on_quit | 28 | ||||
| -rwxr-xr-x | hooks/on_start | 26 | ||||
| -rw-r--r-- | timer.1 | 89 |
6 files changed, 197 insertions, 12 deletions
@@ -6,6 +6,7 @@ include config.mk BIN = timer SRC = ttym.c +HOOKS = hooks/on_start hooks/on_done hooks/on_quit OBJ = $(SRC:.c=.o) HDR = config.h @@ -31,7 +32,7 @@ distclean: clean dist: clean mkdir -p ttym-$(VERSION) cp -R LICENSE Makefile README.md config.def.h config.mk timer.1 ttym.c \ - patches ttym-$(VERSION) + patches hooks ttym-$(VERSION) tar -cf ttym-$(VERSION).tar ttym-$(VERSION) gzip ttym-$(VERSION).tar rm -rf ttym-$(VERSION) @@ -43,9 +44,19 @@ install: all mkdir -p $(DESTDIR)$(MANPREFIX)/man1 cp -f timer.1 $(DESTDIR)$(MANPREFIX)/man1/timer.1 chmod 644 $(DESTDIR)$(MANPREFIX)/man1/timer.1 + mkdir -p $(DESTDIR)$(PREFIX)/share/doc/ttym/hooks + cp -f $(HOOKS) $(DESTDIR)$(PREFIX)/share/doc/ttym/hooks + chmod 755 $(DESTDIR)$(PREFIX)/share/doc/ttym/hooks/on_start + chmod 755 $(DESTDIR)$(PREFIX)/share/doc/ttym/hooks/on_done + chmod 755 $(DESTDIR)$(PREFIX)/share/doc/ttym/hooks/on_quit uninstall: rm -f $(DESTDIR)$(PREFIX)/bin/$(BIN) rm -f $(DESTDIR)$(MANPREFIX)/man1/timer.1 + rm -f $(DESTDIR)$(PREFIX)/share/doc/ttym/hooks/on_start + rm -f $(DESTDIR)$(PREFIX)/share/doc/ttym/hooks/on_done + rm -f $(DESTDIR)$(PREFIX)/share/doc/ttym/hooks/on_quit + rmdir $(DESTDIR)$(PREFIX)/share/doc/ttym/hooks 2>/dev/null || true + rmdir $(DESTDIR)$(PREFIX)/share/doc/ttym 2>/dev/null || true .PHONY: all clean distclean dist install uninstall @@ -47,6 +47,28 @@ Compile-time knobs live in config.def.h. On first build the Makefile copies it to config.h; edit config.h and re-`make` to change defaults. +Hooks +----- + +With the hooks patch applied, `timer` runs executables it finds at +`~/.config/ttym/hooks/{on_start,on_done,on_quit}`, each with three +arguments: mode (`countdown`/`stopwatch`), seconds, and the `-c` +comment. Dropping in an executable file is the only thing needed to +enable one. + +Three commented examples ship in `hooks/` and are installed to +`$(PREFIX)/share/doc/ttym/hooks/` -- terminal title, desktop +notification plus sound on completion, and a TSV record of abandoned +sessions: + + mkdir -p ~/.config/ttym/hooks + cp /usr/local/share/doc/ttym/hooks/on_done ~/.config/ttym/hooks/ + +Hooks run synchronously with stdout and stderr on /dev/null, so keep +them quick and write output to a file rather than the terminal. See +`man timer` for the full contract. + + Usage ----- 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 @@ -254,17 +254,79 @@ or .BR Ctrl+C , or when a stopwatch is stopped. .PP -Each hook is executed synchronously with three arguments: -.IR mode , -.IR duration_seconds , -.IR comment . -The first is -.BR countdown -or -.BR stopwatch ; -the others come from the running session. Hooks must be marked -executable. Standard streams are redirected to -.IR /dev/null . +A hook is any executable file at that name; there is nothing to enable. +A file that is missing or not marked executable is skipped silently. +.SS Arguments +Each hook is run with exactly three positional arguments. +.TP +.B $1 +.BR countdown " or " stopwatch . +.TP +.B $2 +Seconds. The meaning depends on the hook: +.RS +.IP \(bu 2 +.BR on_start : +the requested duration of a countdown, or +.B 0 +for a stopwatch. +.IP \(bu 2 +.BR on_done : +the full duration that elapsed. +.IP \(bu 2 +.BR on_quit : +the time actually elapsed, excluding any time spent paused. A countdown +abandoned after two seconds reports +.BR 2 , +not the duration that was asked for. +.RE +.TP +.B $3 +The +.B \-c +text, or an empty string. Always passed, so +.B $3 +is never unset. +.SS Execution +Hooks run synchronously: +.B timer +forks, executes the hook and waits for it. A slow hook delays the timer +\(em +.B on_start +delays the first frame, and +.B on_done +delays the completion bell and the alert loop. Background anything slow +with +.BR & . +.PP +Standard input, output and error are all redirected to +.IR /dev/null , +so a hook cannot print to the terminal and cannot corrupt the redrawn +progress line. Write to a file, to +.BR logger (1), +or to a notification daemon instead. The exit status is discarded: a +failing hook cannot abort the timer. +.PP +Hooks are executed with +.BR execvp (3), +so a script needs a +.B #! +line like any other. +.SS Examples +Three commented, working examples ship with the source and are installed +to +.IR PREFIX/share/doc/ttym/hooks/ . +They set the terminal title, send a desktop notification and play a +sound on completion, and record abandoned sessions to a TSV file. Copy +the ones you want and edit: +.PP +.RS +.nf +mkdir \-p ~/.config/ttym/hooks +cp /usr/local/share/doc/ttym/hooks/on_done ~/.config/ttym/hooks/ +chmod +x ~/.config/ttym/hooks/on_done +.fi +.RE .SH EXIT STATUS .TP .B 0 @@ -340,6 +402,11 @@ Directory containing the .B on_quit executables described in .BR HOOKS . +.TP +.I PREFIX/share/doc/ttym/hooks/ +Commented example hooks, installed by +.BR "make install" . +Copy them into the directory above to use them. .SH EXAMPLES A twenty\-five minute work session, tagged for later stats: .PP |
