aboutsummaryrefslogtreecommitdiff
path: root/patches/ttym-config-file-3.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-config-file-3.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-config-file-3.0.diff')
-rw-r--r--patches/ttym-config-file-3.0.diff293
1 files changed, 293 insertions, 0 deletions
diff --git a/patches/ttym-config-file-3.0.diff b/patches/ttym-config-file-3.0.diff
new file mode 100644
index 0000000..031f105
--- /dev/null
+++ b/patches/ttym-config-file-3.0.diff
@@ -0,0 +1,293 @@
+diff -ruN a/config.def.h b/config.def.h
+--- a/config.def.h 2026-08-25 17:18:15.738123728 +0200
++++ b/config.def.h 2026-08-25 17:18:15.738123728 +0200
+@@ -1,5 +1,10 @@
+ /* See LICENSE file for copyright and license details. */
+
++/* Defaults below are compile-time. Many can be overridden at runtime by
++ * /etc/ttym.conf and ~/.config/ttym/config; command-line flags override
++ * both. Run `timer --dump-config` for a commented template. See timer(1).
++ */
++
+ /* progress bar glyphs (ASCII default) */
+ static const char *const BAR_FILL = ">";
+ static const char *const BAR_EMPTY = "-";
+diff -ruN a/config.h b/config.h
+--- a/config.h 2026-08-25 17:18:15.738123728 +0200
++++ b/config.h 2026-08-25 17:18:15.738123728 +0200
+@@ -1,5 +1,10 @@
+ /* See LICENSE file for copyright and license details. */
+
++/* Defaults below are compile-time. Many can be overridden at runtime by
++ * /etc/ttym.conf and ~/.config/ttym/config; command-line flags override
++ * both. Run `timer --dump-config` for a commented template. See timer(1).
++ */
++
+ /* progress bar glyphs (ASCII default) */
+ static const char *const BAR_FILL = ">";
+ static const char *const BAR_EMPTY = "-";
+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.738123728 +0200
++++ b/ttym.c 2026-08-25 17:18:15.738123728 +0200
+@@ -25,13 +25,15 @@
+ 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 title[256]; /* runtime config may override */
++ char notify_cmd[512]; /* runtime config may override */
++ char sound_cmd[512]; /* runtime config may override */
+ char msg[1024];
+ int alert_persist; /* alert loop until keypress; --no-persist disables */
+ int flash; /* reverse-video flash during alert loop */
+ char bar_style[32]; /* --bar STYLE */
++ char bar_fill[16]; /* runtime config: explicit glyph, beats bar_style */
++ char bar_empty[16]; /* runtime config: explicit glyph, beats bar_style */
+ } Config;
+
+ static struct termios oldtios;
+@@ -352,7 +354,7 @@
+ static void
+ notify_and_sound(const Config *c)
+ {
+- if (c->notify_cmd && c->notify_cmd[0] && command_exists("notify-send")) {
++ if (c->notify_cmd[0] && command_exists("notify-send")) {
+ char **a;
+ if (build_argv(c->notify_cmd, c, &a) > 0) {
+ spawn_bg(a);
+@@ -360,7 +362,7 @@
+ }
+ }
+ if (!c->quiet) {
+- if (c->sound_cmd && c->sound_cmd[0]) {
++ if (c->sound_cmd[0]) {
+ char **a;
+ if (build_argv(c->sound_cmd, c, &a) > 0) {
+ spawn_bg(a);
+@@ -469,6 +471,178 @@
+ }
+ *fill = s ? s->fill : BAR_FILL;
+ *empty = s ? s->empty : BAR_EMPTY;
++ /* explicit glyphs from the config file beat the named style; --bar
++ * clears them so a flag always wins over the file. */
++ if (c->bar_fill[0]) *fill = c->bar_fill;
++ if (c->bar_empty[0]) *empty = c->bar_empty;
++}
++
++/* ===== runtime config file =====
++ * key = value lines. '#' starts a comment only at the start of a line or
++ * after whitespace, and never inside quotes. Quote a value to protect
++ * surrounding space or a leading '#'; one matched pair is stripped. Unknown keys and
++ * unparseable values are reported on stderr rather than silently dropped.
++ * Precedence: command-line flags > ~/.config/ttym/config > /etc/ttym.conf
++ * > the compile-time defaults in config.h.
++ */
++
++static int
++build_cfg_path(char *buf, size_t bufsz, const char *name)
++{
++ const char *xdg = getenv("XDG_CONFIG_HOME");
++ const char *home = getenv("HOME");
++ if (xdg && *xdg) snprintf(buf, bufsz, "%s/ttym/%s", xdg, name);
++ else if (home && *home) snprintf(buf, bufsz, "%s/.config/ttym/%s", home, name);
++ else return -1;
++ return 0;
++}
++
++static void
++trim(char *s)
++{
++ char *p = s;
++ while (*p && isspace((unsigned char)*p)) p++;
++ if (p != s) memmove(s, p, strlen(p) + 1);
++ size_t n = strlen(s);
++ while (n > 0 && isspace((unsigned char)s[n - 1])) s[--n] = '\0';
++}
++
++static void
++strip_comment(char *s)
++{
++ int q = 0;
++ for (char *p = s; *p; p++) {
++ if (q) {
++ if (*p == q) q = 0;
++ } else if (*p == '"' || *p == '\'') {
++ q = *p;
++ } else if (*p == '#' && (p == s || isspace((unsigned char)p[-1]))) {
++ *p = '\0';
++ return;
++ }
++ }
++}
++
++static void
++unquote(char *s)
++{
++ size_t n = strlen(s);
++ if (n >= 2 && (s[0] == '"' || s[0] == '\'') && s[n - 1] == s[0]) {
++ memmove(s, s + 1, n - 2);
++ s[n - 2] = '\0';
++ }
++}
++
++static int
++cfg_bool(const char *path, int ln, const char *k, const char *v, int cur)
++{
++ if (!strcasecmp(v, "true") || !strcasecmp(v, "yes") ||
++ !strcasecmp(v, "on") || !strcmp(v, "1")) return 1;
++ if (!strcasecmp(v, "false") || !strcasecmp(v, "no") ||
++ !strcasecmp(v, "off") || !strcmp(v, "0")) return 0;
++ fprintf(stderr, "%s:%d: %s: expected on or off, got: %s\n", path, ln, k, v);
++ return cur;
++}
++
++static void
++load_config_file(Config *c, const char *path)
++{
++ FILE *f = fopen(path, "r");
++ if (!f) return;
++ char line[1024];
++ int ln = 0;
++ while (fgets(line, sizeof(line), f)) {
++ ln++;
++ strip_comment(line);
++ char *eq = strchr(line, '=');
++ if (!eq) {
++ trim(line);
++ if (*line)
++ fprintf(stderr, "%s:%d: not key = value: %s\n", path, ln, line);
++ continue;
++ }
++ *eq = '\0';
++ char *k = line, *v = eq + 1;
++ trim(k); trim(v); unquote(v);
++ if (!*k) {
++ fprintf(stderr, "%s:%d: missing key\n", path, ln);
++ continue;
++ }
++ if (!strcmp(k, "bar_fill")) snprintf(c->bar_fill, sizeof(c->bar_fill), "%s", v);
++ else if (!strcmp(k, "bar_empty")) snprintf(c->bar_empty, sizeof(c->bar_empty), "%s", v);
++ else if (!strcmp(k, "title")) snprintf(c->title, sizeof(c->title), "%s", v);
++ else if (!strcmp(k, "notify_cmd")) snprintf(c->notify_cmd, sizeof(c->notify_cmd), "%s", v);
++ else if (!strcmp(k, "sound_cmd")) snprintf(c->sound_cmd, sizeof(c->sound_cmd), "%s", v);
++ else if (!strcmp(k, "flash")) c->flash = cfg_bool(path, ln, k, v, c->flash);
++ else if (!strcmp(k, "alert_persist")) c->alert_persist = cfg_bool(path, ln, k, v, c->alert_persist);
++ else if (!strcmp(k, "bar_style")) {
++ if (find_bar_style(v))
++ snprintf(c->bar_style, sizeof(c->bar_style), "%s", v);
++ else
++ fprintf(stderr, "%s:%d: unknown bar style: %s\n", path, ln, v);
++ }
++ else fprintf(stderr, "%s:%d: unknown key: %s\n", path, ln, k);
++ }
++ fclose(f);
++}
++
++static void
++load_config(Config *c)
++{
++ load_config_file(c, "/etc/ttym.conf");
++ char path[512];
++ if (build_cfg_path(path, sizeof(path), "config") == 0)
++ load_config_file(c, path);
++}
++
++/* Written to stdout by --dump-config; ttym never creates this file itself.
++ * timer --dump-config > ~/.config/ttym/config
++ */
++static void
++dump_config(FILE *f)
++{
++ fputs(
++"# ttym configuration\n"
++"#\n"
++"# Read from /etc/ttym.conf first, then ~/.config/ttym/config\n"
++"# (honours $XDG_CONFIG_HOME). Command-line flags override both; an absent\n"
++"# or commented-out key keeps the compile-time default from config.h.\n"
++"#\n"
++"# Syntax: key = value. '#' begins a comment at the start of a line or after\n"
++"# whitespace. Quote a value to keep surrounding spaces or a leading '#';\n"
++"# one matched pair of quotes is stripped.\n"
++"# ---------------------------------------------------------------------------\n"
++"\n"
++"# Progress-bar glyphs. Set BOTH or NEITHER -- mismatched display widths\n"
++"# break bar alignment. Multi-byte UTF-8 is fine. These beat bar_style,\n"
++"# but --bar on the command line beats them.\n"
++"# bar_fill = > # a '#' glyph must be quoted: bar_fill = \"#\"\n"
++"# bar_empty = -\n"
++"\n"
++"# Named progress-bar style. One of:\n"
++"# unicode ascii hash dots line block arrow minimal\n"
++"# Leave unset to auto-detect (unicode on a UTF-8 locale, otherwise ascii).\n"
++"# bar_style = unicode\n"
++"\n"
++"# Reverse-video flash during the completion alert. on | off\n"
++"# flash = on\n"
++"\n"
++"# Persistent alert: keep ringing the bell until a key is pressed\n"
++"# (foreground runs only). on | off\n"
++"# alert_persist = on\n"
++"\n"
++"# Desktop-notification title.\n"
++"# title = Timer\n"
++"\n"
++"# Desktop-notification command run when a countdown finishes. Tokens are\n"
++"# split on whitespace; {title}/{msg} placeholders expand. Empty disables.\n"
++"# notify_cmd = notify-send -u critical {title} {msg}\n"
++"\n"
++"# Sound played when a countdown finishes. Leave unset to auto-detect a\n"
++"# player (paplay/aplay/mpv/ffplay) and play a default system sound.\n"
++"# Tokens are split on whitespace; {title}/{msg} placeholders expand.\n"
++"# sound_cmd = paplay /usr/share/sounds/freedesktop/stereo/complete.oga\n",
++ f);
+ }
+
+ /* ===== usage =====
+@@ -491,10 +665,12 @@
+ " --no-persist skip alert-until-keypress loop\n"
+ " --flash on|off terminal flash during alert loop\n"
+ " --bar STYLE bar style: unicode|ascii|hash|dots|line|block|arrow|minimal\n"
++" --dump-config print a commented config template on stdout and exit\n"
+ " -- 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"
++"config: /etc/ttym.conf, then ~/.config/ttym/config (--dump-config)\n");
+ }
+
+ /* ===== run loop ===== */
+@@ -633,12 +809,13 @@
+ main(int argc, char **argv)
+ {
+ Config cfg = {0};
+- cfg.title = TITLE;
+- cfg.notify_cmd = NOTIFY_CMD;
+- cfg.sound_cmd = SOUND_CMD;
++ snprintf(cfg.title, sizeof(cfg.title), "%s", TITLE ? TITLE : "");
++ snprintf(cfg.notify_cmd, sizeof(cfg.notify_cmd), "%s", NOTIFY_CMD ? NOTIFY_CMD : "");
++ snprintf(cfg.sound_cmd, sizeof(cfg.sound_cmd), "%s", SOUND_CMD ? SOUND_CMD : "");
+ snprintf(cfg.msg, sizeof(cfg.msg), "%s", "Time is up");
+ cfg.alert_persist = ALERT_PERSIST;
+ cfg.flash = FLASH;
++ load_config(&cfg);
+
+ /* Pre-strip long-form flags before getopt. Stop at "--". */
+ int nargc = 0, past_dd = 0;
+@@ -659,8 +836,13 @@
+ }
+ if (!past_dd && !strcmp(argv[i], "--bar") && i + 1 < argc) {
+ snprintf(cfg.bar_style, sizeof(cfg.bar_style), "%s", argv[++i]);
++ cfg.bar_fill[0] = cfg.bar_empty[0] = '\0';
+ continue;
+ }
++ if (!past_dd && !strcmp(argv[i], "--dump-config")) {
++ dump_config(stdout);
++ return 0;
++ }
+ argv[nargc++] = argv[i];
+ }
+ argv[nargc] = NULL;