aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Makefile13
-rw-r--r--README.md22
-rwxr-xr-xhooks/on_done31
-rwxr-xr-xhooks/on_quit28
-rwxr-xr-xhooks/on_start26
-rw-r--r--timer.189
6 files changed, 197 insertions, 12 deletions
diff --git a/Makefile b/Makefile
index 72b5053..ee17517 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/README.md b/README.md
index 5c0e3ea..c85527c 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/timer.1 b/timer.1
index e72566f..e3fe0bc 100644
--- a/timer.1
+++ b/timer.1
@@ -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