From de024e04d7c5f65c95bc41e2e90cd137f81ef1de Mon Sep 17 00:00:00 2001 From: nicm Date: Sun, 5 Jul 2026 08:24:00 +0000 Subject: [PATCH] Add set-hook -B install a subscription like in control mode. Subscriptions are formats which are checked once a second and if changed invoke a hook. show-hooks -B lists. --- cmd-refresh-client.c | 37 ++----- cmd-set-option.c | 101 +++++++++++++++++- cmd-show-options.c | 47 ++++++++- control.c | 4 +- monitor.c | 244 ++++++++++++++++++++++++++++++++----------- notify.c | 218 +++++++++++++++++++++++++++++++++++--- options.c | 15 +++ tmux.1 | 41 +++++++- tmux.h | 17 ++- 9 files changed, 607 insertions(+), 117 deletions(-) diff --git a/cmd-refresh-client.c b/cmd-refresh-client.c index 16269e249..6f22da83e 100644 --- a/cmd-refresh-client.c +++ b/cmd-refresh-client.c @@ -46,36 +46,17 @@ const struct cmd_entry cmd_refresh_client_entry = { static void cmd_refresh_client_update_subscription(struct client *tc, const char *value) { - char *copy, *split, *name, *what; - enum monitor_type subtype; - int subid = -1; + char *name, *format; + enum monitor_type type; + int id; - copy = name = xstrdup(value); - if ((split = strchr(copy, ':')) == NULL) { - control_remove_sub(tc, copy); - goto out; + if (monitor_parse(value, &name, &type, &id, &format) != 0) { + control_remove_sub(tc, value); + return; } - *split++ = '\0'; - - what = split; - if ((split = strchr(what, ':')) == NULL) - goto out; - *split++ = '\0'; - - if (strcmp(what, "%*") == 0) - subtype = MONITOR_ALL_PANES; - else if (sscanf(what, "%%%d", &subid) == 1 && subid >= 0) - subtype = MONITOR_PANE; - else if (strcmp(what, "@*") == 0) - subtype = MONITOR_ALL_WINDOWS; - else if (sscanf(what, "@%d", &subid) == 1 && subid >= 0) - subtype = MONITOR_WINDOW; - else - subtype = MONITOR_SESSION; - control_add_sub(tc, name, subtype, subid, split); - -out: - free(copy); + control_add_sub(tc, name, type, id, format); + free(name); + free(format); } static enum cmd_retval diff --git a/cmd-set-option.c b/cmd-set-option.c index fbe32cfae..5b5ea3d88 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_monitor_exec(struct cmdq_item *, + struct args *, int); const struct cmd_entry cmd_set_option_entry = { .name = "set-option", @@ -62,8 +64,9 @@ const struct cmd_entry cmd_set_hook_entry = { .name = "set-hook", .alias = NULL, - .args = { "agpRt:uw", 1, 2, cmd_set_option_args_parse }, - .usage = "[-agpRuw] " CMD_TARGET_PANE_USAGE " hook [command]", + .args = { "agpRt:uB:w", 0, 2, cmd_set_option_args_parse }, + .usage = "[-agpRuw] [-B name:what:format] " CMD_TARGET_PANE_USAGE " " + "[hook] [command]", .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL }, @@ -72,14 +75,100 @@ const struct cmd_entry cmd_set_hook_entry = { }; static enum args_parse_type -cmd_set_option_args_parse(__unused struct args *args, u_int idx, +cmd_set_option_args_parse(struct args *args, u_int idx, __unused char **cause) { + if (args_has(args, 'B')) + return (ARGS_PARSE_COMMANDS_OR_STRING); if (idx == 1) return (ARGS_PARSE_COMMANDS_OR_STRING); return (ARGS_PARSE_STRING); } +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; + char *cause = NULL, *name = NULL, *format = NULL; + char *expanded = NULL, *newvalue = NULL; + const char *value, *old; + enum monitor_type type; + int id, scope; + + if (args_count(args) > 1) { + cmdq_error(item, "too many arguments"); + return (CMD_RETURN_ERROR); + } + + value = args_get(args, 'B'); + if (args_has(args, 'u')) { + if (monitor_parse(value, &name, &type, &id, &format) != 0) + name = xstrdup(value); + free(format); + format = NULL; + } else { + if (monitor_parse(value, &name, &type, &id, &format) != 0) { + cmdq_error(item, "invalid subscription: %s", value); + return (CMD_RETURN_ERROR); + } + } + + if (*name != '@') { + cmdq_error(item, "monitor hook name must start with @"); + goto fail; + } + + scope = options_scope_from_name(args, window, name, target, &oo, + &cause); + if (scope == OPTIONS_TABLE_NONE) { + cmdq_error(item, "%s", cause); + free(cause); + goto fail; + } + cmd_find_copy_state(&fs, target); + + if (args_has(args, 'u')) { + notify_monitor_remove(oo, name); + goto out; + } + + if (args_count(args) != 0) { + value = args_string(args, 0); + if (args_has(args, 'F')) { + expanded = format_single_from_target(item, value); + value = expanded; + } + o = options_get_only(oo, name); + if (!args_has(args, 'o') || o == NULL) { + if (args_has(args, 'a') && o != NULL) { + old = options_get_string(oo, name); + xasprintf(&newvalue, "%s%s", old, value); + value = newvalue; + } + options_set_string(oo, name, 0, "%s", value); + options_push_changes(name); + } + } + + notify_monitor_add(item, oo, name, type, id, format, &fs, target->s); + +out: + free(newvalue); + free(expanded); + free(name); + free(format); + return (CMD_RETURN_NORMAL); + +fail: + free(newvalue); + free(expanded); + free(name); + free(format); + return (CMD_RETURN_ERROR); +} + static enum cmd_retval cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) { @@ -96,6 +185,12 @@ 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, 'B')) + return (cmd_set_hook_monitor_exec(item, args, window)); + if (args_count(args) == 0) { + cmdq_error(item, "missing argument"); + return (CMD_RETURN_ERROR); + } /* Expand argument. */ argument = format_single_from_target(item, args_string(args, 0)); diff --git a/cmd-show-options.c b/cmd-show-options.c index 684940607..0a898156a 100644 --- a/cmd-show-options.c +++ b/cmd-show-options.c @@ -32,6 +32,10 @@ static enum cmd_retval cmd_show_options_exec(struct cmd *, struct cmdq_item *); static void cmd_show_options_print(struct cmd *, struct cmdq_item *, struct options_entry *, int, int); +static void cmd_show_hooks_print_monitor(struct cmdq_item *, + struct options_entry *); +static enum cmd_retval cmd_show_hooks_monitor(struct cmd *, struct cmdq_item *, + int, struct options *); static enum cmd_retval cmd_show_options_all(struct cmd *, struct cmdq_item *, int, struct options *); @@ -65,8 +69,8 @@ const struct cmd_entry cmd_show_hooks_entry = { .name = "show-hooks", .alias = NULL, - .args = { "gpt:w", 0, 1, NULL }, - .usage = "[-gpw] " CMD_TARGET_PANE_USAGE " [hook]", + .args = { "Bgpt:w", 0, 1, NULL }, + .usage = "[-Bgpw] " CMD_TARGET_PANE_USAGE " [hook]", .target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL }, @@ -96,6 +100,9 @@ cmd_show_options_exec(struct cmd *self, struct cmdq_item *item) free(cause); return (CMD_RETURN_ERROR); } + if (cmd_get_entry(self) == &cmd_show_hooks_entry && + args_has(args, 'B')) + return (cmd_show_hooks_monitor(self, item, scope, oo)); return (cmd_show_options_all(self, item, scope, oo)); } argument = format_single_from_target(item, args_string(args, 0)); @@ -125,8 +132,13 @@ cmd_show_options_exec(struct cmd *self, struct cmdq_item *item) parent = 1; } else parent = 0; - if (o != NULL) - cmd_show_options_print(self, item, o, idx, parent); + if (o != NULL) { + if (cmd_get_entry(self) == &cmd_show_hooks_entry && + args_has(args, 'B')) + cmd_show_hooks_print_monitor(item, o); + else + cmd_show_options_print(self, item, o, idx, parent); + } else if (*name == '@') { if (args_has(args, 'q')) goto out; @@ -196,6 +208,33 @@ cmd_show_options_print(struct cmd *self, struct cmdq_item *item, free(tmp); } +static void +cmd_show_hooks_print_monitor(struct cmdq_item *item, struct options_entry *o) +{ + char *value; + + value = notify_monitor_to_string(o); + if (value == NULL) + return; + cmdq_print(item, "%s", value); + free(value); +} + +/* Show all hook monitors. */ +static enum cmd_retval +cmd_show_hooks_monitor(__unused struct cmd *self, struct cmdq_item *item, + __unused int scope, struct options *oo) +{ + struct options_entry *o; + + o = options_first(oo); + while (o != NULL) { + cmd_show_hooks_print_monitor(item, o); + o = options_next(o); + } + return (CMD_RETURN_NORMAL); +} + static enum cmd_retval cmd_show_options_all(struct cmd *self, struct cmdq_item *item, int scope, struct options *oo) diff --git a/control.c b/control.c index eb2db98ef..1b026e849 100644 --- a/control.c +++ b/control.c @@ -702,7 +702,7 @@ control_start(struct client *c) RB_INIT(&cs->panes); TAILQ_INIT(&cs->pending_list); TAILQ_INIT(&cs->all_blocks); - cs->subs = monitor_create(c, control_sub_change, NULL); + cs->subs = monitor_create_client(c, control_sub_change, NULL); cs->read_event = bufferevent_new(c->fd, control_read_callback, control_write_callback, control_error_callback, c); @@ -776,7 +776,7 @@ control_add_sub(struct client *c, const char *name, enum monitor_type type, { struct control_state *cs = c->control_state; - monitor_add(cs->subs, name, type, id, format); + monitor_add(cs->subs, name, type, id, format, MONITOR_NOTIFY_INITIAL); } /* Remove a subscription. */ diff --git a/monitor.c b/monitor.c index 512331e6a..5a2408009 100644 --- a/monitor.c +++ b/monitor.c @@ -29,6 +29,7 @@ struct monitor_pane { u_int pane; u_int idx; char *last; + u_int generation; RB_ENTRY(monitor_pane) entry; }; @@ -39,6 +40,7 @@ struct monitor_window { u_int window; u_int idx; char *last; + u_int generation; RB_ENTRY(monitor_window) entry; }; @@ -51,6 +53,7 @@ struct monitor_item { enum monitor_type type; u_int id; + u_int flags; char *last; struct monitor_panes panes; @@ -63,15 +66,33 @@ RB_HEAD(monitor_items, monitor_item); /* Monitored subscription set. */ struct monitor_set { struct client *client; + struct session *session; monitor_cb cb; void *data; struct monitor_items items; struct event timer; + u_int generation; }; static void monitor_timer(__unused int, __unused short, void *); +/* Get the session for this monitor set. */ +static struct session * +monitor_get_session(struct monitor_set *ms) +{ + struct session *s; + + if (ms->client != NULL) + return (ms->client->session); + s = ms->session; + if (s == NULL) + return (RB_MIN(sessions, &sessions)); + if (session_find_by_id(s->id) != s) + return (NULL); + return (s); +} + /* Compare subscriptions. */ static int monitor_item_cmp(struct monitor_item *m1, struct monitor_item *m2) @@ -141,38 +162,56 @@ monitor_free_item(struct monitor_set *ms, struct monitor_item *me) static void monitor_report(struct monitor_set *ms, struct monitor_item *me, struct session *s, struct winlink *wl, struct window_pane *wp, - const char *value) + const char *value, const char *last) { - struct monitor_change change; + struct monitor_change change = { 0 }; + + log_debug("%s: %s changed to %s", __func__, me->name, value); change.name = me->name; change.value = value; + change.last = last; change.c = ms->client; change.s = s; change.wl = wl; change.wp = wp; - log_debug("%s: %s changed to %s", __func__, me->name, value); ms->cb(&change, ms->data); } +/* Check a value against its last value and report if changed. */ +static void +monitor_check_value(struct monitor_set *ms, struct monitor_item *me, + struct session *s, struct winlink *wl, struct window_pane *wp, + char *value, char **last) +{ + if (*last == NULL) { + *last = value; + if (me->flags & MONITOR_NOTIFY_INITIAL) + monitor_report(ms, me, s, wl, wp, value, NULL); + return; + } + + if (strcmp(value, *last) == 0) { + free(value); + return; + } + + monitor_report(ms, me, s, wl, wp, value, *last); + free(*last); + *last = value; +} + /* Check session subscription. */ static void monitor_check_session(struct monitor_set *ms, struct monitor_item *me, struct format_tree *ft) { - struct session *s = ms->client->session; + struct session *s = monitor_get_session(ms); char *value; value = format_expand(ft, me->format); - if (me->last != NULL && strcmp(value, me->last) == 0) { - free(value); - return; - } - - monitor_report(ms, me, s, NULL, NULL, value); - free(me->last); - me->last = value; + monitor_check_value(ms, me, s, NULL, NULL, value, &me->last); } /* Check pane subscription. */ @@ -180,7 +219,7 @@ static void monitor_check_pane(struct monitor_set *ms, struct monitor_item *me) { struct client *c = ms->client; - struct session *s = c->session; + struct session *s = monitor_get_session(ms); struct window_pane *wp; struct window *w; struct winlink *wl; @@ -211,14 +250,7 @@ monitor_check_pane(struct monitor_set *ms, struct monitor_item *me) RB_INSERT(monitor_panes, &me->panes, mp); } - if (mp->last != NULL && strcmp(value, mp->last) == 0) { - free(value); - continue; - } - - monitor_report(ms, me, s, wl, wp, value); - free(mp->last); - mp->last = value; + monitor_check_value(ms, me, s, wl, wp, value, &mp->last); } } @@ -227,7 +259,7 @@ static void monitor_check_all_panes_one(struct monitor_set *ms, struct monitor_item *me, struct format_tree *ft, struct winlink *wl, struct window_pane *wp) { - struct session *s = ms->client->session; + struct session *s = monitor_get_session(ms); char *value; struct monitor_pane *mp, find; @@ -242,15 +274,24 @@ monitor_check_all_panes_one(struct monitor_set *ms, struct monitor_item *me, mp->idx = wl->idx; RB_INSERT(monitor_panes, &me->panes, mp); } + mp->generation = ms->generation; - if (mp->last != NULL && strcmp(value, mp->last) == 0) { - free(value); - return; + monitor_check_value(ms, me, s, wl, wp, value, &mp->last); +} + +/* Remove all-panes entries not seen during the current scan. */ +static void +monitor_sweep_all_panes(struct monitor_item *me, u_int generation) +{ + struct monitor_pane *mp, *mp1; + + RB_FOREACH_SAFE(mp, monitor_panes, &me->panes, mp1) { + if (mp->generation == generation) + continue; + RB_REMOVE(monitor_panes, &me->panes, mp); + free(mp->last); + free(mp); } - - monitor_report(ms, me, s, wl, wp, value); - free(mp->last); - mp->last = value; } /* Check window subscription. */ @@ -258,7 +299,7 @@ static void monitor_check_window(struct monitor_set *ms, struct monitor_item *me) { struct client *c = ms->client; - struct session *s = c->session; + struct session *s = monitor_get_session(ms); struct window *w; struct winlink *wl; struct format_tree *ft; @@ -287,14 +328,7 @@ monitor_check_window(struct monitor_set *ms, struct monitor_item *me) RB_INSERT(monitor_windows, &me->windows, mw); } - if (mw->last != NULL && strcmp(value, mw->last) == 0) { - free(value); - continue; - } - - monitor_report(ms, me, s, wl, NULL, value); - free(mw->last); - mw->last = value; + monitor_check_value(ms, me, s, wl, NULL, value, &mw->last); } } @@ -303,7 +337,7 @@ static void monitor_check_all_windows_one(struct monitor_set *ms, struct monitor_item *me, struct format_tree *ft, struct winlink *wl) { - struct session *s = ms->client->session; + struct session *s = monitor_get_session(ms); struct window *w = wl->window; char *value; struct monitor_window *mw, find; @@ -319,15 +353,24 @@ monitor_check_all_windows_one(struct monitor_set *ms, struct monitor_item *me, mw->idx = wl->idx; RB_INSERT(monitor_windows, &me->windows, mw); } + mw->generation = ms->generation; - if (mw->last != NULL && strcmp(value, mw->last) == 0) { - free(value); - return; + monitor_check_value(ms, me, s, wl, NULL, value, &mw->last); +} + +/* Remove all-windows entries not seen during the current scan. */ +static void +monitor_sweep_all_windows(struct monitor_item *me, u_int generation) +{ + struct monitor_window *mw, *mw1; + + RB_FOREACH_SAFE(mw, monitor_windows, &me->windows, mw1) { + if (mw->generation == generation) + continue; + RB_REMOVE(monitor_windows, &me->windows, mw); + free(mw->last); + free(mw); } - - monitor_report(ms, me, s, wl, NULL, value); - free(mw->last); - mw->last = value; } /* Check session subscriptions. */ @@ -335,7 +378,7 @@ static void monitor_check_sessions(struct monitor_set *ms) { struct client *c = ms->client; - struct session *s = c->session; + struct session *s = monitor_get_session(ms); struct monitor_item *me, *me1; struct format_tree *ft; @@ -374,12 +417,14 @@ static void monitor_check_all_panes(struct monitor_set *ms) { struct client *c = ms->client; - struct session *s = c->session; + struct session *s = monitor_get_session(ms); struct monitor_item *me, *me1; struct window_pane *wp; struct format_tree *ft; struct winlink *wl; + if (++ms->generation == 0) + ms->generation = 1; RB_FOREACH(wl, winlinks, &s->windows) { TAILQ_FOREACH(wp, &wl->window->panes, entry) { ft = format_create_defaults(NULL, c, s, wl, wp); @@ -391,6 +436,10 @@ monitor_check_all_panes(struct monitor_set *ms) format_free(ft); } } + RB_FOREACH_SAFE(me, monitor_items, &ms->items, me1) { + if (me->type == MONITOR_ALL_PANES) + monitor_sweep_all_panes(me, ms->generation); + } } /* Check all-windows subscriptions. */ @@ -398,11 +447,13 @@ static void monitor_check_all_windows(struct monitor_set *ms) { struct client *c = ms->client; - struct session *s = c->session; + struct session *s = monitor_get_session(ms); struct monitor_item *me, *me1; struct format_tree *ft; struct winlink *wl; + if (++ms->generation == 0) + ms->generation = 1; RB_FOREACH(wl, winlinks, &s->windows) { ft = format_create_defaults(NULL, c, s, wl, NULL); RB_FOREACH_SAFE(me, monitor_items, &ms->items, me1) { @@ -412,6 +463,10 @@ monitor_check_all_windows(struct monitor_set *ms) } format_free(ft); } + RB_FOREACH_SAFE(me, monitor_items, &ms->items, me1) { + if (me->type == MONITOR_ALL_WINDOWS) + monitor_sweep_all_windows(me, ms->generation); + } } /* Check subscriptions. */ @@ -419,7 +474,6 @@ static void monitor_timer(__unused int fd, __unused short events, void *data) { struct monitor_set *ms = data; - struct client *c = ms->client; struct monitor_item *me; struct timeval tv = { .tv_sec = 1 }; int have_session = 0, have_all_panes = 0; @@ -428,7 +482,7 @@ monitor_timer(__unused int fd, __unused short events, void *data) log_debug("%s: timer fired", __func__); evtimer_add(&ms->timer, &tv); - if (c->session == NULL) + if (monitor_get_session(ms) == NULL) return; RB_FOREACH(me, monitor_items, &ms->items) { @@ -458,39 +512,104 @@ monitor_timer(__unused int fd, __unused short events, void *data) } /* Create a monitor set. */ -struct monitor_set * -monitor_create(struct client *c, monitor_cb cb, void *data) +static struct monitor_set * +monitor_create(monitor_cb cb, void *data) { struct monitor_set *ms; ms = xcalloc(1, sizeof *ms); - ms->client = c; ms->cb = cb; ms->data = data; RB_INIT(&ms->items); return (ms); } +/* Create a client monitor set. */ +struct monitor_set * +monitor_create_client(struct client *c, monitor_cb cb, void *data) +{ + struct monitor_set *ms; + + ms = monitor_create(cb, data); + ms->client = c; + return (ms); +} + +/* Create a monitor set for a session. */ +struct monitor_set * +monitor_create_session(struct session *s, monitor_cb cb, void *data) +{ + struct monitor_set *ms; + + ms = monitor_create(cb, data); + ms->session = s; + if (s != NULL) + session_add_ref(s, __func__); + return (ms); +} + /* Destroy a monitor set. */ void monitor_destroy(struct monitor_set *ms) { struct monitor_item *me, *me1; - if (ms == NULL) - return; + if (ms != NULL) { + if (evtimer_initialized(&ms->timer)) + evtimer_del(&ms->timer); + RB_FOREACH_SAFE(me, monitor_items, &ms->items, me1) + monitor_free_item(ms, me); + if (ms->session != NULL) + session_remove_ref(ms->session, __func__); + free(ms); + } +} - if (evtimer_initialized(&ms->timer)) - evtimer_del(&ms->timer); - RB_FOREACH_SAFE(me, monitor_items, &ms->items, me1) - monitor_free_item(ms, me); - free(ms); +/* Parse a subscription. */ +int +monitor_parse(const char *value, char **name, enum monitor_type *type, int *id, + char **format) +{ + char *copy, *what, *split; + + copy = xstrdup(value); + *id = -1; + + what = strchr(copy, ':'); + if (what == NULL) + goto fail; + *what++ = '\0'; + + split = strchr(what, ':'); + if (split == NULL) + goto fail; + *split++ = '\0'; + + if (strcmp(what, "%*") == 0) + *type = MONITOR_ALL_PANES; + else if (sscanf(what, "%%%d", id) == 1 && *id >= 0) + *type = MONITOR_PANE; + else if (strcmp(what, "@*") == 0) + *type = MONITOR_ALL_WINDOWS; + else if (sscanf(what, "@%d", id) == 1 && *id >= 0) + *type = MONITOR_WINDOW; + else + *type = MONITOR_SESSION; + *name = xstrdup(copy); + *format = xstrdup(split); + + free(copy); + return (0); + +fail: + free(copy); + return (-1); } /* Add a subscription. */ void monitor_add(struct monitor_set *ms, const char *name, enum monitor_type type, - int id, const char *format) + int id, const char *format, u_int flags) { struct monitor_item *me, find = { .name = (char *)name }; struct timeval tv = { .tv_sec = 1 }; @@ -503,6 +622,7 @@ monitor_add(struct monitor_set *ms, const char *name, enum monitor_type type, me->format = xstrdup(format); me->type = type; me->id = id; + me->flags = flags; RB_INIT(&me->panes); RB_INIT(&me->windows); RB_INSERT(monitor_items, &ms->items, me); diff --git a/notify.c b/notify.c index ac5d2e924..50bb55022 100644 --- a/notify.c +++ b/notify.c @@ -28,12 +28,25 @@ struct notify_entry { const char *name; struct cmd_find_state fs; struct format_tree *formats; + struct options *oo; struct client *client; struct session *session; struct window *window; int pane; const char *pbname; + int expand; +}; + +struct notify_monitor { + struct options *oo; + + struct monitor_set *set; + struct cmd_find_state fs; + + enum monitor_type type; + int id; + char *format; }; static struct cmdq_item * @@ -51,7 +64,31 @@ notify_insert_one_hook(struct cmdq_item *item, struct notify_entry *ne, free(s); } new_item = cmdq_get_command(cmdlist, state); - return (cmdq_insert_after(item, new_item)); + if (item != NULL) + return (cmdq_insert_after(item, new_item)); + return (cmdq_append(NULL, new_item)); +} + +static struct cmd_parse_result * +notify_parse_hook(struct notify_entry *ne, struct cmd_find_state *fs, + const char *value) +{ + struct cmd_parse_result *pr; + struct format_tree *ft; + char *expanded; + + if (!ne->expand) + return (cmd_parse_from_string(value, NULL)); + + ft = format_create_defaults(NULL, ne->client, fs->s, fs->wl, fs->wp); + if (ne->formats != NULL) + format_merge(ft, ne->formats); + expanded = format_expand(ft, value); + format_free(ft); + + pr = cmd_parse_from_string(expanded, NULL); + free(expanded); + return (pr); } static void @@ -74,18 +111,23 @@ notify_insert_hook(struct cmdq_item *item, struct notify_entry *ne) else cmd_find_copy_state(&fs, &ne->fs); - if (fs.s == NULL) - oo = global_s_options; - else - oo = fs.s->options; - o = options_get(oo, ne->name); - if (o == NULL && fs.wp != NULL) { - oo = fs.wp->options; - o = options_get(oo, ne->name); - } - if (o == NULL && fs.wl != NULL) { - oo = fs.wl->window->options; + if (ne->oo != NULL) { + oo = ne->oo; + o = options_get_only(oo, ne->name); + } else { + if (fs.s == NULL) + oo = global_s_options; + else + oo = fs.s->options; o = options_get(oo, ne->name); + if (o == NULL && fs.wp != NULL) { + oo = fs.wp->options; + o = options_get(oo, ne->name); + } + if (o == NULL && fs.wl != NULL) { + oo = fs.wl->window->options; + o = options_get(oo, ne->name); + } } if (o == NULL) { log_debug("%s: hook %s not found", __func__, ne->name); @@ -97,7 +139,7 @@ notify_insert_hook(struct cmdq_item *item, struct notify_entry *ne) if (*ne->name == '@') { value = options_get_string(oo, ne->name); - pr = cmd_parse_from_string(value, NULL); + pr = notify_parse_hook(ne, &fs, value); switch (pr->status) { case CMD_PARSE_ERROR: log_debug("%s: can't parse hook %s: %s", __func__, @@ -111,8 +153,24 @@ notify_insert_hook(struct cmdq_item *item, struct notify_entry *ne) } else { a = options_array_first(o); while (a != NULL) { - cmdlist = options_array_item_value(a)->cmdlist; - item = notify_insert_one_hook(item, ne, cmdlist, state); + if (ne->expand) { + value = options_array_item_value(a)->string; + pr = notify_parse_hook(ne, &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 = notify_insert_one_hook(item, ne, + pr->cmdlist, state); + break; + } + } else { + cmdlist = options_array_item_value(a)->cmdlist; + item = notify_insert_one_hook(item, ne, cmdlist, + state); + } a = options_array_next(a); } } @@ -176,6 +234,136 @@ notify_callback(struct cmdq_item *item, void *data) return (CMD_RETURN_NORMAL); } +void +notify_monitor_free(void *data) +{ + struct notify_monitor *nhm = data; + + monitor_destroy(nhm->set); + free(nhm->format); + free(nhm); +} + +void +notify_monitor_remove(struct options *oo, const char *name) +{ + struct options_entry *o; + struct notify_monitor *nhm; + + o = options_get_only(oo, name); + if (o == NULL) + return; + + nhm = options_get_monitor_data(o); + if (nhm != NULL) { + options_set_monitor_data(o, NULL); + notify_monitor_free(nhm); + } +} + +static void +notify_monitor_cb(struct monitor_change *change, void *data) +{ + struct notify_monitor *nhm = data; + struct notify_entry ne; + struct cmdq_item *item; + struct window *w; + + item = cmdq_running(NULL); + if (item != NULL && (cmdq_get_flags(item) & CMDQ_STATE_NOHOOKS)) + return; + + memset(&ne, 0, sizeof ne); + ne.name = change->name; + ne.oo = nhm->oo; + ne.client = change->c; + ne.expand = 1; + if (change->wp != NULL && change->wl != NULL) + cmd_find_from_winlink_pane(&ne.fs, change->wl, change->wp, 0); + else if (change->wl != NULL) + cmd_find_from_winlink(&ne.fs, change->wl, 0); + else if (change->s != NULL) + cmd_find_from_session(&ne.fs, change->s, 0); + else + cmd_find_copy_state(&ne.fs, &nhm->fs); + ne.formats = format_create(change->c, item, FORMAT_NONE, FORMAT_NOJOBS); + format_add(ne.formats, "hook", "%s", change->name); + format_add(ne.formats, "hook_value", "%s", change->value); + format_add(ne.formats, "hook_last", "%s", + change->last == NULL ? "" : change->last); + if (change->s != NULL) { + format_add(ne.formats, "hook_session", "$%u", change->s->id); + format_add(ne.formats, "hook_session_name", "%s", change->s->name); + } + if (change->wl != NULL) { + w = change->wl->window; + format_add(ne.formats, "hook_window", "@%u", w->id); + format_add(ne.formats, "hook_window_name", "%s", w->name); + format_add(ne.formats, "hook_window_index", "%d", change->wl->idx); + } + if (change->wp != NULL) { + format_add(ne.formats, "hook_pane", "%%%u", change->wp->id); + } + + notify_insert_hook(item, &ne); + format_free(ne.formats); +} + +void +notify_monitor_add(__unused struct cmdq_item *item, struct options *oo, + const char *name, enum monitor_type type, int id, const char *format, + struct cmd_find_state *fs, struct session *s) +{ + struct options_entry *o; + struct notify_monitor *nhm; + + notify_monitor_remove(oo, name); + o = options_get_only(oo, name); + if (o == NULL) + o = options_set_string(oo, name, 0, "%s", ""); + + nhm = xcalloc(1, sizeof *nhm); + nhm->oo = oo; + cmd_find_copy_state(&nhm->fs, fs); + nhm->type = type; + nhm->id = id; + nhm->format = xstrdup(format); + nhm->set = monitor_create_session(s, notify_monitor_cb, nhm); + options_set_monitor_data(o, nhm); + monitor_add(nhm->set, name, type, id, format, 0); +} + +/* Convert a hook monitor to its value. */ +char * +notify_monitor_to_string(struct options_entry *o) +{ + struct notify_monitor *nhm = options_get_monitor_data(o); + const char *name = options_name(o); + char *s; + + if (nhm == NULL) + return (NULL); + + switch (nhm->type) { + case MONITOR_SESSION: + xasprintf(&s, "%s::%s", name, nhm->format); + break; + case MONITOR_PANE: + xasprintf(&s, "%s:%%%d:%s", name, nhm->id, nhm->format); + break; + case MONITOR_ALL_PANES: + xasprintf(&s, "%s:%%*:%s", name, nhm->format); + break; + case MONITOR_WINDOW: + xasprintf(&s, "%s:@%d:%s", name, nhm->id, nhm->format); + break; + case MONITOR_ALL_WINDOWS: + xasprintf(&s, "%s:@*:%s", name, nhm->format); + break; + } + return (s); +} + static void notify_add(const char *name, struct cmd_find_state *fs, struct client *c, struct session *s, struct window *w, struct window_pane *wp, diff --git a/options.c b/options.c index e34320869..19da38886 100644 --- a/options.c +++ b/options.c @@ -56,6 +56,7 @@ struct options_entry { int cached; struct style style; + void *monitor_data; RB_ENTRY(options_entry) entry; }; @@ -354,6 +355,8 @@ options_remove(struct options_entry *o) options_array_clear(o); else options_value_free(o, &o->value); + if (o->monitor_data != NULL) + notify_monitor_free(o->monitor_data); RB_REMOVE(options_tree, &oo->tree, o); free((void *)o->name); free(o); @@ -371,6 +374,18 @@ options_owner(struct options_entry *o) return (o->owner); } +void * +options_get_monitor_data(struct options_entry *o) +{ + return (o->monitor_data); +} + +void +options_set_monitor_data(struct options_entry *o, void *data) +{ + o->monitor_data = data; +} + const struct options_table_entry * options_table_entry(struct options_entry *o) { diff --git a/tmux.1 b/tmux.1 index 4eb9d9f08..54d30072b 100644 --- a/tmux.1 +++ b/tmux.1 @@ -6395,6 +6395,7 @@ Hooks are managed with these commands: .Bl -tag -width Ds .It Xo Ic set\-hook .Op Fl agpRuw +.Op Fl B Ar name:what:format .Op Fl t Ar target\-pane .Ar hook\-name .Op Ar command @@ -6411,18 +6412,53 @@ The flags are the same as for .Ic set\-option . .Pp With +.Fl B , +.Ar name:what:format +uses the same subscription syntax as +.Ic refresh\-client +.Fl B : +.Ar name +is the hook to run, +.Ar what +selects the session, pane, all panes, window, or all windows, and +.Ar format +is expanded once a second. +For monitor hooks, +.Ar name +must begin with +.Ql @ . +If +.Ar command +is given, it is stored as the +.Ql @ +hook command; otherwise only the monitor is created or replaced. +Note that monitor hooks are not inherited, the hook is only run from +the scope where it is created. +With +.Fl u , +the subscription named by +.Fl B +is removed. +.Pp +With .Fl R , run .Ar hook\-name immediately. .It Xo Ic show\-hooks -.Op Fl gpw +.Op Fl Bgpw .Op Fl t Ar target\-pane .Op Ar hook .Xc Shows hooks. The flags are the same as for .Ic show\-options . +.Pp +With +.Fl B , +shows the subscriptions installed with +.Em set\-hook +.Fl B . .El .Sh MOUSE SUPPORT If the @@ -7060,10 +7096,13 @@ The following variables are available, where appropriate: .It Li "history_size" Ta "" Ta "Size of history in lines" .It Li "hook" Ta "" Ta "Name of running hook, if any" .It Li "hook_client" Ta "" Ta "Name of client where hook was run, if any" +.It Li "hook_last" Ta "" Ta "Previous value for a monitor hook" .It Li "hook_pane" Ta "" Ta "ID of pane where hook was run, if any" .It Li "hook_session" Ta "" Ta "ID of session where hook was run, if any" .It Li "hook_session_name" Ta "" Ta "Name of session where hook was run, if any" +.It Li "hook_value" Ta "" Ta "New value for a monitor hook" .It Li "hook_window" Ta "" Ta "ID of window where hook was run, if any" +.It Li "hook_window_index" Ta "" Ta "Index of window where hook was run, if any" .It Li "hook_window_name" Ta "" Ta "Name of window where hook was run, if any" .It Li "host" Ta "#H" Ta "Hostname of local host" .It Li "host_short" Ta "#h" Ta "Hostname of local host (no domain name)" diff --git a/tmux.h b/tmux.h index 9fa174a82..dd2a69c15 100644 --- a/tmux.h +++ b/tmux.h @@ -2282,9 +2282,11 @@ enum monitor_type { MONITOR_WINDOW, MONITOR_ALL_WINDOWS }; +#define MONITOR_NOTIFY_INITIAL 0x1 struct monitor_change { const char *name; const char *value; + const char *last; struct client *c; struct session *s; @@ -2612,6 +2614,12 @@ char *format_trim_right(const char *, u_int); /* notify.c */ void notify_hook(struct cmdq_item *, const char *); +void notify_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 *); @@ -2634,6 +2642,8 @@ struct options_entry *options_default(struct options *, char *options_default_to_string(const struct options_table_entry *); const char *options_name(struct options_entry *); struct options *options_owner(struct options_entry *); +void *options_get_monitor_data(struct options_entry *); +void options_set_monitor_data(struct options_entry *, void *); const struct options_table_entry *options_table_entry(struct options_entry *); struct options_entry *options_get_only(struct options *, const char *); struct options_entry *options_get(struct options *, const char *); @@ -3805,10 +3815,13 @@ char *default_window_name(struct window *); char *parse_window_name(const char *); /* monitor.c */ -struct monitor_set *monitor_create(struct client *, monitor_cb, void *); +struct monitor_set *monitor_create_client(struct client *, monitor_cb, void *); +struct monitor_set *monitor_create_session(struct session *, monitor_cb, void *); 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 *); + const char *, u_int); void monitor_remove(struct monitor_set *, const char *); /* control.c */