aboutsummaryrefslogtreecommitdiff
path: root/patches/ttym-notify-2.0.diff
diff options
context:
space:
mode:
authorLukasz Kasprzak <lukas@labunix.xyz>2026-08-25 19:02:35 +0200
committerLukasz Kasprzak <lukas@labunix.xyz>2026-08-25 19:02:35 +0200
commit8f99f1650e4a7363af1d83190b4082fb72a9f73f (patch)
tree86ed3b714c376e95f98d33d7572e76d6b43f06d3 /patches/ttym-notify-2.0.diff
parent2f6b29abd0d766ef477eb29c5a7bf6c1e88e9630 (diff)
downloadttym-8f99f1650e4a7363af1d83190b4082fb72a9f73f.tar.gz
ttym-8f99f1650e4a7363af1d83190b4082fb72a9f73f.zip
ttym 3.0: fix terminal and config bugs, fold terminal-only patches into base
Correctness fixes: - restore the terminal on SIGQUIT and SIGHUP. atexit never runs on an uncaught fatal signal, so Ctrl+\ left the cursor hidden and autowrap disabled. - drop the unconditional newline in restore_tty. Every run emitted a stray blank line, including piped output. - treat EINTR as an interruption rather than a poll() failure, which delayed Ctrl+C by up to one 100ms frame. config-file patch: - make comment stripping quote-aware so a value may contain '#', and strip one matched pair of quotes. bar_fill = "#" was silently dropped, despite '#' being the glyph of the hash style this build ships. - resolve bar_fill/bar_empty inside resolve_bar and clear them on --bar, so command-line flags beat the config file as documented. Previously config glyphs silently overrode --bar. - report unknown keys, unparseable booleans and malformed lines on stderr. flash = enabled silently meant off. - never write to disk. --dump-config prints a commented template on stdout instead of the binary creating ~/.config/ttym/config on first run. Patch set: - fold persist-alert, flash and bar-styles into the base. The base is now terminal-only: it opens /dev/tty, draws and exits, with no file writes and no subprocesses. Everything still in patches/ changes what the program touches. - seven patches become four; config-file's prerequisites drop from six patches to one (notify), so it is regenerable mechanically. - regenerate every patch. All apply with zero fuzz and compile. Source: - rewrite argv in place rather than into a malloc'd copy (C99 5.1.2.2.1), removing die(), four free() calls and <stdarg.h>. - fold the duplicated elapsed-time expression into elapsed(). - drop dead code: have_any, (void)cfg, and an unreachable branch in parse_duration. - replace hand-counted padding with \033[K. Docs and build: - timer.1: -c is the third hook argument, not the fourth. Add EXIT STATUS (a stopwatch always exits 130 -- it has no natural end) and ENVIRONMENT. Document config precedence and quoting. - wire VERSION into a dist target; it was defined and never used. - track .gitignore, which previously ignored itself, so a fresh clone had no ignore rules. Claude-Session: https://claude.ai/code/session_01APLBs8RB1FcUaC4viVkzbP
Diffstat (limited to 'patches/ttym-notify-2.0.diff')
-rw-r--r--patches/ttym-notify-2.0.diff232
1 files changed, 0 insertions, 232 deletions
diff --git a/patches/ttym-notify-2.0.diff b/patches/ttym-notify-2.0.diff
deleted file mode 100644
index a80eef3..0000000
--- a/patches/ttym-notify-2.0.diff
+++ /dev/null
@@ -1,232 +0,0 @@
-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);