diff --git a/cmd-set-option.c b/cmd-set-option.c index 1b6de1e86..1d95bddc9 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 = { "agpERTt:uB:w", 0, 2, cmd_set_option_args_parse }, + .usage = "[-agpERTuw] [-B name:what:format] " CMD_TARGET_PANE_USAGE " " "[hook] [command]", .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL }, @@ -85,17 +87,63 @@ 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) { struct cmd_find_state *target = cmdq_get_target(item), fs; struct options *oo; struct options_entry *o; + struct session *s = NULL; char *cause = NULL, *name = NULL, *format = NULL; char *expanded = NULL, *newvalue = NULL; const char *value, *old; enum monitor_type type; - int id, scope; + int id, scope, flags = 0; if (args_count(args) > 1) { cmdq_error(item, "too many arguments"); @@ -130,7 +178,7 @@ cmd_set_hook_monitor_exec(struct cmdq_item *item, struct args *args, int window) cmd_find_copy_state(&fs, target); if (args_has(args, 'u')) { - notify_monitor_remove(oo, name); + hooks_monitor_remove(oo, name); goto out; } @@ -152,7 +200,13 @@ cmd_set_hook_monitor_exec(struct cmdq_item *item, struct args *args, int window) } } - notify_monitor_add(item, oo, name, type, id, format, &fs, target->s); + if (oo != global_options && + oo != global_s_options && + oo != global_w_options) + s = target->s; + if (args_has(args, 'T')) + flags |= MONITOR_NOTIFY_TRUE; + hooks_monitor_add(item, oo, name, type, id, format, flags, &fs, s); out: free(newvalue); @@ -185,6 +239,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) { @@ -197,7 +253,7 @@ cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) /* If set-hook -R, fire the hook straight away. */ if (cmd_get_entry(self) == &cmd_set_hook_entry && args_has(args, 'R')) { - notify_hook(item, argument); + hooks_run(item, argument); free(argument); return (CMD_RETURN_NORMAL); } @@ -289,6 +345,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/format.c b/format.c index a911fed59..912f3a9db 100644 --- a/format.c +++ b/format.c @@ -2187,6 +2187,106 @@ format_cb_pane_dead_time(struct format_tree *ft) return (NULL); } +/* Callback for pane_last_output_time. */ +static void * +format_cb_pane_last_output_time(struct format_tree *ft) +{ + struct window_pane *wp = ft->wp; + static struct timeval tv; + + if (wp != NULL && wp->last_output_time != 0) { + tv.tv_sec = wp->last_output_time; + tv.tv_usec = 0; + return (&tv); + } + return (NULL); +} + +/* Callback for pane_last_prompt_time. */ +static void * +format_cb_pane_last_prompt_time(struct format_tree *ft) +{ + struct window_pane *wp = ft->wp; + static struct timeval tv; + + if (wp != NULL && wp->last_prompt_time != 0) { + tv.tv_sec = wp->last_prompt_time; + tv.tv_usec = 0; + return (&tv); + } + return (NULL); +} + +/* Callback for pane_command_start_time. */ +static void * +format_cb_pane_command_start_time(struct format_tree *ft) +{ + struct window_pane *wp = ft->wp; + static struct timeval tv; + + if (wp != NULL && wp->cmd_start_time != 0) { + tv.tv_sec = wp->cmd_start_time; + tv.tv_usec = 0; + return (&tv); + } + return (NULL); +} + +/* Callback for pane_command_end_time. */ +static void * +format_cb_pane_command_end_time(struct format_tree *ft) +{ + struct window_pane *wp = ft->wp; + static struct timeval tv; + + if (wp != NULL && wp->cmd_end_time != 0) { + tv.tv_sec = wp->cmd_end_time; + tv.tv_usec = 0; + return (&tv); + } + return (NULL); +} + +/* Callback for pane_command_running. */ +static void * +format_cb_pane_command_running(struct format_tree *ft) +{ + struct window_pane *wp = ft->wp; + + if (wp != NULL) + return (format_printf("%d", !!(wp->flags & PANE_CMDRUNNING))); + return (NULL); +} + +/* Callback for pane_command_duration. */ +static void * +format_cb_pane_command_duration(struct format_tree *ft) +{ + struct window_pane *wp = ft->wp; + time_t end; + + if (wp == NULL || wp->cmd_start_time == 0) + return (NULL); + if (wp->flags & PANE_CMDRUNNING) + end = time(NULL); + else + end = wp->cmd_end_time; + if (end < wp->cmd_start_time) + end = wp->cmd_start_time; + return (format_printf("%lld", (long long)(end - wp->cmd_start_time))); +} + +/* Callback for pane_command_status. */ +static void * +format_cb_pane_command_status(struct format_tree *ft) +{ + struct window_pane *wp = ft->wp; + + if (wp != NULL && wp->cmd_status != -1) + return (format_printf("%d", wp->cmd_status)); + return (NULL); +} + /* Callback for pane_format. */ static void * format_cb_pane_format(struct format_tree *ft) @@ -3503,6 +3603,21 @@ static const struct format_table_entry format_table[] = { { "pane_bottom", FORMAT_TABLE_STRING, format_cb_pane_bottom }, + { "pane_command_duration", FORMAT_TABLE_STRING, + format_cb_pane_command_duration + }, + { "pane_command_end_time", FORMAT_TABLE_TIME, + format_cb_pane_command_end_time + }, + { "pane_command_running", FORMAT_TABLE_STRING, + format_cb_pane_command_running + }, + { "pane_command_start_time", FORMAT_TABLE_TIME, + format_cb_pane_command_start_time + }, + { "pane_command_status", FORMAT_TABLE_STRING, + format_cb_pane_command_status + }, { "pane_current_command", FORMAT_TABLE_STRING, format_cb_current_command }, @@ -3554,6 +3669,12 @@ static const struct format_table_entry format_table[] = { { "pane_last", FORMAT_TABLE_STRING, format_cb_pane_last }, + { "pane_last_output_time", FORMAT_TABLE_TIME, + format_cb_pane_last_output_time + }, + { "pane_last_prompt_time", FORMAT_TABLE_TIME, + format_cb_pane_last_prompt_time + }, { "pane_left", FORMAT_TABLE_STRING, format_cb_pane_left }, diff --git a/hooks.c b/hooks.c new file mode 100644 index 000000000..097b768b4 --- /dev/null +++ b/hooks.c @@ -0,0 +1,461 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2026 Nicholas Marriott + * Copyright (c) 2012 George Nachman + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER + * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING + * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include + +#include +#include + +#include "tmux.h" + +/* Hook monitor state owned by an option entry. */ +struct hook_monitor { + struct options *oo; + + struct monitor_set *set; + struct events_sink *sink; + struct cmd_find_state fs; + + enum monitor_type type; + int id; + char *format; +}; + +/* Hook command data built from an event payload. */ +struct hooks_data { + const char *name; + struct cmd_find_state fs; + struct format_tree *formats; + struct options *oo; + struct client *client; + int expand; +}; + +/* Hook event sink registered for a notify event name. */ +struct hooks_event { + char *name; + struct events_sink *sink; + TAILQ_ENTRY(hooks_event) entry; +}; +TAILQ_HEAD(hooks_events, hooks_event); +static struct hooks_events hooks_events = TAILQ_HEAD_INITIALIZER(hooks_events); + +/* Insert one hook command list. */ +static struct cmdq_item * +hooks_insert_one(struct cmdq_item *item, struct hooks_data *hd, + struct cmd_list *cmdlist, struct cmdq_state *state) +{ + struct cmdq_item *new_item; + char *s; + + if (cmdlist == NULL) + return (item); + if (log_get_level() != 0) { + s = cmd_list_print(cmdlist, 0); + log_debug("%s: hook %s is: %s", __func__, hd->name, s); + free(s); + } + new_item = cmdq_get_command(cmdlist, state); + if (item != NULL) + return (cmdq_insert_after(item, new_item)); + return (cmdq_append(NULL, new_item)); +} + +/* Parse a hook command. */ +static struct cmd_parse_result * +hooks_parse(struct hooks_data *hd, struct cmd_find_state *fs, + const char *value) +{ + struct cmd_parse_result *pr; + struct format_tree *ft; + char *expanded; + + if (!hd->expand) + return (cmd_parse_from_string(value, NULL)); + + ft = format_create_defaults(NULL, hd->client, fs->s, fs->wl, fs->wp); + if (hd->formats != NULL) + format_merge(ft, hd->formats); + expanded = format_expand(ft, value); + format_free(ft); + + pr = cmd_parse_from_string(expanded, NULL); + free(expanded); + return (pr); +} + +/* Insert commands for a hook. */ +static void +hooks_insert(struct cmdq_item *item, struct hooks_data *hd) +{ + struct cmd_find_state fs; + struct options *oo; + struct cmdq_state *state; + struct options_entry *o; + struct options_array_item *a; + struct cmd_list *cmdlist; + const char *value; + struct cmd_parse_result *pr; + + log_debug("%s: inserting hook %s", __func__, hd->name); + + cmd_find_clear_state(&fs, 0); + if (cmd_find_empty_state(&hd->fs) || !cmd_find_valid_state(&hd->fs)) + cmd_find_from_nothing(&fs, 0); + else + cmd_find_copy_state(&fs, &hd->fs); + + if (hd->oo != NULL) { + oo = hd->oo; + o = options_get_only(oo, hd->name); + } else { + if (fs.s == NULL) + oo = global_s_options; + else + oo = fs.s->options; + o = options_get(oo, hd->name); + if (o == NULL && fs.wp != NULL) { + oo = fs.wp->options; + o = options_get(oo, hd->name); + } + if (o == NULL && fs.wl != NULL) { + oo = fs.wl->window->options; + o = options_get(oo, hd->name); + } + } + if (o == NULL) { + log_debug("%s: hook %s not found", __func__, hd->name); + return; + } + + 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 == '@') { + value = options_get_string(oo, hd->name); + pr = hooks_parse(hd, &fs, value); + switch (pr->status) { + case CMD_PARSE_ERROR: + log_debug("%s: can't parse hook %s: %s", __func__, + hd->name, pr->error); + free(pr->error); + break; + case CMD_PARSE_SUCCESS: + hooks_insert_one(item, hd, pr->cmdlist, state); + break; + } + } else { + a = options_array_first(o); + while (a != NULL) { + if (hd->expand) { + value = options_array_item_value(a)->string; + pr = hooks_parse(hd, &fs, value); + switch (pr->status) { + case CMD_PARSE_ERROR: + if (pr->error != NULL) { + cmdq_error(item, "%s", + pr->error); + } + break; + case CMD_PARSE_SUCCESS: + item = hooks_insert_one(item, hd, + pr->cmdlist, state); + break; + } + } else { + cmdlist = options_array_item_value(a)->cmdlist; + item = hooks_insert_one(item, hd, cmdlist, + state); + } + a = options_array_next(a); + } + } + + cmdq_free_state(state); +} + +/* Insert commands for a hook event. */ +static void +hooks_insert_event(struct cmdq_item *item, const char *name, + struct event_payload *ep, struct options *oo, int expand) +{ + struct hooks_data hd; + struct format_tree *ft; + struct client *c; + + if (item != NULL && (cmdq_get_flags(item) & CMDQ_STATE_NOHOOKS)) + return; + + c = event_payload_get_client(ep, "client"); + ft = format_create(c, item, FORMAT_NONE, FORMAT_NOJOBS); + event_payload_add_formats(ep, ft, "hook_"); + format_add(ft, "hook", "%s", name); + format_log_debug(ft, __func__); + + memset(&hd, 0, sizeof hd); + hd.name = name; + cmd_find_clear_state(&hd.fs, 0); + event_payload_get_target(ep, &hd.fs); + hd.formats = ft; + hd.oo = oo; + hd.client = c; + hd.expand = expand; + + hooks_insert(item, &hd); + format_free(ft); +} + +/* Handle an event for hooks. */ +static void +hooks_event_cb(const char *name, struct event_payload *ep, + __unused void *sink_data) +{ + struct cmdq_item *item; + + if (event_payload_get_pointer(ep, "_hook_monitor") != NULL) + return; + + 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); +} + +/* Add a hook event sink. */ +void +hooks_add_event(const char *name) +{ + struct hooks_event *he; + + TAILQ_FOREACH(he, &hooks_events, entry) { + if (strcmp(he->name, name) == 0) + return; + } + + he = xcalloc(1, sizeof *he); + he->name = xstrdup(name); + he->sink = events_add_sink(name, hooks_event_cb, NULL); + TAILQ_INSERT_TAIL(&hooks_events, he, entry); +} + +/* Return if an event name can be fired through the hooks path. */ +int +hooks_valid_event_name(const char *name) +{ + const struct options_table_entry *oe; + + if (*name == '@') + return (1); + oe = options_search(name); + return (oe != NULL && (oe->flags & OPTIONS_TABLE_IS_HOOK)); +} + +/* Add hook event sinks for all built-in hooks. */ +void +hooks_build_events(void) +{ + const struct options_table_entry *oe; + + for (oe = options_table; oe->name != NULL; oe++) { + if (oe->flags & OPTIONS_TABLE_IS_HOOK) + hooks_add_event(oe->name); + } +} + +/* Run a hook immediately. */ +void +hooks_run(struct cmdq_item *item, const char *name) +{ + struct cmd_find_state *target = cmdq_get_target(item); + struct hooks_data hd = { 0 }; + + hd.name = name; + cmd_find_copy_state(&hd.fs, target); + hd.client = cmdq_get_client(item); + + hd.formats = format_create(NULL, NULL, 0, FORMAT_NOJOBS); + format_add(hd.formats, "hook", "%s", name); + format_log_debug(hd.formats, __func__); + + hooks_insert(item, &hd); + format_free(hd.formats); +} + +/* Free a hook monitor. */ +void +hooks_monitor_free(void *data) +{ + struct hook_monitor *hm = data; + + events_remove_sink(hm->sink); + monitor_destroy(hm->set); + free(hm->format); + free(hm); +} + +/* Remove a hook monitor. */ +void +hooks_monitor_remove(struct options *oo, const char *name) +{ + struct options_entry *o; + struct hook_monitor *hm; + + o = options_get_only(oo, name); + if (o == NULL) + return; + + hm = options_get_monitor_data(o); + if (hm != NULL) { + options_set_monitor_data(o, NULL); + hooks_monitor_free(hm); + } +} + +/* Handle a hook monitor event. */ +static void +hooks_monitor_hook_cb(const char *name, struct event_payload *ep, + void *sink_data) +{ + struct hook_monitor *hm = sink_data; + + if (event_payload_get_pointer(ep, "_hook_monitor") == hm) + hooks_insert_event(cmdq_running(NULL), name, ep, hm->oo, 1); +} + +/* Fire a hook monitor event. */ +static void +hooks_monitor_cb(struct monitor_change *change, void *data) +{ + struct hook_monitor *hm = data; + struct event_payload *ep; + struct winlink *wl = change->wl; + struct window_pane *wp = change->wp; + struct cmd_find_state fs; + + ep = event_payload_create(); + event_payload_set_pointer(ep, "_hook_monitor", data, NULL, NULL); + + cmd_find_clear_state(&fs, 0); + if (wl != NULL && wp != NULL && wp->window == wl->window) + cmd_find_from_winlink_pane(&fs, wl, wp, 0); + else if (wl != NULL) + cmd_find_from_winlink(&fs, wl, 0); + else if (wp != NULL) + cmd_find_from_pane(&fs, wp, 0); + else if (change->s != NULL) + cmd_find_from_session(&fs, change->s, 0); + else + cmd_find_copy_state(&fs, &hm->fs); + event_payload_set_target(ep, &fs); + + if (change->value != NULL) + event_payload_set_string(ep, "value", "%s", change->value); + else + event_payload_set_string(ep, "value", "%s", ""); + if (change->last != NULL) + event_payload_set_string(ep, "last", "%s", change->last); + else + event_payload_set_string(ep, "last", "%s", ""); + + if (change->c != NULL) + event_payload_set_client(ep, "client", change->c); + if (change->s != NULL) + event_payload_set_session(ep, "session", change->s); + if (wl != NULL) { + if (change->s == NULL) + event_payload_set_session(ep, "session", + wl->session); + event_payload_set_window(ep, "window", wl->window); + event_payload_set_int(ep, "window_index", wl->idx); + } + if (wp != NULL) { + event_payload_set_pane(ep, "pane", wp); + if (wl == NULL) + event_payload_set_window(ep, "window", wp->window); + } + + events_fire(change->name, ep); +} + +/* Add a hook monitor. */ +void +hooks_monitor_add(__unused struct cmdq_item *item, struct options *oo, + const char *name, enum monitor_type type, int id, const char *format, + int flags, struct cmd_find_state *fs, struct session *s) +{ + struct options_entry *o; + struct hook_monitor *hm; + + hooks_monitor_remove(oo, name); + o = options_get_only(oo, name); + if (o == NULL) + o = options_set_string(oo, name, 0, "%s", ""); + + hm = xcalloc(1, sizeof *hm); + hm->oo = oo; + cmd_find_copy_state(&hm->fs, fs); + hm->type = type; + hm->id = id; + hm->format = xstrdup(format); + hm->set = monitor_create_session(s, hooks_monitor_cb, hm); + hm->sink = events_add_sink(name, hooks_monitor_hook_cb, hm); + options_set_monitor_data(o, hm); + monitor_add(hm->set, name, type, id, format, flags); +} + +/* Convert a hook monitor to its value. */ +char * +hooks_monitor_to_string(struct options_entry *o) +{ + struct hook_monitor *hm = options_get_monitor_data(o); + const char *name = options_name(o); + char *s; + + if (hm == NULL) + return (NULL); + + switch (hm->type) { + case MONITOR_SESSION: + xasprintf(&s, "%s::%s", name, hm->format); + break; + case MONITOR_PANE: + xasprintf(&s, "%s:%%%d:%s", name, hm->id, hm->format); + break; + case MONITOR_ALL_PANES: + xasprintf(&s, "%s:%%*:%s", name, hm->format); + break; + case MONITOR_WINDOW: + xasprintf(&s, "%s:@%d:%s", name, hm->id, hm->format); + break; + case MONITOR_ALL_WINDOWS: + xasprintf(&s, "%s:@*:%s", name, hm->format); + break; + } + return (s); +} diff --git a/input.c b/input.c index a85b555f8..8e6184825 100644 --- a/input.c +++ b/input.c @@ -174,6 +174,8 @@ static void input_osc_110(struct input_ctx *, const char *); static void input_osc_111(struct input_ctx *, const char *); static void input_osc_112(struct input_ctx *, const char *); static void input_osc_133(struct input_ctx *, const char *); +static void input_fire_pane_title_changed(struct window_pane *, + const char *); /* Transition entry/exit handlers. */ static void input_clear(struct input_ctx *); @@ -210,6 +212,21 @@ static int input_end_bel(struct input_ctx *); /* Command table comparison function. */ static int input_table_compare(const void *, const void *); +static void +input_fire_pane_title_changed(struct window_pane *wp, const char *title) +{ + struct event_payload *ep; + struct cmd_find_state fs; + + ep = event_payload_create(); + cmd_find_from_pane(&fs, wp, 0); + event_payload_set_target(ep, &fs); + event_payload_set_pane(ep, "pane", wp); + event_payload_set_window(ep, "window", wp->window); + event_payload_set_string(ep, "new_title", "%s", title); + events_fire("pane-title-changed", ep); +} + /* Command table entry. */ struct input_table_entry { int ch; @@ -1017,6 +1034,8 @@ input_parse_pane(struct window_pane *wp) size_t new_size; new_data = window_pane_get_new_data(wp, &wp->offset, &new_size); + if (new_size != 0) + wp->last_output_time = time(NULL); input_parse_buffer(wp, new_data, new_size); window_pane_update_used_data(wp, &wp->offset, new_size); } @@ -2180,7 +2199,7 @@ input_csi_dispatch_winops(struct input_ctx *ictx) screen_pop_title(sctx->s); if (wp == NULL) break; - notify_pane("pane-title-changed", wp); + events_fire_pane("pane-title-changed", wp); server_redraw_window_borders(w); server_status_window(w); break; @@ -2700,7 +2719,7 @@ input_exit_osc(struct input_ctx *ictx) if (wp != NULL && options_get_number(wp->options, "allow-set-title") && screen_set_title(sctx->s, p, 1)) { - notify_pane("pane-title-changed", wp); + input_fire_pane_title_changed(wp, p); server_redraw_window_borders(wp->window); server_status_window(wp->window); } @@ -2778,7 +2797,7 @@ input_exit_apc(struct input_ctx *ictx) if (wp != NULL && options_get_number(wp->options, "allow-set-title") && screen_set_title(sctx->s, ictx->input_buf, 1)) { - notify_pane("pane-title-changed", wp); + input_fire_pane_title_changed(wp, ictx->input_buf); server_redraw_window_borders(wp->window); server_status_window(wp->window); } @@ -3146,7 +3165,8 @@ input_osc_12(struct input_ctx *ictx, const char *p) c = ictx->ctx.s->ccolour; if (c == -1) c = ictx->ctx.s->default_ccolour; - input_osc_colour_reply(ictx, 1, 12, 0, c, ictx->input_end); + input_osc_colour_reply(ictx, 1, 12, 0, c, + ictx->input_end); } return; } @@ -3166,24 +3186,92 @@ input_osc_112(struct input_ctx *ictx, const char *p) screen_set_cursor_colour(ictx->ctx.s, -1); } +/* Fire an OSC 133 command event. */ +static void +input_fire_command_event(struct window_pane *wp, const char *name) +{ + struct event_payload *ep; + struct cmd_find_state fs; + time_t tstart = wp->cmd_start_time, end; + time_t tend = wp->cmd_end_time; + + ep = event_payload_create(); + cmd_find_from_pane(&fs, wp, 0); + event_payload_set_target(ep, &fs); + if (fs.s != NULL) + event_payload_set_session(ep, "session", fs.s); + if (fs.wl != NULL) + event_payload_set_int(ep, "window_index", fs.wl->idx); + event_payload_set_window(ep, "window", wp->window); + event_payload_set_pane(ep, "pane", wp); + + if (wp->cmd_status != -1) + event_payload_set_int(ep, "command_status", wp->cmd_status); + if (tstart != 0) + event_payload_set_time(ep, "command_start_time", tstart); + if (tend != 0) + event_payload_set_time(ep, "command_end_time", tend); + + if (tstart != 0) { + if (wp->flags & PANE_CMDRUNNING) + end = time(NULL); + else + end = tend; + if (end < tstart) + end = tstart; + end -= tstart; + event_payload_set_uint(ep, "command_duration", end); + } + + events_fire(name, ep); +} + /* Handle the OSC 133 sequence. */ static void input_osc_133(struct input_ctx *ictx, const char *p) { + struct window_pane *wp = ictx->wp; struct grid *gd = ictx->ctx.s->grid; u_int line = ictx->ctx.s->cy + gd->hsize; - struct grid_line *gl; + struct grid_line *gl = NULL; + const char *errstr; + int status; - if (line > gd->hsize + gd->sy - 1) - return; - gl = grid_get_line(gd, line); + if (line <= gd->hsize + gd->sy - 1) + gl = grid_get_line(gd, line); switch (*p) { case 'A': - gl->flags |= GRID_LINE_START_PROMPT; + if (gl != NULL) + gl->flags |= GRID_LINE_START_PROMPT; + if (wp != NULL) { + wp->last_prompt_time = time(NULL); + events_fire_pane("pane-shell-prompt", wp); + } break; case 'C': - gl->flags |= GRID_LINE_START_OUTPUT; + if (gl != NULL) + gl->flags |= GRID_LINE_START_OUTPUT; + if (wp != NULL) { + wp->cmd_start_time = time(NULL); + wp->cmd_end_time = 0; + wp->flags |= PANE_CMDRUNNING; + wp->cmd_status = -1; + input_fire_command_event(wp, "pane-command-started"); + } + break; + case 'D': + if (wp != NULL) { + wp->cmd_end_time = time(NULL); + wp->flags &= ~PANE_CMDRUNNING; + wp->cmd_status = -1; + if (p[1] == ';' && p[2] != '\0') { + status = strtonum(p + 2, 0, INT_MAX, &errstr); + if (errstr == NULL) + wp->cmd_status = status; + } + input_fire_command_event(wp, "pane-command-finished"); + } break; } } @@ -3289,7 +3377,7 @@ input_osc_52(struct input_ctx *ictx, const char *p) screen_write_start_pane(&ctx, wp, NULL); screen_write_setselection(&ctx, clip, out, outlen); screen_write_stop(&ctx); - notify_pane("pane-set-clipboard", wp); + events_fire_pane("pane-set-clipboard", wp); paste_add(NULL, out, outlen); } } diff --git a/monitor.c b/monitor.c index 6d1a747fd..114f7b1da 100644 --- a/monitor.c +++ b/monitor.c @@ -1,7 +1,7 @@ /* $OpenBSD$ */ /* - * Copyright (c) 2026 Nicholas Marriott + * Copyright (c) 2026 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above @@ -53,7 +53,7 @@ struct monitor_item { enum monitor_type type; u_int id; - u_int flags; + int flags; char *last; struct monitor_panes panes; @@ -198,7 +198,9 @@ monitor_check_value(struct monitor_set *ms, struct monitor_item *me, { if (*last == NULL) { *last = value; - if (me->flags & MONITOR_NOTIFY_INITIAL) + if ((me->flags & MONITOR_NOTIFY_INITIAL) && + ((~me->flags & MONITOR_NOTIFY_TRUE) || + format_true(value))) monitor_report(ms, me, s, wl, wp, value, NULL); return; } @@ -208,7 +210,8 @@ monitor_check_value(struct monitor_set *ms, struct monitor_item *me, return; } - monitor_report(ms, me, s, wl, wp, value, *last); + if ((~me->flags & MONITOR_NOTIFY_TRUE) || format_true(value)) + monitor_report(ms, me, s, wl, wp, value, *last); free(*last); *last = value; } @@ -621,7 +624,7 @@ fail: /* Add a subscription. */ void monitor_add(struct monitor_set *ms, const char *name, enum monitor_type type, - int id, const char *format, u_int flags) + int id, const char *format, int flags) { struct monitor_item *me, find = { .name = (char *)name }; struct timeval tv = { .tv_sec = 1 }; diff --git a/options-table.c b/options-table.c index afcfea18a..9441a427a 100644 --- a/options-table.c +++ b/options-table.c @@ -1932,12 +1932,22 @@ const struct options_table_entry options_table[] = { OPTIONS_TABLE_HOOK("client-light-theme", ""), OPTIONS_TABLE_HOOK("client-dark-theme", ""), OPTIONS_TABLE_HOOK("command-error", ""), + OPTIONS_TABLE_HOOK("marked-pane-changed", ""), + OPTIONS_TABLE_PANE_HOOK("pane-command-finished", ""), + OPTIONS_TABLE_PANE_HOOK("pane-command-started", ""), + OPTIONS_TABLE_PANE_HOOK("pane-created", ""), OPTIONS_TABLE_PANE_HOOK("pane-died", ""), OPTIONS_TABLE_PANE_HOOK("pane-exited", ""), OPTIONS_TABLE_PANE_HOOK("pane-focus-in", ""), OPTIONS_TABLE_PANE_HOOK("pane-focus-out", ""), OPTIONS_TABLE_PANE_HOOK("pane-mode-changed", ""), + OPTIONS_TABLE_PANE_HOOK("pane-mode-entered", ""), + OPTIONS_TABLE_PANE_HOOK("pane-mode-exited", ""), + OPTIONS_TABLE_PANE_HOOK("pane-prompt-closed", ""), + OPTIONS_TABLE_PANE_HOOK("pane-prompt-opened", ""), + OPTIONS_TABLE_PANE_HOOK("pane-resized", ""), OPTIONS_TABLE_PANE_HOOK("pane-set-clipboard", ""), + OPTIONS_TABLE_PANE_HOOK("pane-shell-prompt", ""), OPTIONS_TABLE_PANE_HOOK("pane-title-changed", ""), OPTIONS_TABLE_HOOK("session-closed", ""), OPTIONS_TABLE_HOOK("session-created", ""), @@ -1948,6 +1958,8 @@ const struct options_table_entry options_table[] = { OPTIONS_TABLE_WINDOW_HOOK("window-pane-changed", ""), OPTIONS_TABLE_WINDOW_HOOK("window-renamed", ""), OPTIONS_TABLE_WINDOW_HOOK("window-resized", ""), + OPTIONS_TABLE_WINDOW_HOOK("window-unzoomed", ""), + OPTIONS_TABLE_WINDOW_HOOK("window-zoomed", ""), OPTIONS_TABLE_HOOK("window-unlinked", ""), { .name = NULL } diff --git a/tmux.1 b/tmux.1 index dfca7fe34..e4b708aaa 100644 --- a/tmux.1 +++ b/tmux.1 @@ -6325,11 +6325,7 @@ For example, this could be used to write to a log file: set\-hook \-g command\-error "run\-shell \\"echo 'a tmux command failed' >>/tmp/log\\"" .Ed .Pp -All the notifications listed in the -.Sx CONTROL MODE -section are hooks (without any arguments), except -.Ic %exit . -The following additional hooks are available: +The following hooks are available in addition to after hooks: .Bl -tag -width "XXXXXXXXXXXXXXXXXXXXXX" .It alert\-activity Run when a window has activity. @@ -6348,11 +6344,11 @@ Run when a client becomes the latest active client of its session. .It client\-attached Run when a client is attached. .It client\-detached -Run when a client is detached +Run when a client is detached. .It client\-focus\-in -Run when focus enters a client +Run when focus enters a client. .It client\-focus\-out -Run when focus exits a client +Run when focus exits a client. .It client\-resized Run when a client is resized. .It client\-session\-changed @@ -6363,6 +6359,21 @@ Run when a client switches to a light theme. Run when a client switches to a dark theme. .It command\-error Run when a command fails. +.It marked\-pane\-changed +Run when the marked pane is set or cleared. +.It pane\-command\-finished +Run when an OSC 133 command finishes in a pane. +The event payload includes +.Ql hook_command_start_time , +.Ql hook_command_end_time , +.Ql hook_command_duration +and +.Ql hook_command_status +if present. +.It pane\-command\-started +Run when an OSC 133 command starts in a pane. +.It pane\-created +Run when a pane is created or respawned. .It pane\-died Run when the program running in a pane exits, but .Ic remain\-on\-exit @@ -6377,20 +6388,40 @@ option is on. Run when the focus exits a pane, if the .Ic focus\-events option is on. +.It pane\-mode\-changed +Run when a pane changes mode. +.It pane\-mode\-entered +Run when a pane enters a mode. +.It pane\-mode\-exited +Run when a pane exits a mode. +.It pane\-prompt\-closed +Run when a prompt in a pane is closed. +.It pane\-prompt\-opened +Run when a prompt is opened in a pane. +.It pane\-resized +Run when a pane is resized. .It pane\-set\-clipboard Run when the terminal clipboard is set using the .Xr xterm 1 escape sequence. -.It session\-created -Run when a new session created. +.It pane\-shell\-prompt +Run when an OSC 133 shell prompt starts in a pane. +.It pane\-title\-changed +Run when a pane title is changed. .It session\-closed -Run when a session closed. +Run when a session is closed. +.It session\-created +Run when a new session is created. .It session\-renamed Run when a session is renamed. +.It session\-window\-changed +Run when a session changes its active window. .It window\-layout\-changed Run when a window layout is changed. .It window\-linked Run when a window is linked into a session. +.It window\-pane\-changed +Run when a window changes its active pane. .It window\-renamed Run when a window is renamed. .It window\-resized @@ -6398,6 +6429,10 @@ Run when a window is resized. This may be after the .Ar client\-resized hook is run. +.It window\-unzoomed +Run when a window is unzoomed. +.It window\-zoomed +Run when a window is zoomed. .It window\-unlinked Run when a window is unlinked from a session. .El @@ -6405,13 +6440,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 agpERTuw .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 @@ -6434,6 +6471,11 @@ is the hook to run, selects the session, pane, all panes, window, or all windows, and .Ar format is expanded once a second. +With +.Fl T , +the hook is run only when +.Ar format +is true. For monitor hooks, .Ar name must begin with @@ -6452,6 +6494,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 @@ -7161,6 +7211,11 @@ The following variables are available, where appropriate: .It Li "pane_at_top" Ta "" Ta "1 if pane is at the top of window" .It Li "pane_bg" Ta "" Ta "Pane background colour" .It Li "pane_bottom" Ta "" Ta "Bottom of pane" +.It Li "pane_command_duration" Ta "" Ta "Current or most recent OSC 133 command duration in seconds" +.It Li "pane_command_end_time" Ta "" Ta "Time most recent OSC 133 command ended" +.It Li "pane_command_running" Ta "" Ta "1 if an OSC 133 command is running" +.It Li "pane_command_start_time" Ta "" Ta "Time current or most recent OSC 133 command started" +.It Li "pane_command_status" Ta "" Ta "Exit status from most recent OSC 133 command" .It Li "pane_current_command" Ta "" Ta "Current command if available" .It Li "pane_current_path" Ta "" Ta "Current path if available" .It Li "pane_dead" Ta "" Ta "1 if pane is dead" @@ -7178,6 +7233,8 @@ The following variables are available, where appropriate: .It Li "pane_input_off" Ta "" Ta "1 if input to pane is disabled" .It Li "pane_key_mode" Ta "" Ta "Extended key reporting mode in this pane" .It Li "pane_last" Ta "" Ta "1 if last pane" +.It Li "pane_last_output_time" Ta "" Ta "Time pane last produced output" +.It Li "pane_last_prompt_time" Ta "" Ta "Time most recent OSC 133 prompt began" .It Li "pane_left" Ta "" Ta "Left of pane" .It Li "pane_marked" Ta "" Ta "1 if this is the marked pane" .It Li "pane_marked_set" Ta "" Ta "1 if a marked pane is set" @@ -8562,8 +8619,10 @@ 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 L | S | U -.Ar channel +.Op Fl ELSUlv +.Op Fl F Ar format +.Op Fl w Ar waiter +.Ar name .Xc .D1 Pq alias: Ic wait When used without options, prevents the client from exiting until woken using @@ -8576,6 +8635,41 @@ 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 +.Ar name . +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 +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). +.Pp +.Fl l +list the waiters for +.Ar name +and +.Fl w +wakes +.Ar waiter +on +.Ar name +immediately. .El .Sh EXIT MESSAGES When a diff --git a/tmux.h b/tmux.h index db46e6fb1..2d88c5255 100644 --- a/tmux.h +++ b/tmux.h @@ -49,6 +49,9 @@ struct cmdq_state; struct cmds; struct control_state; struct environ; +struct event_payload; +struct event_payload_item; +struct events_sink; struct format_job_tree; struct format_tree; struct hyperlinks_uri; @@ -1298,6 +1301,7 @@ struct window_pane { #define PANE_UNSEENCHANGES 0x4000 #define PANE_REDRAWSCROLLBAR 0x8000 #define PANE_DESTROYED 0x10000 +#define PANE_CMDRUNNING 0x20000 bitstr_t *sync_dirty; u_int sync_dirty_size; @@ -1320,6 +1324,12 @@ struct window_pane { struct cmdq_item *wait_item; /* new-pane -W: waiting for pane exit */ struct spawn_editor_state *editor; + time_t last_output_time; + time_t last_prompt_time; + time_t cmd_start_time; + time_t cmd_end_time; + int cmd_status; + int fd; struct bufferevent *event; @@ -2331,6 +2341,7 @@ enum monitor_type { MONITOR_ALL_WINDOWS }; #define MONITOR_NOTIFY_INITIAL 0x1 +#define MONITOR_NOTIFY_TRUE 0x2 struct monitor_change { const char *name; const char *value; @@ -2343,6 +2354,23 @@ struct monitor_change { }; typedef void (*monitor_cb)(struct monitor_change *, void *); +/* Event payload type. */ +enum event_payload_type { + EVENT_PAYLOAD_STRING, + EVENT_PAYLOAD_TIME, + EVENT_PAYLOAD_INT, + EVENT_PAYLOAD_UINT, + EVENT_PAYLOAD_CLIENT, + EVENT_PAYLOAD_SESSION, + EVENT_PAYLOAD_WINDOW, + EVENT_PAYLOAD_PANE, + EVENT_PAYLOAD_POINTER +}; + +/* Event payload callbacks. */ +typedef void (*event_payload_free_cb)(void *); +typedef void (*event_payload_print_cb)(void *, struct evbuffer *); + /* Key binding and key table. */ struct key_binding { key_code key; @@ -2652,29 +2680,78 @@ char *format_grid_hyperlink(struct grid *, u_int, u_int, struct screen *); char *format_grid_line(struct grid *, u_int); -/* format-draw.c */ -void format_draw(struct screen_write_ctx *, - const struct grid_cell *, u_int, const char *, - struct style_ranges *, int); -u_int format_width(const char *); -char *format_trim_left(const char *, u_int); -char *format_trim_right(const char *, u_int); +/* events-payload.c */ +struct event_payload *event_payload_create(void); +void event_payload_free(struct event_payload *); +void printflike(2, 3) event_payload_log(struct event_payload *, const char *, + ...); +char *event_payload_item_print(struct event_payload_item *); +void event_payload_set_target(struct event_payload *, + struct cmd_find_state *); +int event_payload_get_target(struct event_payload *, + struct cmd_find_state *); +void printflike(3, 4) event_payload_set_string(struct event_payload *, + const char *, const char *, ...); +void event_payload_set_time(struct event_payload *, const char *, time_t); +void event_payload_set_int(struct event_payload *, const char *, int); +void event_payload_set_uint(struct event_payload *, const char *, u_int); +void event_payload_set_client(struct event_payload *, const char *, + struct client *); +void event_payload_set_session(struct event_payload *, const char *, + struct session *); +void event_payload_set_window(struct event_payload *, const char *, + struct window *); +void event_payload_set_pane(struct event_payload *, const char *, + struct window_pane *); +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 *); +enum event_payload_type event_payload_item_type(struct event_payload_item *); +time_t event_payload_get_time(struct event_payload *, const char *); +int event_payload_get_int(struct event_payload *, const char *, int *); +int event_payload_get_uint(struct event_payload *, const char *, u_int *); +struct client *event_payload_get_client(struct event_payload *, const char *); +struct session *event_payload_get_session(struct event_payload *, const char *); +struct window *event_payload_get_window(struct event_payload *, const char *); +struct window_pane *event_payload_get_pane(struct event_payload *, + const char *); +void *event_payload_get_pointer(struct event_payload *, const char *); -/* notify.c */ -void notify_hook(struct cmdq_item *, const char *); -void notify_monitor_add(struct cmdq_item *, struct options *, +/* events.c */ +typedef void (*events_cb)(const char *, struct event_payload *, void *); +struct events_sink *events_add_sink(const char *, events_cb, void *); +void events_remove_sink(struct events_sink *); +void events_fire(const char *, struct event_payload *); +void events_fire_client(const char *, struct client *); +void events_fire_session(const char *, struct session *); +void events_fire_window(const char *, struct window *); +void events_fire_pane(const char *, struct window_pane *); +void events_fire_winlink(const char *, struct winlink *); + +/* format-draw.c */ +void format_draw(struct screen_write_ctx *, const struct grid_cell *, + u_int, const char *, struct style_ranges *, int); +u_int format_width(const char *); +char *format_trim_left(const char *, u_int); +char *format_trim_right(const char *, u_int); + +/* hooks.c */ +void hooks_add_event(const char *); +int hooks_valid_event_name(const char *); +void hooks_build_events(void); +void hooks_run(struct cmdq_item *, const char *); +void hooks_monitor_add(struct cmdq_item *, struct options *, const char *, enum monitor_type, int, const char *, - struct cmd_find_state *, struct session *); -void notify_monitor_remove(struct options *, const char *); -void notify_monitor_free(void *); -char *notify_monitor_to_string(struct options_entry *); -void notify_client(const char *, struct client *); -void notify_session(const char *, struct session *); -void notify_winlink(const char *, struct winlink *); -void notify_session_window(const char *, struct session *, struct window *); -void notify_window(const char *, struct window *); -void notify_pane(const char *, struct window_pane *); -void notify_paste_buffer(const char *, int); + int, struct cmd_find_state *, struct session *); +void hooks_monitor_remove(struct options *, const char *); +void hooks_monitor_free(void *); +char *hooks_monitor_to_string(struct options_entry *); /* options.c */ struct options *options_create(struct options *); @@ -3882,7 +3959,7 @@ void monitor_destroy(struct monitor_set *); int monitor_parse(const char *, char **, enum monitor_type *, int *, char **); void monitor_add(struct monitor_set *, const char *, enum monitor_type, int, - const char *, u_int); + const char *, int); void monitor_remove(struct monitor_set *, const char *); /* control.c */ @@ -3905,20 +3982,7 @@ void control_add_sub(struct client *, const char *, enum monitor_type, int, void control_remove_sub(struct client *, const char *); /* control-notify.c */ -void control_notify_pane_mode_changed(int); -void control_notify_window_layout_changed(struct window *); -void control_notify_window_pane_changed(struct window *); -void control_notify_window_unlinked(struct session *, struct window *); -void control_notify_window_linked(struct session *, struct window *); -void control_notify_window_renamed(struct window *); -void control_notify_client_session_changed(struct client *); -void control_notify_client_detached(struct client *); -void control_notify_session_renamed(struct session *); -void control_notify_session_created(struct session *); -void control_notify_session_closed(struct session *); -void control_notify_session_window_changed(struct session *); -void control_notify_paste_buffer_changed(const char *); -void control_notify_paste_buffer_deleted(const char *); +void control_build_events(void); /* session.c */ extern struct sessions sessions; diff --git a/window.c b/window.c index 298491604..4b9c37d5a 100644 --- a/window.c +++ b/window.c @@ -73,8 +73,8 @@ static struct window_pane *window_pane_create(struct window *, u_int, u_int, static void window_pane_destroy(struct window_pane *); static void window_pane_free(struct window_pane *); static void window_pane_scrollbar_timer(int, short, void *); -static void window_pane_full_size_offset(struct window_pane *wp, - int *xoff, int *yoff, u_int *sx, u_int *sy); +static void window_pane_full_size_offset(struct window_pane *, int *, int *, + u_int *, u_int *); RB_GENERATE(windows, window, entry, window_cmp); RB_GENERATE(winlinks, winlink, entry, winlink_cmp); @@ -86,6 +86,7 @@ struct window_pane_prompt { status_prompt_input_cb inputcb; prompt_free_cb freecb; void *data; + enum prompt_type type; }; int @@ -94,6 +95,78 @@ window_cmp(struct window *w1, struct window *w2) return (w1->id - w2->id); } +static void +window_fire_renamed(struct window *w, const char *old_name) +{ + struct event_payload *ep; + struct cmd_find_state fs; + + ep = event_payload_create(); + cmd_find_from_window(&fs, w, 0); + event_payload_set_target(ep, &fs); + event_payload_set_window(ep, "window", w); + event_payload_set_string(ep, "old_name", "%s", old_name); + event_payload_set_string(ep, "new_name", "%s", w->name); + events_fire("window-renamed", ep); +} + +static void +window_fire_pane_changed(struct window *w, struct window_pane *wp, + struct window_pane *lastwp) +{ + struct event_payload *ep; + struct cmd_find_state fs; + + ep = event_payload_create(); + cmd_find_from_pane(&fs, wp, 0); + event_payload_set_target(ep, &fs); + event_payload_set_window(ep, "window", w); + event_payload_set_pane(ep, "pane", wp); + event_payload_set_pane(ep, "new_pane", wp); + if (lastwp != NULL) + event_payload_set_pane(ep, "old_pane", lastwp); + events_fire("window-pane-changed", ep); +} + +static void +window_fire_pane_mode_changed(const char *name, struct window_pane *wp, + const char *previous, const char *current, int entered) +{ + struct event_payload *ep; + struct cmd_find_state fs; + + ep = event_payload_create(); + cmd_find_from_pane(&fs, wp, 0); + event_payload_set_target(ep, &fs); + event_payload_set_pane(ep, "pane", wp); + event_payload_set_window(ep, "window", wp->window); + + if (current != NULL) + event_payload_set_string(ep, "current_mode", "%s", current); + if (previous != NULL) + event_payload_set_string(ep, "previous_mode", "%s", previous); + event_payload_set_int(ep, "mode_entered", entered); + + events_fire(name, ep); +} + +static void +window_fire_pane_prompt(const char *name, struct window_pane *wp, + enum prompt_type type) +{ + struct event_payload *ep; + struct cmd_find_state fs; + const char *type_string = prompt_type_string(type); + + ep = event_payload_create(); + cmd_find_from_pane(&fs, wp, 0); + event_payload_set_target(ep, &fs); + event_payload_set_pane(ep, "pane", wp); + event_payload_set_window(ep, "window", wp->window); + event_payload_set_string(ep, "prompt_type", "%s", type_string); + events_fire(name, ep); +} + int winlink_cmp(struct winlink *wl1, struct winlink *wl2) { @@ -444,13 +517,15 @@ window_pane_remove_ref(struct window_pane *wp, const char *from) void window_set_name(struct window *w, const char *new_name, int untrusted) { - char *name; + char *last, *name; name = clean_name(new_name, untrusted); if (name != NULL) { + last = xstrdup(w->name); free(w->name); w->name = name; - notify_window("window-renamed", w); + window_fire_renamed(w, last); + free(last); } } @@ -560,13 +635,13 @@ window_pane_update_focus(struct window_pane *wp) log_debug("%s: %%%u focus out", __func__, wp->id); if (wp->base.mode & MODE_FOCUSON) bufferevent_write(wp->event, "\033[O", 3); - notify_pane("pane-focus-out", wp); + events_fire_pane("pane-focus-out", wp); wp->flags &= ~PANE_FOCUSED; } else if (focused && (~wp->flags & PANE_FOCUSED)) { log_debug("%s: %%%u focus in", __func__, wp->id); if (wp->base.mode & MODE_FOCUSON) bufferevent_write(wp->event, "\033[I", 3); - notify_pane("pane-focus-in", wp); + events_fire_pane("pane-focus-in", wp); wp->flags |= PANE_FOCUSED; } else log_debug("%s: %%%u focus unchanged", __func__, wp->id); @@ -602,7 +677,7 @@ window_set_active_pane(struct window *w, struct window_pane *wp, int notify) server_redraw_window(w); if (notify) - notify_window("window-pane-changed", w); + window_fire_pane_changed(w, w->active, lastwp); return (1); } @@ -792,7 +867,8 @@ window_zoom(struct window_pane *wp) w->saved_layout_root = w->layout_root; layout_init(w, wp); w->flags |= WINDOW_ZOOMED; - notify_window("window-layout-changed", w); + events_fire_window("window-zoomed", w); + events_fire_window("window-layout-changed", w); redraw_invalidate_scene(w); return (0); @@ -818,8 +894,10 @@ window_unzoom(struct window *w, int notify) } layout_fix_panes(w, NULL); - if (notify) - notify_window("window-layout-changed", w); + if (notify) { + events_fire_window("window-unzoomed", w); + events_fire_window("window-layout-changed", w); + } redraw_invalidate_scene(w); return (0); @@ -901,7 +979,7 @@ window_lost_pane(struct window *w, struct window_pane *wp) if (w->active != NULL) { window_pane_stack_remove(&w->last_panes, w->active); w->active->flags |= PANE_CHANGED; - notify_window("window-pane-changed", w); + events_fire_window("window-pane-changed", w); window_update_focus(w); } } @@ -1106,6 +1184,7 @@ window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit) wp->window = w; wp->options = options_create(w->options); wp->flags = PANE_STYLECHANGED; + wp->cmd_status = -1; wp->id = next_window_pane_id++; RB_INSERT(window_pane_tree, &all_window_panes, wp); @@ -1230,9 +1309,9 @@ window_pane_destroy(struct window_pane *wp) window_pane_wait_finish(wp); spawn_editor_finish(wp); - window_pane_clear_prompt(wp); RB_REMOVE(window_pane_tree, &all_window_panes, wp); wp->flags |= PANE_DESTROYED; + window_pane_clear_prompt(wp); window_pane_free_modes(wp); screen_write_clear_dirty(wp); @@ -1363,6 +1442,8 @@ window_pane_resize(struct window_pane *wp, u_int sx, u_int sy) { struct window_mode_entry *wme; struct window_pane_resize *r; + struct event_payload *ep; + struct cmd_find_state fs; if (sx == wp->sx && sy == wp->sy) return; @@ -1385,6 +1466,17 @@ window_pane_resize(struct window_pane *wp, u_int sx, u_int sy) wme = TAILQ_FIRST(&wp->modes); if (wme != NULL && wme->mode->resize != NULL) wme->mode->resize(wme, sx, sy); + + ep = event_payload_create(); + cmd_find_from_pane(&fs, wp, 0); + event_payload_set_target(ep, &fs); + event_payload_set_pane(ep, "pane", wp); + event_payload_set_window(ep, "window", wp->window); + event_payload_set_uint(ep, "width", sx); + event_payload_set_uint(ep, "height", sy); + event_payload_set_uint(ep, "old_width", r->osx); + event_payload_set_uint(ep, "old_height", r->osy); + events_fire("pane-resized", ep); } int @@ -1394,9 +1486,13 @@ window_pane_set_mode(struct window_pane *wp, struct window_pane *swp, { struct window_mode_entry *wme; struct window *w = wp->window; + const char *name = mode->name, *p = NULL; - if (!TAILQ_EMPTY(&wp->modes) && TAILQ_FIRST(&wp->modes)->mode == mode) - return (1); + if (!TAILQ_EMPTY(&wp->modes)) { + if (TAILQ_FIRST(&wp->modes)->mode == mode) + return (1); + p = TAILQ_FIRST(&wp->modes)->mode->name; + } TAILQ_FOREACH(wme, &wp->modes, entry) { if (wme->mode == mode) @@ -1422,7 +1518,9 @@ window_pane_set_mode(struct window_pane *wp, struct window_pane *swp, server_redraw_window_borders(wp->window); server_status_window(wp->window); - notify_pane("pane-mode-changed", wp); + + window_fire_pane_mode_changed("pane-mode-entered", wp, p, name, 1); + window_fire_pane_mode_changed("pane-mode-changed", wp, p, name, 1); return (0); } @@ -1433,11 +1531,13 @@ window_pane_reset_mode(struct window_pane *wp) struct window_mode_entry *wme, *next; struct window *w = wp->window; int kill; + const char *name, *p; if (TAILQ_EMPTY(&wp->modes)) return; wme = TAILQ_FIRST(&wp->modes); + p = wme->mode->name; kill = wme->kill; TAILQ_REMOVE(&wp->modes, wme, entry); wme->mode->free(wme); @@ -1454,13 +1554,16 @@ window_pane_reset_mode(struct window_pane *wp) if (next->mode->resize != NULL) next->mode->resize(next, wp->sx, wp->sy); } + name = (next == NULL ? NULL : next->mode->name); wp->flags |= (PANE_REDRAW|PANE_REDRAWSCROLLBAR|PANE_CHANGED); layout_fix_panes(w, NULL); server_redraw_window_borders(wp->window); server_status_window(wp->window); - notify_pane("pane-mode-changed", wp); + + window_fire_pane_mode_changed("pane-mode-exited", wp, p, name, 0); + window_fire_pane_mode_changed("pane-mode-changed", wp, p, name, 0); if (kill) server_kill_pane(wp); @@ -1523,6 +1626,7 @@ window_pane_set_prompt(struct window_pane *wp, struct client *c, wpp->inputcb = inputcb; wpp->freecb = freecb; wpp->data = data; + wpp->type = type; memset(&pd, 0, sizeof pd); prompt_set_options(&pd, s); @@ -1540,19 +1644,28 @@ window_pane_set_prompt(struct window_pane *wp, struct client *c, wp->flags |= PANE_REDRAW; prompt_incremental_start(wp->prompt); + window_fire_pane_prompt("pane-prompt-opened", wp, type); } /* Close a pane prompt. */ void window_pane_clear_prompt(struct window_pane *wp) { - struct prompt *prompt = wp->prompt; + struct prompt *prompt = wp->prompt; + struct window_pane_prompt *wpp = wp->prompt_data; + enum prompt_type type = PROMPT_TYPE_INVALID; - if (prompt == NULL) - return; - wp->prompt = NULL; - prompt_free(prompt); - wp->flags |= PANE_REDRAW; + if (prompt != NULL) { + if (wpp != NULL) + type = wpp->type; + + wp->prompt = NULL; + prompt_free(prompt); + wp->flags |= PANE_REDRAW; + + if (~wp->flags & PANE_DESTROYED) + window_fire_pane_prompt("pane-prompt-closed", wp, type); + } } /* Does this pane have an open prompt? */