module/script: add poll-interval option

When set to a non-negative value, the script module will call the
configured script every <poll-interval> 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
This commit is contained in:
Daniel Eklöf 2021-07-04 20:23:01 +02:00
parent e4a0b375e5
commit cf41d008f8
No known key found for this signature in database
GPG key ID: 5BBD4992C116573F
3 changed files with 101 additions and 17 deletions

View file

@ -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

View file

@ -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:

View file

@ -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, <)) {
/* Weve reached the timeout, its 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,
};