forked from external/yambar
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:
parent
e4a0b375e5
commit
cf41d008f8
3 changed files with 101 additions and 17 deletions
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue