Run after hooks as events.

This commit is contained in:
Nicholas Marriott
2026-07-06 11:35:42 +01:00
parent 38e609ed89
commit 95b198ce30
3 changed files with 59 additions and 58 deletions

View File

@@ -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);
}

19
hooks.c
View File

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

View File

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