diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a95f16..e42b0fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ * YAML parsing error messages being replaced with a generic _“unknown error”_. * Memory leak when a YAML parsing error was encountered. +* clock: update every second when necessary + (https://codeberg.org/dnkl/yambar/issues/12). ### Security diff --git a/modules/clock.c b/modules/clock.c index 0599e9a..6c0cb56 100644 --- a/modules/clock.c +++ b/modules/clock.c @@ -4,7 +4,11 @@ #include #include +#include +#define LOG_MODULE "clock" +#define LOG_ENABLE_DBG 0 +#include "../log.h" #include "../bar/bar.h" #include "../config.h" #include "../config-verify.h" @@ -12,6 +16,10 @@ struct private { struct particle *label; + enum { + UPDATE_GRANULARITY_SECONDS, + UPDATE_GRANULARITY_MINUTES, + } update_granularity; char *date_format; char *time_format; }; @@ -55,20 +63,56 @@ content(struct module *mod) static int run(struct module *mod) { + const struct private *m = mod->private; const struct bar *bar = mod->bar; bar->refresh(bar); while (true) { - time_t now = time(NULL); - time_t now_no_secs = now / 60 * 60; - assert(now_no_secs % 60 == 0); + struct timespec _now; + clock_gettime(CLOCK_REALTIME, &_now); - time_t next_min = now_no_secs + 60; - time_t timeout = next_min - now; - assert(timeout >= 0 && timeout <= 60); + const struct timeval now = { + .tv_sec = _now.tv_sec, + .tv_usec = _now.tv_nsec / 1000, + }; + + int timeout_ms; + + switch (m->update_granularity) { + case UPDATE_GRANULARITY_SECONDS: { + const struct timeval next_second = { + .tv_sec = now.tv_sec + 1, + .tv_usec = 0}; + + struct timeval _timeout; + timersub(&next_second, &now, &_timeout); + + assert(_timeout.tv_sec == 0 || + (_timeout.tv_sec == 1 && _timeout.tv_usec == 0)); + timeout_ms = _timeout.tv_usec / 1000; + break; + } + + case UPDATE_GRANULARITY_MINUTES: { + const struct timeval next_minute = { + .tv_sec = now.tv_sec / 60 * 60 + 60, + .tv_usec = 0, + }; + + struct timeval _timeout; + timersub(&next_minute, &now, &_timeout); + timeout_ms = _timeout.tv_sec * 1000 + _timeout.tv_usec / 1000; + } + } + + /* Add 1ms to account for rounding errors */ + timeout_ms++; + + LOG_DBG("now: %lds %ldµs -> timeout: %dms", + now.tv_sec, now.tv_usec, timeout_ms); struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}}; - poll(fds, 1, timeout * 1000); + poll(fds, 1, timeout_ms); if (fds[0].revents & POLLIN) break; @@ -87,6 +131,31 @@ clock_new(struct particle *label, const char *date_format, const char *time_form m->date_format = strdup(date_format); m->time_format = strdup(time_format); + static const char *const seconds_formatters[] = { + "%c", + "%s", + "%S", + "%T", + "%r", + "%X", + }; + + m->update_granularity = UPDATE_GRANULARITY_MINUTES; + + for (size_t i = 0; + i < sizeof(seconds_formatters) / sizeof(seconds_formatters[0]); + i++) + { + if (strstr(time_format, seconds_formatters[i]) != NULL) { + m->update_granularity = UPDATE_GRANULARITY_SECONDS; + break; + } + } + + LOG_DBG("using %s update granularity", + (m->update_granularity == UPDATE_GRANULARITY_MINUTES + ? "minutes" : "seconds")); + struct module *mod = module_common_new(); mod->private = m; mod->run = &run;