From 7e216e86587a37b9eb2aea5718cf0508ae2ec06b Mon Sep 17 00:00:00 2001 From: Nicholas Marriott Date: Tue, 7 Jul 2026 13:45:14 +0100 Subject: [PATCH] Add -E to set-hook to fire an event. --- cmd-set-option.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++-- hooks.c | 2 +- tmux.1 | 15 ++++++++++++- tmux.h | 1 + 4 files changed, 69 insertions(+), 4 deletions(-) diff --git a/cmd-set-option.c b/cmd-set-option.c index 397456a91..b29872adf 100644 --- a/cmd-set-option.c +++ b/cmd-set-option.c @@ -31,6 +31,8 @@ static enum args_parse_type cmd_set_option_args_parse(struct args *, u_int, char **); static enum cmd_retval cmd_set_option_exec(struct cmd *, struct cmdq_item *); +static enum cmd_retval cmd_set_hook_event_exec(struct cmd *, + struct cmdq_item *); static enum cmd_retval cmd_set_hook_monitor_exec(struct cmdq_item *, struct args *, int); @@ -64,8 +66,8 @@ const struct cmd_entry cmd_set_hook_entry = { .name = "set-hook", .alias = NULL, - .args = { "agpRt:uB:w", 0, 2, cmd_set_option_args_parse }, - .usage = "[-agpRuw] [-B name:what:format] " CMD_TARGET_PANE_USAGE " " + .args = { "agpERt:uB:w", 0, 2, cmd_set_option_args_parse }, + .usage = "[-agpERuw] [-B name:what:format] " CMD_TARGET_PANE_USAGE " " "[hook] [command]", .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL }, @@ -85,6 +87,51 @@ cmd_set_option_args_parse(struct args *args, u_int idx, return (ARGS_PARSE_STRING); } +static enum cmd_retval +cmd_set_hook_event_exec(struct cmd *self, struct cmdq_item *item) +{ + struct args *args = cmd_get_args(self); + struct cmd_find_state *target = cmdq_get_target(item); + struct event_payload *ep; + struct client *c; + char *argument; + + if (args_count(args) == 0) { + cmdq_error(item, "missing argument"); + return (CMD_RETURN_ERROR); + } + if (args_count(args) != 1) { + cmdq_error(item, "too many arguments"); + return (CMD_RETURN_ERROR); + } + + argument = format_single_from_target(item, args_string(args, 0)); + if (*argument != '@') { + cmdq_error(item, "event name must start with @"); + free(argument); + return (CMD_RETURN_ERROR); + } + + ep = event_payload_create(); + event_payload_set_target(ep, target); + c = cmdq_get_client(item); + if (c != NULL) + event_payload_set_client(ep, "client", c); + if (target->s != NULL) + event_payload_set_session(ep, "session", target->s); + if (target->w != NULL) + event_payload_set_window(ep, "window", target->w); + if (target->wl != NULL) + event_payload_set_int(ep, "window_index", target->wl->idx); + else if (target->idx != -1) + event_payload_set_int(ep, "window_index", target->idx); + if (target->wp != NULL) + event_payload_set_pane(ep, "pane", target->wp); + events_fire(argument, ep); + free(argument); + return (CMD_RETURN_NORMAL); +} + static enum cmd_retval cmd_set_hook_monitor_exec(struct cmdq_item *item, struct args *args, int window) { @@ -185,6 +232,8 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) int scope; window = (cmd_get_entry(self) == &cmd_set_window_option_entry); + if (cmd_get_entry(self) == &cmd_set_hook_entry && args_has(args, 'E')) + return (cmd_set_hook_event_exec(self, item)); if (cmd_get_entry(self) == &cmd_set_hook_entry && args_has(args, 'B')) return (cmd_set_hook_monitor_exec(item, args, window)); if (args_count(args) == 0) { @@ -289,6 +338,8 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) goto fail; } options_set_string(oo, name, append, "%s", value); + if (cmd_get_entry(self) == &cmd_set_hook_entry) + hooks_add_event(name); } else if (array_key == NULL && !options_is_array(parent)) { error = options_from_string(oo, options_table_entry(parent), options_table_entry(parent)->name, value, diff --git a/hooks.c b/hooks.c index 56d80e523..558f48f9f 100644 --- a/hooks.c +++ b/hooks.c @@ -245,7 +245,7 @@ hooks_event_cb(const char *name, struct event_payload *ep, } /* Add a hook event sink. */ -static void +void hooks_add_event(const char *name) { struct hooks_event *he; diff --git a/tmux.1 b/tmux.1 index b4a78dbd4..4fdc042a4 100644 --- a/tmux.1 +++ b/tmux.1 @@ -6400,13 +6400,15 @@ Run when a window is unlinked from a session. Hooks are managed with these commands: .Bl -tag -width Ds .It Xo Ic set\-hook -.Op Fl agpRuw +.Op Fl agpERuw .Op Fl B Ar name:what:format .Op Fl t Ar target\-pane .Ar hook\-name .Op Ar command .Xc Without +.Fl E +or .Fl R , sets (or with .Fl u @@ -6447,6 +6449,14 @@ the subscription named by is removed. .Pp With +.Fl E , +fire the user event named +.Ar hook\-name . +.Ar hook\-name +must begin with +.Ql @ . +.Pp +With .Fl R , run .Ar hook\-name @@ -8582,6 +8592,9 @@ Events include hook and notification names, and user .Ql @ events generated by .Ic set-hook +.Fl E +or +.Ic set-hook .Fl B . If .Fl F diff --git a/tmux.h b/tmux.h index 8739b47fa..3e3afe64a 100644 --- a/tmux.h +++ b/tmux.h @@ -2734,6 +2734,7 @@ char *format_trim_left(const char *, u_int); char *format_trim_right(const char *, u_int); /* hooks.c */ +void hooks_add_event(const char *); void hooks_build_events(void); void hooks_run(struct cmdq_item *, const char *); void hooks_monitor_add(struct cmdq_item *, struct options *,