diff options
| author | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-25 19:02:35 +0200 |
|---|---|---|
| committer | Lukasz Kasprzak <lukas@labunix.xyz> | 2026-08-25 19:02:35 +0200 |
| commit | 8f99f1650e4a7363af1d83190b4082fb72a9f73f (patch) | |
| tree | 86ed3b714c376e95f98d33d7572e76d6b43f06d3 /patches/ttym-config-file-3.0.diff | |
| parent | 2f6b29abd0d766ef477eb29c5a7bf6c1e88e9630 (diff) | |
| download | ttym-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.diff | 293 |
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; |
