From cf41d008f8b6bce77c98f8e5782f8b5c4606c25d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 4 Jul 2021 20:23:01 +0200 Subject: [PATCH 1/2] module/script: add poll-interval option When set to a non-negative value, the script module will call the configured script every second. In this mode, the script is expected to write one tag set and then exit. This is intended to simplify the implementation of scripts that would otherwise just do a loop + sleep. Closes #67 --- CHANGELOG.md | 2 + doc/yambar-modules-script.5.scd | 24 ++++++--- modules/script.c | 92 +++++++++++++++++++++++++++++---- 3 files changed, 101 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 850a307..af8d75a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,8 @@ * Text shaping support. * Support for middle and right mouse buttons, mouse wheel and trackpad scrolling (https://codeberg.org/dnkl/yambar/issues/39). +* script: polling mode. See the new `poll-interval` option + (https://codeberg.org/dnkl/yambar/issues/67). ### Changed diff --git a/doc/yambar-modules-script.5.scd b/doc/yambar-modules-script.5.scd index 6ba1384..e4347f8 100644 --- a/doc/yambar-modules-script.5.scd +++ b/doc/yambar-modules-script.5.scd @@ -8,11 +8,16 @@ script - This module executes a user-provided script (or binary!) This module executes a user-provided script (or binary!) that writes tags on its stdout. -The script can either exit immediately after writing a set of tags, in -which case yambar will display those tags until yambar is -terminated. Or, the script can continue executing and update yambar -with new tag sets, either periodically, or when there is new data to -feed to yambar. +Scripts can be run in two modes: yambar polled, or continously. In the +yambar polled mode, the script is expected to write one set of tags +and then exit. Yambar will execute the script again after a +configurable amount of time. + +In continous mode, the script is executed once. It will typically run +in a loop, sending an updated tag set whenever it needs, or wants +to. The last tag set is used (displayed) by yambar until a new tag set +is received. This mode is intended to be used by scripts that depends +on non-polling methods to update their state. Tag sets, or _transactions_, are separated by an empty line (e.g. *echo ""*). The empty line is required to commit (update) the @@ -70,6 +75,10 @@ User defined. : list of strings : no : Arguments to pass to the script/binary. +| poll-interval +: integer +: Number of seconds between each script run. If unset, continous mode + is used. # EXAMPLES @@ -89,8 +98,9 @@ while true; do done ``` -This script will emit a single string tag, _test_, and alternate its -value between *hello* and *world* every three seconds. +This script runs in continous mode, and will emit a single string tag, +_test_, and alternate its value between *hello* and *world* every +three seconds. A corresponding yambar configuration could look like this: diff --git a/modules/script.c b/modules/script.c index a034910..189f4b6 100644 --- a/modules/script.c +++ b/modules/script.c @@ -25,6 +25,8 @@ struct private { char *path; size_t argc; char **argv; + int poll_interval; + bool aborted; struct particle *content; @@ -331,7 +333,7 @@ data_received(struct module *mod, const char *data, size_t len) static int run_loop(struct module *mod, pid_t pid, int comm_fd) { - int ret = 0; + int ret = 1; while (true) { struct pollfd fds[] = { @@ -360,19 +362,18 @@ run_loop(struct module *mod, pid_t pid, int comm_fd) data_received(mod, data, amount); } - if (fds[0].revents & POLLHUP) { + if (fds[0].revents & (POLLHUP | POLLIN)) { /* Aborted */ + struct private *m = mod->private; + m->aborted = true; + ret = 0; break; } if (fds[1].revents & POLLHUP) { /* Child's stdout closed */ LOG_DBG("script pipe closed (script terminated?)"); - break; - } - - if (fds[0].revents & POLLIN) { - /* Aborted */ + ret = 0; break; } } @@ -381,7 +382,7 @@ run_loop(struct module *mod, pid_t pid, int comm_fd) } static int -run(struct module *mod) +execute_script(struct module *mod) { struct private *m = mod->private; @@ -565,9 +566,75 @@ run(struct module *mod) return ret; } +static int +run(struct module *mod) +{ + struct private *m = mod->private; + + int ret = 1; + bool keep_going = true; + + while (keep_going && !m->aborted) { + ret = execute_script(mod); + + if (ret != 0) + break; + if (m->aborted) + break; + if (m->poll_interval < 0) + break; + + struct timeval now; + if (gettimeofday(&now, NULL) < 0) { + LOG_ERRNO("failed to get current time"); + break; + } + + struct timeval poll_interval = {.tv_sec = m->poll_interval}; + + struct timeval timeout; + timeradd(&now, &poll_interval, &timeout); + + while (true) { + struct pollfd fds[] = {{.fd = mod->abort_fd, .events = POLLIN}}; + + struct timeval now; + if (gettimeofday(&now, NULL) < 0) { + LOG_ERRNO("failed to get current time"); + keep_going = false; + break; + } + + if (!timercmp(&now, &timeout, <)) { + /* We’ve reached the timeout, it’s time to execute the script again */ + break; + } + + struct timeval time_left; + timersub(&timeout, &now, &time_left); + + int r = poll(fds, 1, time_left.tv_sec * 1000 + time_left.tv_usec / 1000); + if (r < 0) { + if (errno == EINTR) + continue; + LOG_ERRNO("failed to poll"); + keep_going = false; + break; + } + + if (r > 0) { + m->aborted = true; + break; + } + } + } + + return ret; +} + static struct module * script_new(const char *path, size_t argc, const char *const argv[static argc], - struct particle *_content) + int poll_interval, struct particle *_content) { struct private *m = calloc(1, sizeof(*m)); m->path = strdup(path); @@ -576,6 +643,7 @@ script_new(const char *path, size_t argc, const char *const argv[static argc], m->argv = malloc(argc * sizeof(m->argv[0])); for (size_t i = 0; i < argc; i++) m->argv[i] = strdup(argv[i]); + m->poll_interval = poll_interval; struct module *mod = module_common_new(); mod->private = m; @@ -592,6 +660,7 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited) const struct yml_node *path = yml_get_value(node, "path"); const struct yml_node *args = yml_get_value(node, "args"); const struct yml_node *c = yml_get_value(node, "content"); + const struct yml_node *poll_interval = yml_get_value(node, "poll-interval"); size_t argc = args != NULL ? yml_list_length(args) : 0; const char *argv[argc]; @@ -607,7 +676,9 @@ from_conf(const struct yml_node *node, struct conf_inherit inherited) } return script_new( - yml_value_as_string(path), argc, argv, conf_to_particle(c, inherited)); + yml_value_as_string(path), argc, argv, + poll_interval != NULL ? yml_value_as_int(poll_interval) : -1, + conf_to_particle(c, inherited)); } static bool @@ -637,6 +708,7 @@ verify_conf(keychain_t *chain, const struct yml_node *node) static const struct attr_info attrs[] = { {"path", true, &conf_verify_path}, {"args", false, &conf_verify_args}, + {"poll-interval", false, &conf_verify_int}, MODULE_COMMON_ATTRS, }; From 0ddabacc7785011bda86b91787a81201028f0971 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Ekl=C3=B6f?= Date: Sun, 11 Jul 2021 15:27:57 +0200 Subject: [PATCH 2/2] doc: yambar-modules-script: codespell fixes --- doc/yambar-modules-script.5.scd | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/yambar-modules-script.5.scd b/doc/yambar-modules-script.5.scd index e4347f8..26b210f 100644 --- a/doc/yambar-modules-script.5.scd +++ b/doc/yambar-modules-script.5.scd @@ -8,12 +8,12 @@ script - This module executes a user-provided script (or binary!) This module executes a user-provided script (or binary!) that writes tags on its stdout. -Scripts can be run in two modes: yambar polled, or continously. In the +Scripts can be run in two modes: yambar polled, or continuously. In the yambar polled mode, the script is expected to write one set of tags and then exit. Yambar will execute the script again after a configurable amount of time. -In continous mode, the script is executed once. It will typically run +In continuous mode, the script is executed once. It will typically run in a loop, sending an updated tag set whenever it needs, or wants to. The last tag set is used (displayed) by yambar until a new tag set is received. This mode is intended to be used by scripts that depends @@ -77,7 +77,7 @@ User defined. : Arguments to pass to the script/binary. | poll-interval : integer -: Number of seconds between each script run. If unset, continous mode +: Number of seconds between each script run. If unset, continuous mode is used. # EXAMPLES @@ -98,7 +98,7 @@ while true; do done ``` -This script runs in continous mode, and will emit a single string tag, +This script runs in continuous mode, and will emit a single string tag, _test_, and alternate its value between *hello* and *world* every three seconds.