From 95b198ce30c141286c9cff68ef8f8a32ba338e32 Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Mon, 6 Jul 2026 11:35:42 +0100 Subject: [PATCH] Run after hooks as events. --- cmd-queue.c | 68 +++++++++++------------------------------- hooks.c | 19 ++++++++++-- regress/hooks-after.sh | 30 ++++++++++++++++--- 3 files changed, 59 insertions(+), 58 deletions(-) diff --git a/cmd-queue.c b/cmd-queue.c index 2a221516d..8a388bb02 100644 --- a/cmd-queue.c +++ b/cmd-queue.c @@ -350,74 +350,53 @@ cmdq_insert_after(struct cmdq_item *after, struct cmdq_item *item) /* Insert a hook. */ void -cmdq_insert_hook(struct session *s, struct cmdq_item *item, +cmdq_insert_hook(__unused struct session *s, struct cmdq_item *item, struct cmd_find_state *current, const char *fmt, ...) { - struct cmdq_state *state = item->state; struct cmd *cmd = item->cmd; struct args *args = cmd_get_args(cmd); struct args_entry *ae; struct args_value *av; - struct options *oo; + struct event_payload *ep; va_list ap; char *name, tmp[32], flag, *arguments; u_int i; const char *value; - struct cmdq_item *new_item; - struct cmdq_state *new_state; - struct options_entry *o; - struct options_array_item *a; - struct cmd_list *cmdlist; if (item->state->flags & CMDQ_STATE_NOHOOKS) return; - if (s == NULL) - oo = global_s_options; - else - oo = s->options; va_start(ap, fmt); xvasprintf(&name, fmt, ap); va_end(ap); - o = options_get(oo, name); - if (o == NULL) { - free(name); - return; - } - log_debug("running hook %s (parent %p)", name, item); - - /* - * The hooks get a new state because they should not update the current - * target or formats for any subsequent commands. - */ - new_state = cmdq_new_state(current, &state->event, CMDQ_STATE_NOHOOKS); - cmdq_add_format(new_state, "hook", "%s", name); + ep = event_payload_create(); + if (current != NULL) + event_payload_set_target(ep, current); + event_payload_set_pointer(ep, "_cmdq_item", item, NULL, NULL); arguments = args_print(args); - cmdq_add_format(new_state, "hook_arguments", "%s", arguments); + event_payload_set_string(ep, "arguments", "%s", arguments); free(arguments); for (i = 0; i < args_count(args); i++) { - xsnprintf(tmp, sizeof tmp, "hook_argument_%d", i); - cmdq_add_format(new_state, tmp, "%s", args_string(args, i)); + xsnprintf(tmp, sizeof tmp, "argument_%u", i); + event_payload_set_string(ep, tmp, "%s", args_string(args, i)); } flag = args_first(args, &ae); while (flag != 0) { value = args_get(args, flag); - if (value == NULL) { - xsnprintf(tmp, sizeof tmp, "hook_flag_%c", flag); - cmdq_add_format(new_state, tmp, "1"); - } else { - xsnprintf(tmp, sizeof tmp, "hook_flag_%c", flag); - cmdq_add_format(new_state, tmp, "%s", value); - } + xsnprintf(tmp, sizeof tmp, "flag_%c", flag); + if (value == NULL) + event_payload_set_string(ep, tmp, "1"); + else + event_payload_set_string(ep, tmp, "%s", value); i = 0; av = args_first_value(args, flag); while (av != NULL) { - xsnprintf(tmp, sizeof tmp, "hook_flag_%c_%d", flag, i); - cmdq_add_format(new_state, tmp, "%s", av->string); + xsnprintf(tmp, sizeof tmp, "flag_%c_%u", flag, i); + event_payload_set_string(ep, tmp, "%s", av->string); i++; av = args_next_value(av); } @@ -425,20 +404,7 @@ cmdq_insert_hook(struct session *s, struct cmdq_item *item, flag = args_next(&ae); } - a = options_array_first(o); - while (a != NULL) { - cmdlist = options_array_item_value(a)->cmdlist; - if (cmdlist != NULL) { - new_item = cmdq_get_command(cmdlist, new_state); - if (item != NULL) - item = cmdq_insert_after(item, new_item); - else - item = cmdq_append(NULL, new_item); - } - a = options_array_next(a); - } - - cmdq_free_state(new_state); + events_fire(name, ep); free(name); } diff --git a/hooks.c b/hooks.c index 45d80b7c9..488e1df7f 100644 --- a/hooks.c +++ b/hooks.c @@ -144,7 +144,12 @@ hooks_insert(struct cmdq_item *item, struct hooks_data *hd) return; } - state = cmdq_new_state(&fs, NULL, CMDQ_STATE_NOHOOKS); + if (item == NULL) + state = cmdq_new_state(&fs, NULL, CMDQ_STATE_NOHOOKS); + else { + state = cmdq_new_state(&fs, cmdq_get_event(item), + CMDQ_STATE_NOHOOKS); + } cmdq_add_formats(state, hd->formats); if (*hd->name == '@') { @@ -168,8 +173,10 @@ hooks_insert(struct cmdq_item *item, struct hooks_data *hd) pr = hooks_parse(hd, &fs, value); switch (pr->status) { case CMD_PARSE_ERROR: - if (pr->error != NULL) - cmdq_error(item, "%s", pr->error); + if (pr->error != NULL) { + cmdq_error(item, "%s", + pr->error); + } break; case CMD_PARSE_SUCCESS: item = hooks_insert_one(item, hd, @@ -285,6 +292,12 @@ hooks_event_cb(const char *name, struct event_payload *ep, { struct cmdq_item *item; + item = event_payload_get_pointer(ep, "_cmdq_item"); + if (item != NULL) { + hooks_insert_event(item, name, ep, NULL, 0); + return; + } + item = cmdq_running(NULL); if (item == NULL || (~cmdq_get_flags(item) & CMDQ_STATE_NOHOOKS)) hooks_insert_event(NULL, name, ep, NULL, 0); diff --git a/regress/hooks-after.sh b/regress/hooks-after.sh index ee7fbfe0e..605109570 100644 --- a/regress/hooks-after.sh +++ b/regress/hooks-after.sh @@ -63,10 +63,10 @@ $TMUX new -d -s two || fail "new-session two failed" # An after hook fires with the command arguments as formats. $TMUX set -g @after 0 || fail "set @after failed" $TMUX set-hook -g after-rename-window \ - 'set -gF @after "#{hook}|#{hook_argument_0}|#{hook_flag_t}"' || + 'set -gF @after "#{hook}|#{hook_arguments}|#{hook_argument_0}|#{hook_flag_t}"' || fail "set-hook -g after-rename-window failed" $TMUX rename-window -t one:0 first || fail "rename-window first failed" -wait_for @after 'after-rename-window|first|one:0' +wait_for @after 'after-rename-window|-t one:0 first|first|one:0' # An appended hook command runs after the first. $TMUX set -g @after2 0 || fail "set @after2 failed" @@ -74,10 +74,32 @@ $TMUX set-hook -ga after-rename-window \ 'set -gF @after2 "#{@after}+2"' || fail "set-hook -ga after-rename-window failed" $TMUX rename-window -t one:0 second || fail "rename-window second failed" -wait_for @after 'after-rename-window|second|one:0' -wait_for @after2 'after-rename-window|second|one:0+2' +wait_for @after 'after-rename-window|-t one:0 second|second|one:0' +wait_for @after2 'after-rename-window|-t one:0 second|second|one:0+2' $TMUX set-hook -gu after-rename-window || fail "set-hook -gu failed" +# A hook command is inserted after the command that fired it, before the next +# command in the same command list. +$TMUX set -g @order '' || fail "set @order failed" +$TMUX set-hook -g after-rename-window \ + 'set -gF @order "#{@order}H"' || + fail "set-hook -g after-rename-window order failed" +$TMUX rename-window -t one:0 ordered \; set -gF @order '#{@order}N' || + fail "rename-window order failed" +wait_for @order 'HN' +$TMUX set-hook -gu after-rename-window || fail "set-hook -gu order failed" + +# Repeated flag values are available as numbered hook flag formats. +$TMUX set -g @flags 0 || fail "set @flags failed" +$TMUX set-hook -g after-split-window \ + 'set -gF @flags "#{hook_arguments}|#{hook_argument_0}|#{hook_flag_t}|#{hook_flag_e}|#{hook_flag_e_0}|#{hook_flag_e_1}"' || + fail "set-hook -g after-split-window failed" +$TMUX split-window -d -t one:0 -e A=1 -e B=2 'sleep 60' || + fail "split-window flags failed" +wait_for @flags '-d -e A=1 -e B=2 -t one:0 "sleep 60"|sleep 60|one:0|B=2|A=1|B=2' +$TMUX set-hook -gu after-split-window || + fail "set-hook -gu after-split-window failed" + # A session after hook only fires for commands targeting that session and # the hook commands run with the command target as current state. $TMUX set -g @safter 0 || fail "set @safter failed"