From 39435840461bb588ecbcbe93b56d2fc1b6e76d17 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 7 Jul 2026 12:45:32 +0100 Subject: [PATCH] Add wait-for -F and -v for events. --- cmd-wait-for.c | 73 ++++++++++++++++++++++++++++++++----------- events-payload.c | 35 +++++++++++++++++++++ events.c | 2 ++ hooks.c | 63 ++----------------------------------- regress/wait-for-E.sh | 28 +++++++++++++++++ tmux.1 | 29 +++++++++++------ tmux.h | 2 ++ 7 files changed, 144 insertions(+), 88 deletions(-) diff --git a/cmd-wait-for.c b/cmd-wait-for.c index bd2d63268..4f8af2da4 100644 --- a/cmd-wait-for.c +++ b/cmd-wait-for.c @@ -34,8 +34,8 @@ const struct cmd_entry cmd_wait_for_entry = { .name = "wait-for", .alias = "wait", - .args = { "ELSU", 1, 1, NULL }, - .usage = "[-E|-L|-S|-U] channel", + .args = { "EF:LSUv", 1, 1, NULL }, + .usage = "[-ELSUv] [-F format] channel", .flags = 0, .exec = cmd_wait_for_exec @@ -49,6 +49,8 @@ struct wait_item { struct wait_event_item { struct cmdq_item *item; struct events_sink *sink; + char *filter; + int verbose; TAILQ_ENTRY(wait_event_item) entry; }; static TAILQ_HEAD(, wait_event_item) wait_event_items = @@ -84,7 +86,8 @@ static enum cmd_retval cmd_wait_for_lock(struct cmdq_item *, const char *, struct wait_channel *); static enum cmd_retval cmd_wait_for_unlock(struct cmdq_item *, const char *, struct wait_channel *); -static enum cmd_retval cmd_wait_for_event(struct cmdq_item *, const char *); +static enum cmd_retval cmd_wait_for_event(struct cmdq_item *, const char *, + struct args *); static void cmd_wait_for_event_cb(const char *, struct event_payload *, void *); @@ -131,23 +134,14 @@ cmd_wait_for_remove(struct wait_channel *wc) static enum cmd_retval cmd_wait_for_exec(struct cmd *self, struct cmdq_item *item) { - struct args *args = cmd_get_args(self); + struct args *args = cmd_get_args(self); const char *name = args_string(args, 0); - struct wait_channel *wc, find; - - if (args_has(args, 'E') && - (args_has(args, 'L') || args_has(args, 'S') || - args_has(args, 'U'))) { - cmdq_error(item, "-E cannot be used with -L, -S or -U"); - return (CMD_RETURN_ERROR); - } + struct wait_channel *wc, find = { .name = name }; if (args_has(args, 'E')) - return (cmd_wait_for_event(item, name)); + return (cmd_wait_for_event(item, name, args)); - find.name = name; wc = RB_FIND(wait_channels, &wait_channels, &find); - if (args_has(args, 'S')) return (cmd_wait_for_signal(item, name, wc)); if (args_has(args, 'L')) @@ -158,21 +152,61 @@ cmd_wait_for_exec(struct cmd *self, struct cmdq_item *item) } static void -cmd_wait_for_event_cb(__unused const char *name, - __unused struct event_payload *ep, void *item_data) +cmd_wait_for_event_print(struct wait_event_item *wei, struct event_payload *ep) +{ + struct event_payload_item *epi; + const char *key; + char *value; + + epi = event_payload_first(ep); + while (epi != NULL) { + key = event_payload_item_name(epi); + if (*key != '_') { + value = event_payload_item_print(epi); + cmdq_print(wei->item, "%s=%s", key, value); + free(value); + } + epi = event_payload_next(epi); + } +} + +static void +cmd_wait_for_event_cb(const char *name, struct event_payload *ep, + void *item_data) { struct wait_event_item *wei = item_data; + struct format_tree *ft; + char *expanded; + int flag; + + if (wei->verbose) + cmd_wait_for_event_print(wei, ep); + + if (wei->filter != NULL) { + ft = format_create(cmdq_get_client(wei->item), wei->item, + FORMAT_NONE, FORMAT_NOJOBS); + event_payload_add_formats(ep, ft, NULL); + expanded = format_expand(ft, wei->filter); + flag = format_true(expanded); + free(expanded); + format_free(ft); + + if (!flag) + return; + } TAILQ_REMOVE(&wait_event_items, wei, entry); events_remove_sink(wei->sink); cmdq_continue(wei->item); + free(wei->filter); free(wei); } static enum cmd_retval -cmd_wait_for_event(struct cmdq_item *item, const char *name) +cmd_wait_for_event(struct cmdq_item *item, const char *name, struct args *args) { struct wait_event_item *wei; + const char *filter = args_get(args, 'F'); if (cmdq_get_client(item) == NULL) { cmdq_error(item, "not able to wait"); @@ -181,6 +215,8 @@ cmd_wait_for_event(struct cmdq_item *item, const char *name) wei = xcalloc(1, sizeof *wei); wei->item = item; + wei->filter = (filter != NULL ? xstrdup(filter) : NULL); + wei->verbose = args_has(args, 'v'); wei->sink = events_add_sink(name, cmd_wait_for_event_cb, wei); TAILQ_INSERT_TAIL(&wait_event_items, wei, entry); @@ -302,6 +338,7 @@ cmd_wait_for_flush(void) TAILQ_REMOVE(&wait_event_items, wei, entry); events_remove_sink(wei->sink); cmdq_continue(wei->item); + free(wei->filter); free(wei); } diff --git a/events-payload.c b/events-payload.c index 01f833bf3..d087b27a6 100644 --- a/events-payload.c +++ b/events-payload.c @@ -480,6 +480,41 @@ event_payload_print(struct event_payload *ep, const char *name) return (event_payload_item_print(epi)); } +/* Add payload items as formats. */ +void +event_payload_add_formats(struct event_payload *ep, struct format_tree *ft, + const char *prefix) +{ + struct event_payload_item *epi; + char *name, *value; + const char *key; + + if (prefix == NULL) + prefix = ""; + + RB_FOREACH(epi, event_payload_tree, &ep->items) { + key = epi->name; + if (*key == '_') + continue; + + value = event_payload_item_print(epi); + xasprintf(&name, "%s%s", prefix, key); + format_add(ft, name, "%s", value); + free(name); + free(value); + + if (epi->type == EVENT_PAYLOAD_SESSION) { + xasprintf(&name, "%s%s_name", prefix, key); + format_add(ft, name, "%s", epi->session->name); + free(name); + } else if (epi->type == EVENT_PAYLOAD_WINDOW) { + xasprintf(&name, "%s%s_name", prefix, key); + format_add(ft, name, "%s", epi->window->name); + free(name); + } + } +} + /* Get the first payload item. */ struct event_payload_item * event_payload_first(struct event_payload *ep) diff --git a/events.c b/events.c index 8ae5de102..57f254d3f 100644 --- a/events.c +++ b/events.c @@ -96,6 +96,8 @@ events_fire(const char *name, struct event_payload *ep) struct events_sink *es; u_int generation = events_generation; + event_payload_set_string(ep, "event", "%s", name); + if (log_get_level() != 0) event_payload_log(ep, "%s: %s: ", __func__, name); diff --git a/hooks.c b/hooks.c index 488e1df7f..56d80e523 100644 --- a/hooks.c +++ b/hooks.c @@ -195,66 +195,6 @@ hooks_insert(struct cmdq_item *item, struct hooks_data *hd) cmdq_free_state(state); } -/* Add payload formats for a hook. */ -static void -hooks_add_formats(struct format_tree *ft, const char *name, - struct event_payload *ep) -{ - struct event_payload_item *epi; - struct client *c; - struct session *s; - struct window *w; - struct window_pane *wp; - char *fname, *value; - const char *key; - const char *svalue; - - epi = event_payload_first(ep); - while (epi != NULL) { - key = event_payload_item_name(epi); - if (*key != '_') { - value = event_payload_item_print(epi); - xasprintf(&fname, "hook_%s", key); - format_add(ft, fname, "%s", value); - free(fname); - free(value); - } - epi = event_payload_next(epi); - } - - format_add(ft, "hook", "%s", name); - - c = event_payload_get_client(ep, "client"); - if (c != NULL) - format_add(ft, "hook_client", "%s", c->name); - s = event_payload_get_session(ep, "session"); - if (s != NULL) { - format_add(ft, "hook_session", "$%u", s->id); - format_add(ft, "hook_session_name", "%s", s->name); - } - w = event_payload_get_window(ep, "window"); - if (w != NULL) { - format_add(ft, "hook_window", "@%u", w->id); - format_add(ft, "hook_window_name", "%s", w->name); - } - wp = event_payload_get_pane(ep, "pane"); - if (wp != NULL) - format_add(ft, "hook_pane", "%%%u", wp->id); - else { - value = event_payload_print(ep, "pane"); - if (value != NULL) { - format_add(ft, "hook_pane", "%s", value); - free(value); - } - } - svalue = event_payload_get_string(ep, "value"); - if (svalue != NULL) - format_add(ft, "hook_value", "%s", svalue); - svalue = event_payload_get_string(ep, "last"); - if (svalue != NULL) - format_add(ft, "hook_last", "%s", svalue); -} - /* Insert commands for a hook event. */ static void hooks_insert_event(struct cmdq_item *item, const char *name, @@ -269,7 +209,8 @@ hooks_insert_event(struct cmdq_item *item, const char *name, c = event_payload_get_client(ep, "client"); ft = format_create(c, item, FORMAT_NONE, FORMAT_NOJOBS); - hooks_add_formats(ft, name, ep); + event_payload_add_formats(ep, ft, "hook_"); + format_add(ft, "hook", "%s", name); format_log_debug(ft, __func__); memset(&hd, 0, sizeof hd); diff --git a/regress/wait-for-E.sh b/regress/wait-for-E.sh index c7b3535ba..c94155e03 100644 --- a/regress/wait-for-E.sh +++ b/regress/wait-for-E.sh @@ -94,6 +94,34 @@ $TMUX set -g @wf_value 2 || fail "set @wf_value 2 failed" wait_channel wf-late wait "$late_pid" || fail "late wait-for -E command failed" +$TMUX set -g @filtered 0 || fail "set @filtered failed" +$TMUX wait-for -E -F '#{==:#{value},3}' @wf \; set -g @filtered 1 \; \ + wait-for -S wf-filtered & +filtered_pid=$! +assert_unchanged @filtered 0 5 + +$TMUX set -g @wf_value unmatched || fail "set @wf_value unmatched failed" +assert_unchanged @filtered 0 5 + +$TMUX set -g @wf_value 3 || fail "set @wf_value 3 failed" +wait_channel wf-filtered +wait "$filtered_pid" || fail "filtered wait-for -E command failed" + +verbose_file="$OUT/verbose" +$TMUX wait-for -E -v @wf \; wait-for -S wf-verbose >"$verbose_file" & +verbose_pid=$! + +sleep 0.5 +$TMUX set -g @wf_value 4 || fail "set @wf_value 4 failed" +wait_channel wf-verbose +wait "$verbose_pid" || fail "verbose wait-for -E command failed" +grep '^event=@wf$' "$verbose_file" >/dev/null || + fail "verbose wait-for -E did not print event payload" +grep '^value=4$' "$verbose_file" >/dev/null || + fail "verbose wait-for -E did not print value payload" +grep '^_hook_monitor=' "$verbose_file" >/dev/null && + fail "verbose wait-for -E printed private payload" + $TMUX new -d -s wf2 || fail "new-session wf2 failed" $TMUX wait-for -E window-renamed \; wait-for -S wf-renamed & diff --git a/tmux.1 b/tmux.1 index 5a4806922..aafe2d0be 100644 --- a/tmux.1 +++ b/tmux.1 @@ -8553,7 +8553,8 @@ or the current pane if omitted) after the command finishes. If the command fails, the exit status is also displayed. .Tg wait .It Xo Ic wait\-for -.Op Fl E | L | S | U +.Op Fl ELSUv +.Op Fl F Ar format .Ar channel | event\-name .Xc .D1 Pq alias: Ic wait @@ -8562,8 +8563,14 @@ When used without options, prevents the client from exiting until woken using .Fl S with the same channel. When -.Fl E -is used, +.Fl L +is used, the channel is locked and any clients that try to lock the same +channel are made to wait until the channel is unlocked with +.Ic wait\-for +.Fl U . +.Pp +With +.Fl E , .Nm waits for the next event with the given name. Events include hook and notification names, and user @@ -8571,12 +8578,16 @@ Events include hook and notification names, and user events generated by .Ic set-hook .Fl B . -When -.Fl L -is used, the channel is locked and any clients that try to lock the same -channel are made to wait until the channel is unlocked with -.Ic wait\-for -.Fl U . +If +.Fl F +is given, +.Ar format +must also be true. +If +.Fl v +is given, event payload keys are printed (whether or not +.Ar format +is true). .El .Sh EXIT MESSAGES When a diff --git a/tmux.h b/tmux.h index 7b1efef88..a7c22b581 100644 --- a/tmux.h +++ b/tmux.h @@ -2697,6 +2697,8 @@ void event_payload_set_pointer(struct event_payload *, const char *, void *, event_payload_free_cb, event_payload_print_cb); const char *event_payload_get_string(struct event_payload *, const char *); char *event_payload_print(struct event_payload *, const char *); +void event_payload_add_formats(struct event_payload *, + struct format_tree *, const char *); struct event_payload_item *event_payload_first(struct event_payload *); struct event_payload_item *event_payload_next(struct event_payload_item *); const char *event_payload_item_name(struct event_payload_item *);