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..26b210f 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 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 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 +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, continuous 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 continuous 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, };