diff options
Diffstat (limited to 'patches/ttym-notify-2.0.diff')
| -rw-r--r-- | patches/ttym-notify-2.0.diff | 232 |
1 files changed, 232 insertions, 0 deletions
diff --git a/patches/ttym-notify-2.0.diff b/patches/ttym-notify-2.0.diff new file mode 100644 index 0000000..a80eef3 --- /dev/null +++ b/patches/ttym-notify-2.0.diff @@ -0,0 +1,232 @@ +diff -ruN a/config.def.h b/config.def.h +--- a/config.def.h 2026-05-20 12:03:30.597867999 +0200 ++++ b/config.def.h 2026-05-20 12:03:30.597867999 +0200 +@@ -3,3 +3,16 @@ + /* progress bar glyphs (ASCII default) */ + static const char *const BAR_FILL = ">"; + static const char *const BAR_EMPTY = "-"; ++ ++/* desktop notification on countdown completion. ++ * Tokens are split on whitespace; placeholders {title} and {msg} are expanded. ++ * Set NULL or "" to disable. ++ */ ++static const char *const TITLE = "Timer"; ++static const char *const NOTIFY_CMD = "notify-send -u critical {title} {msg}"; ++ ++/* sound on countdown completion. If SOUND_CMD is "" the binary autodetects ++ * a player (paplay/aplay/mpv/ffplay) and plays a default freedesktop sound. ++ * SOUND_CMD is split on whitespace; same token rules as NOTIFY_CMD. ++ */ ++static const char *const SOUND_CMD = ""; +diff -ruN a/ttym.c b/ttym.c +--- a/ttym.c 2026-05-20 12:03:30.597867999 +0200 ++++ b/ttym.c 2026-05-20 12:03:53.438340036 +0200 +@@ -12,6 +12,7 @@ + #include <stdlib.h> + #include <string.h> + #include <sys/ioctl.h> ++#include <sys/wait.h> + #include <termios.h> + #include <time.h> + #include <unistd.h> +@@ -23,6 +24,10 @@ + typedef struct { + int quiet; /* -q: suppress completion bell */ + int silent; /* -s: no bell, no extras */ ++ const char *title; ++ const char *notify_cmd; ++ const char *sound_cmd; ++ char msg[1024]; + } Config; + + static struct termios oldtios; +@@ -226,6 +231,142 @@ + fflush(stdout); + } + ++/* ===== subprocess + placeholder expansion ===== ++ * Tokens split on whitespace; {title} and {msg} expanded within each token. ++ * No shell involved — argv stays argv. ++ */ ++ ++static int ++build_argv(const char *cmd, const Config *c, char ***out) ++{ ++ char buf[2048]; ++ snprintf(buf, sizeof(buf), "%s", cmd); ++ char **argv = calloc(64, sizeof(char *)); ++ if (!argv) return -1; ++ int argc = 0; ++ char *p = buf; ++ while (*p && argc < 63) { ++ while (*p && isspace((unsigned char)*p)) p++; ++ if (!*p) break; ++ char *start = p; ++ while (*p && !isspace((unsigned char)*p)) p++; ++ if (*p) *p++ = '\0'; ++ char tok[2048]; size_t oi = 0; ++ for (char *q = start; *q && oi < sizeof(tok) - 1; ) { ++ const char *sub = NULL; size_t skip = 0; ++ if (!strncmp(q, "{msg}", 5)) { sub = c->msg; skip = 5; } ++ else if (!strncmp(q, "{title}", 7)) { sub = c->title; skip = 7; } ++ if (sub) { ++ size_t sl = strlen(sub); ++ if (oi + sl >= sizeof(tok)) sl = sizeof(tok) - 1 - oi; ++ memcpy(tok + oi, sub, sl); ++ oi += sl; q += skip; ++ } else { tok[oi++] = *q++; } ++ } ++ tok[oi] = '\0'; ++ argv[argc++] = strdup(tok); ++ } ++ argv[argc] = NULL; ++ *out = argv; ++ return argc; ++} ++ ++static void ++free_argv(char **argv) ++{ ++ if (!argv) return; ++ for (int i = 0; argv[i]; i++) free(argv[i]); ++ free(argv); ++} ++ ++static void ++spawn_bg(char *const argv[]) ++{ ++ pid_t pid = fork(); ++ if (pid == 0) { ++ setsid(); ++ int devnull = open("/dev/null", O_RDWR); ++ if (devnull >= 0) { ++ dup2(devnull, 0); dup2(devnull, 1); dup2(devnull, 2); ++ if (devnull > 2) close(devnull); ++ } ++ execvp(argv[0], argv); ++ _exit(127); ++ } ++ (void)pid; ++} ++ ++static int ++command_exists(const char *name) ++{ ++ const char *path = getenv("PATH"); ++ if (!path) return 0; ++ char buf[512]; ++ const char *p = path; ++ while (*p) { ++ const char *e = strchr(p, ':'); ++ size_t len = e ? (size_t)(e - p) : strlen(p); ++ if (len > 0 && len + strlen(name) + 2 < sizeof(buf)) { ++ snprintf(buf, sizeof(buf), "%.*s/%s", (int)len, p, name); ++ if (access(buf, X_OK) == 0) return 1; ++ } ++ if (!e) break; ++ p = e + 1; ++ } ++ return 0; ++} ++ ++static const char * ++detect_sound_player(void) ++{ ++ static const char *cands[] = { "paplay", "aplay", "mpv", "ffplay", NULL }; ++ for (int i = 0; cands[i]; i++) ++ if (command_exists(cands[i])) return cands[i]; ++ return NULL; ++} ++ ++static const char * ++default_sound_path(void) ++{ ++ static const char *paths[] = { ++ "/usr/share/sounds/freedesktop/stereo/complete.oga", ++ "/usr/share/sounds/freedesktop/stereo/alarm-clock-elapsed.oga", ++ "/usr/share/sounds/alsa/Front_Center.wav", ++ NULL ++ }; ++ for (int i = 0; paths[i]; i++) ++ if (access(paths[i], R_OK) == 0) return paths[i]; ++ return NULL; ++} ++ ++static void ++notify_and_sound(const Config *c) ++{ ++ if (c->notify_cmd && c->notify_cmd[0] && command_exists("notify-send")) { ++ char **a; ++ if (build_argv(c->notify_cmd, c, &a) > 0) { ++ spawn_bg(a); ++ free_argv(a); ++ } ++ } ++ if (!c->quiet) { ++ if (c->sound_cmd && c->sound_cmd[0]) { ++ char **a; ++ if (build_argv(c->sound_cmd, c, &a) > 0) { ++ spawn_bg(a); ++ free_argv(a); ++ } ++ } else { ++ const char *pl = detect_sound_player(); ++ const char *sp = default_sound_path(); ++ if (pl && sp) { ++ char *a[] = { (char *)pl, (char *)sp, NULL }; ++ spawn_bg(a); ++ } ++ } ++ } ++} ++ + /* ===== usage ===== + * Patches MUST update this in lockstep with new flags so -h always + * reflects the build. +@@ -236,8 +377,8 @@ + { + fprintf(f, + "usage:\n" +-" timer [FLAGS] DURATION countdown\n" +-" timer [FLAGS] stopwatch\n" ++" timer [FLAGS] DURATION [MESSAGE...] countdown\n" ++" timer [FLAGS] stopwatch\n" + "\n" + "DURATION: 25m | 90s | 2h | 1h30m | 01:30:00 | 25:00\n" + "FLAGS:\n" +@@ -370,6 +511,7 @@ + printf("\r\033[32mDONE %s 100%%%*s\033[0m\n", tb, 20, ""); + else + printf("DONE %s 100%%\n", tb); ++ if (!cfg->silent) notify_and_sound(cfg); + if (!cfg->silent && !cfg->quiet && out_tty) { + fputc('\a', stdout); + fflush(stdout); +@@ -387,6 +529,10 @@ + main(int argc, char **argv) + { + Config cfg = {0}; ++ cfg.title = TITLE; ++ cfg.notify_cmd = NOTIFY_CMD; ++ cfg.sound_cmd = SOUND_CMD; ++ snprintf(cfg.msg, sizeof(cfg.msg), "%s", "Time is up"); + + /* Pre-strip long-form flags before getopt. Stop at "--". */ + int new_argc = 0; +@@ -424,6 +570,16 @@ + free(new_argv); + return 1; + } ++ if (rem_argc > 1) { ++ size_t off = 0; ++ cfg.msg[0] = '\0'; ++ for (int i = 1; i < rem_argc && off < sizeof(cfg.msg) - 2; i++) { ++ int n = snprintf(cfg.msg + off, sizeof(cfg.msg) - off, ++ "%s%s", (i > 1 ? " " : ""), rem_argv[i]); ++ if (n < 0) break; ++ off += (size_t)n; ++ } ++ } + } + + int rc = run(total, stopwatch, &cfg); |
