1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
|
Binary files a/timer and b/timer differ
diff -ruN a/ttym.c b/ttym.c
--- a/ttym.c 2026-08-25 17:18:15.722123437 +0200
+++ b/ttym.c 2026-08-25 17:18:15.726123510 +0200
@@ -13,6 +13,8 @@
#include <string.h>
#include <strings.h>
#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
#include <termios.h>
#include <time.h>
#include <unistd.h>
@@ -24,6 +26,7 @@
typedef struct {
int quiet; /* -q: suppress completion bell */
int silent; /* -s: no bell, no extras */
+ char comment[512]; /* passed as 3rd arg to hooks */
int alert_persist; /* alert loop until keypress; --no-persist disables */
int flash; /* reverse-video flash during alert loop */
char bar_style[32]; /* --bar STYLE */
@@ -236,6 +239,43 @@
fflush(stdout);
}
+/* ===== hooks ===== */
+
+static int
+build_hook_path(char *buf, size_t bufsz, const char *name)
+{
+ const char *home = getenv("HOME");
+ const char *xdg = getenv("XDG_CONFIG_HOME");
+ if (xdg && *xdg) snprintf(buf, bufsz, "%s/ttym/hooks/%s", xdg, name);
+ else if (home && *home) snprintf(buf, bufsz, "%s/.config/ttym/hooks/%s", home, name);
+ else return -1;
+ return 0;
+}
+
+static void
+run_hook(const char *name, const char *mode, long duration_s, const char *comment)
+{
+ char path[512];
+ if (build_hook_path(path, sizeof(path), name) < 0) return;
+ if (access(path, X_OK) != 0) return;
+ char dur[32];
+ snprintf(dur, sizeof(dur), "%ld", duration_s);
+ pid_t pid = fork();
+ if (pid == 0) {
+ int devnull = open("/dev/null", O_RDWR);
+ if (devnull >= 0) {
+ dup2(devnull, 0); dup2(devnull, 1); dup2(devnull, 2);
+ if (devnull > 2) close(devnull);
+ }
+ char *argv[] = { path, (char *)mode, dur,
+ (char *)(comment ? comment : ""), NULL };
+ execvp(argv[0], argv);
+ _exit(127);
+ } else if (pid > 0) {
+ waitpid(pid, NULL, 0);
+ }
+}
+
/* ===== alert loop =====
* Bell + poll for keypress, ~1.5s per beat; 4-min safety cap.
*/
@@ -345,6 +385,7 @@
"\n"
"DURATION: 25m | 90s | 2h | 1h30m | 01:30:00 | 25:00\n"
"FLAGS:\n"
+" -c TEXT comment, passed as 3rd arg to hooks\n"
" -q no sound\n"
" -s silent\n"
" --no-persist skip alert-until-keypress loop\n"
@@ -353,7 +394,8 @@
" -- end of options\n"
" -h help\n"
"\n"
-"controls: [space] pause/resume [q | Ctrl+C] quit\n");
+"controls: [space] pause/resume [q | Ctrl+C] quit\n"
+"hooks: ~/.config/ttym/hooks/{on_start,on_done,on_quit} (executable)\n");
}
/* ===== run loop ===== */
@@ -393,6 +435,9 @@
if (out_tty)
fputs("\033[?25l\033[?7l", stdout);
+ run_hook("on_start", stopwatch ? "stopwatch" : "countdown",
+ stopwatch ? 0 : total, cfg->comment);
+
long start_ms = now_ms();
long pause_accum = 0;
long pause_start = 0;
@@ -457,6 +502,7 @@
printf("\r\033[32mSTOP %s\033[0m\033[K\n", el);
else
printf("STOP %s\n", el);
+ run_hook("on_quit", "stopwatch", elapsed_s, cfg->comment);
} else if (user_quit) {
char el[24];
fmt_clock(elapsed_s, el, sizeof(el));
@@ -464,6 +510,7 @@
printf("\r\033[33mQUIT at %s\033[0m\033[K\n", el);
else
printf("QUIT at %s\n", el);
+ run_hook("on_quit", "countdown", elapsed_s, cfg->comment);
} else {
char tb[24];
fmt_clock(total, tb, sizeof(tb));
@@ -471,6 +518,7 @@
printf("\r\033[32mDONE %s 100%%\033[0m\033[K\n", tb);
else
printf("DONE %s 100%%\n", tb);
+ run_hook("on_done", "countdown", total, cfg->comment);
if (!cfg->silent && !cfg->quiet && out_tty) {
fputc('\a', stdout);
fflush(stdout);
@@ -520,10 +568,11 @@
argv[nargc] = NULL;
int opt;
- while ((opt = getopt(nargc, argv, "qsh")) != -1) {
+ while ((opt = getopt(nargc, argv, "qsc:h")) != -1) {
switch (opt) {
case 'q': cfg.quiet = 1; break;
case 's': cfg.silent = 1; break;
+ case 'c': snprintf(cfg.comment, sizeof(cfg.comment), "%s", optarg); break;
case 'h': usage(stdout); return 0;
default: usage(stderr); return 1;
}
|