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.
This commit is contained in:
nicm
2026-07-05 08:24:00 +00:00
parent 7a8f8f5a74
commit de024e04d7
9 changed files with 607 additions and 117 deletions

View File

@@ -46,36 +46,17 @@ const struct cmd_entry cmd_refresh_client_entry = {
static void static void
cmd_refresh_client_update_subscription(struct client *tc, const char *value) cmd_refresh_client_update_subscription(struct client *tc, const char *value)
{ {
char *copy, *split, *name, *what; char *name, *format;
enum monitor_type subtype; enum monitor_type type;
int subid = -1; int id;
copy = name = xstrdup(value); if (monitor_parse(value, &name, &type, &id, &format) != 0) {
if ((split = strchr(copy, ':')) == NULL) { control_remove_sub(tc, value);
control_remove_sub(tc, copy); return;
goto out;
} }
*split++ = '\0'; control_add_sub(tc, name, type, id, format);
free(name);
what = split; free(format);
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);
} }
static enum cmd_retval static enum cmd_retval

View File

@@ -31,6 +31,8 @@ static enum args_parse_type cmd_set_option_args_parse(struct args *,
u_int, char **); u_int, char **);
static enum cmd_retval cmd_set_option_exec(struct cmd *, static enum cmd_retval cmd_set_option_exec(struct cmd *,
struct cmdq_item *); 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 = { const struct cmd_entry cmd_set_option_entry = {
.name = "set-option", .name = "set-option",
@@ -62,8 +64,9 @@ const struct cmd_entry cmd_set_hook_entry = {
.name = "set-hook", .name = "set-hook",
.alias = NULL, .alias = NULL,
.args = { "agpRt:uw", 1, 2, cmd_set_option_args_parse }, .args = { "agpRt:uB:w", 0, 2, cmd_set_option_args_parse },
.usage = "[-agpRuw] " CMD_TARGET_PANE_USAGE " hook [command]", .usage = "[-agpRuw] [-B name:what:format] " CMD_TARGET_PANE_USAGE " "
"[hook] [command]",
.target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL }, .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 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) __unused char **cause)
{ {
if (args_has(args, 'B'))
return (ARGS_PARSE_COMMANDS_OR_STRING);
if (idx == 1) if (idx == 1)
return (ARGS_PARSE_COMMANDS_OR_STRING); return (ARGS_PARSE_COMMANDS_OR_STRING);
return (ARGS_PARSE_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 static enum cmd_retval
cmd_set_option_exec(struct cmd *self, struct cmdq_item *item) 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; int scope;
window = (cmd_get_entry(self) == &cmd_set_window_option_entry); 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. */ /* Expand argument. */
argument = format_single_from_target(item, args_string(args, 0)); argument = format_single_from_target(item, args_string(args, 0));

View File

@@ -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 *, static void cmd_show_options_print(struct cmd *, struct cmdq_item *,
struct options_entry *, int, int); 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 *, static enum cmd_retval cmd_show_options_all(struct cmd *, struct cmdq_item *,
int, struct options *); int, struct options *);
@@ -65,8 +69,8 @@ const struct cmd_entry cmd_show_hooks_entry = {
.name = "show-hooks", .name = "show-hooks",
.alias = NULL, .alias = NULL,
.args = { "gpt:w", 0, 1, NULL }, .args = { "Bgpt:w", 0, 1, NULL },
.usage = "[-gpw] " CMD_TARGET_PANE_USAGE " [hook]", .usage = "[-Bgpw] " CMD_TARGET_PANE_USAGE " [hook]",
.target = { 't', CMD_FIND_PANE, CMD_FIND_CANFAIL }, .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); free(cause);
return (CMD_RETURN_ERROR); 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)); return (cmd_show_options_all(self, item, scope, oo));
} }
argument = format_single_from_target(item, args_string(args, 0)); 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; parent = 1;
} else } else
parent = 0; parent = 0;
if (o != NULL) if (o != NULL) {
cmd_show_options_print(self, item, o, idx, parent); 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 == '@') { else if (*name == '@') {
if (args_has(args, 'q')) if (args_has(args, 'q'))
goto out; goto out;
@@ -196,6 +208,33 @@ cmd_show_options_print(struct cmd *self, struct cmdq_item *item,
free(tmp); 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 static enum cmd_retval
cmd_show_options_all(struct cmd *self, struct cmdq_item *item, int scope, cmd_show_options_all(struct cmd *self, struct cmdq_item *item, int scope,
struct options *oo) struct options *oo)

View File

@@ -702,7 +702,7 @@ control_start(struct client *c)
RB_INIT(&cs->panes); RB_INIT(&cs->panes);
TAILQ_INIT(&cs->pending_list); TAILQ_INIT(&cs->pending_list);
TAILQ_INIT(&cs->all_blocks); 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, cs->read_event = bufferevent_new(c->fd, control_read_callback,
control_write_callback, control_error_callback, c); 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; 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. */ /* Remove a subscription. */

244
monitor.c
View File

@@ -29,6 +29,7 @@ struct monitor_pane {
u_int pane; u_int pane;
u_int idx; u_int idx;
char *last; char *last;
u_int generation;
RB_ENTRY(monitor_pane) entry; RB_ENTRY(monitor_pane) entry;
}; };
@@ -39,6 +40,7 @@ struct monitor_window {
u_int window; u_int window;
u_int idx; u_int idx;
char *last; char *last;
u_int generation;
RB_ENTRY(monitor_window) entry; RB_ENTRY(monitor_window) entry;
}; };
@@ -51,6 +53,7 @@ struct monitor_item {
enum monitor_type type; enum monitor_type type;
u_int id; u_int id;
u_int flags;
char *last; char *last;
struct monitor_panes panes; struct monitor_panes panes;
@@ -63,15 +66,33 @@ RB_HEAD(monitor_items, monitor_item);
/* Monitored subscription set. */ /* Monitored subscription set. */
struct monitor_set { struct monitor_set {
struct client *client; struct client *client;
struct session *session;
monitor_cb cb; monitor_cb cb;
void *data; void *data;
struct monitor_items items; struct monitor_items items;
struct event timer; struct event timer;
u_int generation;
}; };
static void monitor_timer(__unused int, __unused short, void *); 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. */ /* Compare subscriptions. */
static int static int
monitor_item_cmp(struct monitor_item *m1, struct monitor_item *m2) 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 static void
monitor_report(struct monitor_set *ms, struct monitor_item *me, monitor_report(struct monitor_set *ms, struct monitor_item *me,
struct session *s, struct winlink *wl, struct window_pane *wp, 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.name = me->name;
change.value = value; change.value = value;
change.last = last;
change.c = ms->client; change.c = ms->client;
change.s = s; change.s = s;
change.wl = wl; change.wl = wl;
change.wp = wp; change.wp = wp;
log_debug("%s: %s changed to %s", __func__, me->name, value);
ms->cb(&change, ms->data); 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. */ /* Check session subscription. */
static void static void
monitor_check_session(struct monitor_set *ms, struct monitor_item *me, monitor_check_session(struct monitor_set *ms, struct monitor_item *me,
struct format_tree *ft) struct format_tree *ft)
{ {
struct session *s = ms->client->session; struct session *s = monitor_get_session(ms);
char *value; char *value;
value = format_expand(ft, me->format); value = format_expand(ft, me->format);
if (me->last != NULL && strcmp(value, me->last) == 0) { monitor_check_value(ms, me, s, NULL, NULL, value, &me->last);
free(value);
return;
}
monitor_report(ms, me, s, NULL, NULL, value);
free(me->last);
me->last = value;
} }
/* Check pane subscription. */ /* Check pane subscription. */
@@ -180,7 +219,7 @@ static void
monitor_check_pane(struct monitor_set *ms, struct monitor_item *me) monitor_check_pane(struct monitor_set *ms, struct monitor_item *me)
{ {
struct client *c = ms->client; struct client *c = ms->client;
struct session *s = c->session; struct session *s = monitor_get_session(ms);
struct window_pane *wp; struct window_pane *wp;
struct window *w; struct window *w;
struct winlink *wl; 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); RB_INSERT(monitor_panes, &me->panes, mp);
} }
if (mp->last != NULL && strcmp(value, mp->last) == 0) { monitor_check_value(ms, me, s, wl, wp, value, &mp->last);
free(value);
continue;
}
monitor_report(ms, me, s, wl, wp, value);
free(mp->last);
mp->last = value;
} }
} }
@@ -227,7 +259,7 @@ static void
monitor_check_all_panes_one(struct monitor_set *ms, struct monitor_item *me, 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 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; char *value;
struct monitor_pane *mp, find; 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; mp->idx = wl->idx;
RB_INSERT(monitor_panes, &me->panes, mp); RB_INSERT(monitor_panes, &me->panes, mp);
} }
mp->generation = ms->generation;
if (mp->last != NULL && strcmp(value, mp->last) == 0) { monitor_check_value(ms, me, s, wl, wp, value, &mp->last);
free(value); }
return;
/* 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. */ /* Check window subscription. */
@@ -258,7 +299,7 @@ static void
monitor_check_window(struct monitor_set *ms, struct monitor_item *me) monitor_check_window(struct monitor_set *ms, struct monitor_item *me)
{ {
struct client *c = ms->client; struct client *c = ms->client;
struct session *s = c->session; struct session *s = monitor_get_session(ms);
struct window *w; struct window *w;
struct winlink *wl; struct winlink *wl;
struct format_tree *ft; 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); RB_INSERT(monitor_windows, &me->windows, mw);
} }
if (mw->last != NULL && strcmp(value, mw->last) == 0) { monitor_check_value(ms, me, s, wl, NULL, value, &mw->last);
free(value);
continue;
}
monitor_report(ms, me, s, wl, NULL, value);
free(mw->last);
mw->last = value;
} }
} }
@@ -303,7 +337,7 @@ static void
monitor_check_all_windows_one(struct monitor_set *ms, struct monitor_item *me, monitor_check_all_windows_one(struct monitor_set *ms, struct monitor_item *me,
struct format_tree *ft, struct winlink *wl) 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; struct window *w = wl->window;
char *value; char *value;
struct monitor_window *mw, find; 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; mw->idx = wl->idx;
RB_INSERT(monitor_windows, &me->windows, mw); RB_INSERT(monitor_windows, &me->windows, mw);
} }
mw->generation = ms->generation;
if (mw->last != NULL && strcmp(value, mw->last) == 0) { monitor_check_value(ms, me, s, wl, NULL, value, &mw->last);
free(value); }
return;
/* 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. */ /* Check session subscriptions. */
@@ -335,7 +378,7 @@ static void
monitor_check_sessions(struct monitor_set *ms) monitor_check_sessions(struct monitor_set *ms)
{ {
struct client *c = ms->client; struct client *c = ms->client;
struct session *s = c->session; struct session *s = monitor_get_session(ms);
struct monitor_item *me, *me1; struct monitor_item *me, *me1;
struct format_tree *ft; struct format_tree *ft;
@@ -374,12 +417,14 @@ static void
monitor_check_all_panes(struct monitor_set *ms) monitor_check_all_panes(struct monitor_set *ms)
{ {
struct client *c = ms->client; struct client *c = ms->client;
struct session *s = c->session; struct session *s = monitor_get_session(ms);
struct monitor_item *me, *me1; struct monitor_item *me, *me1;
struct window_pane *wp; struct window_pane *wp;
struct format_tree *ft; struct format_tree *ft;
struct winlink *wl; struct winlink *wl;
if (++ms->generation == 0)
ms->generation = 1;
RB_FOREACH(wl, winlinks, &s->windows) { RB_FOREACH(wl, winlinks, &s->windows) {
TAILQ_FOREACH(wp, &wl->window->panes, entry) { TAILQ_FOREACH(wp, &wl->window->panes, entry) {
ft = format_create_defaults(NULL, c, s, wl, wp); ft = format_create_defaults(NULL, c, s, wl, wp);
@@ -391,6 +436,10 @@ monitor_check_all_panes(struct monitor_set *ms)
format_free(ft); 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. */ /* Check all-windows subscriptions. */
@@ -398,11 +447,13 @@ static void
monitor_check_all_windows(struct monitor_set *ms) monitor_check_all_windows(struct monitor_set *ms)
{ {
struct client *c = ms->client; struct client *c = ms->client;
struct session *s = c->session; struct session *s = monitor_get_session(ms);
struct monitor_item *me, *me1; struct monitor_item *me, *me1;
struct format_tree *ft; struct format_tree *ft;
struct winlink *wl; struct winlink *wl;
if (++ms->generation == 0)
ms->generation = 1;
RB_FOREACH(wl, winlinks, &s->windows) { RB_FOREACH(wl, winlinks, &s->windows) {
ft = format_create_defaults(NULL, c, s, wl, NULL); ft = format_create_defaults(NULL, c, s, wl, NULL);
RB_FOREACH_SAFE(me, monitor_items, &ms->items, me1) { RB_FOREACH_SAFE(me, monitor_items, &ms->items, me1) {
@@ -412,6 +463,10 @@ monitor_check_all_windows(struct monitor_set *ms)
} }
format_free(ft); 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. */ /* Check subscriptions. */
@@ -419,7 +474,6 @@ static void
monitor_timer(__unused int fd, __unused short events, void *data) monitor_timer(__unused int fd, __unused short events, void *data)
{ {
struct monitor_set *ms = data; struct monitor_set *ms = data;
struct client *c = ms->client;
struct monitor_item *me; struct monitor_item *me;
struct timeval tv = { .tv_sec = 1 }; struct timeval tv = { .tv_sec = 1 };
int have_session = 0, have_all_panes = 0; 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__); log_debug("%s: timer fired", __func__);
evtimer_add(&ms->timer, &tv); evtimer_add(&ms->timer, &tv);
if (c->session == NULL) if (monitor_get_session(ms) == NULL)
return; return;
RB_FOREACH(me, monitor_items, &ms->items) { 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. */ /* Create a monitor set. */
struct monitor_set * static struct monitor_set *
monitor_create(struct client *c, monitor_cb cb, void *data) monitor_create(monitor_cb cb, void *data)
{ {
struct monitor_set *ms; struct monitor_set *ms;
ms = xcalloc(1, sizeof *ms); ms = xcalloc(1, sizeof *ms);
ms->client = c;
ms->cb = cb; ms->cb = cb;
ms->data = data; ms->data = data;
RB_INIT(&ms->items); RB_INIT(&ms->items);
return (ms); 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. */ /* Destroy a monitor set. */
void void
monitor_destroy(struct monitor_set *ms) monitor_destroy(struct monitor_set *ms)
{ {
struct monitor_item *me, *me1; struct monitor_item *me, *me1;
if (ms == NULL) if (ms != NULL) {
return; 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)) /* Parse a subscription. */
evtimer_del(&ms->timer); int
RB_FOREACH_SAFE(me, monitor_items, &ms->items, me1) monitor_parse(const char *value, char **name, enum monitor_type *type, int *id,
monitor_free_item(ms, me); char **format)
free(ms); {
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. */ /* Add a subscription. */
void void
monitor_add(struct monitor_set *ms, const char *name, enum monitor_type type, 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 monitor_item *me, find = { .name = (char *)name };
struct timeval tv = { .tv_sec = 1 }; 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->format = xstrdup(format);
me->type = type; me->type = type;
me->id = id; me->id = id;
me->flags = flags;
RB_INIT(&me->panes); RB_INIT(&me->panes);
RB_INIT(&me->windows); RB_INIT(&me->windows);
RB_INSERT(monitor_items, &ms->items, me); RB_INSERT(monitor_items, &ms->items, me);

218
notify.c
View File

@@ -28,12 +28,25 @@ struct notify_entry {
const char *name; const char *name;
struct cmd_find_state fs; struct cmd_find_state fs;
struct format_tree *formats; struct format_tree *formats;
struct options *oo;
struct client *client; struct client *client;
struct session *session; struct session *session;
struct window *window; struct window *window;
int pane; int pane;
const char *pbname; 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 * static struct cmdq_item *
@@ -51,7 +64,31 @@ notify_insert_one_hook(struct cmdq_item *item, struct notify_entry *ne,
free(s); free(s);
} }
new_item = cmdq_get_command(cmdlist, state); 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 static void
@@ -74,18 +111,23 @@ notify_insert_hook(struct cmdq_item *item, struct notify_entry *ne)
else else
cmd_find_copy_state(&fs, &ne->fs); cmd_find_copy_state(&fs, &ne->fs);
if (fs.s == NULL) if (ne->oo != NULL) {
oo = global_s_options; oo = ne->oo;
else o = options_get_only(oo, ne->name);
oo = fs.s->options; } else {
o = options_get(oo, ne->name); if (fs.s == NULL)
if (o == NULL && fs.wp != NULL) { oo = global_s_options;
oo = fs.wp->options; else
o = options_get(oo, ne->name); oo = fs.s->options;
}
if (o == NULL && fs.wl != NULL) {
oo = fs.wl->window->options;
o = options_get(oo, ne->name); 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) { if (o == NULL) {
log_debug("%s: hook %s not found", __func__, ne->name); 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 == '@') { if (*ne->name == '@') {
value = options_get_string(oo, 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) { switch (pr->status) {
case CMD_PARSE_ERROR: case CMD_PARSE_ERROR:
log_debug("%s: can't parse hook %s: %s", __func__, 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 { } else {
a = options_array_first(o); a = options_array_first(o);
while (a != NULL) { while (a != NULL) {
cmdlist = options_array_item_value(a)->cmdlist; if (ne->expand) {
item = notify_insert_one_hook(item, ne, cmdlist, state); 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); a = options_array_next(a);
} }
} }
@@ -176,6 +234,136 @@ notify_callback(struct cmdq_item *item, void *data)
return (CMD_RETURN_NORMAL); 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 static void
notify_add(const char *name, struct cmd_find_state *fs, struct client *c, notify_add(const char *name, struct cmd_find_state *fs, struct client *c,
struct session *s, struct window *w, struct window_pane *wp, struct session *s, struct window *w, struct window_pane *wp,

View File

@@ -56,6 +56,7 @@ struct options_entry {
int cached; int cached;
struct style style; struct style style;
void *monitor_data;
RB_ENTRY(options_entry) entry; RB_ENTRY(options_entry) entry;
}; };
@@ -354,6 +355,8 @@ options_remove(struct options_entry *o)
options_array_clear(o); options_array_clear(o);
else else
options_value_free(o, &o->value); options_value_free(o, &o->value);
if (o->monitor_data != NULL)
notify_monitor_free(o->monitor_data);
RB_REMOVE(options_tree, &oo->tree, o); RB_REMOVE(options_tree, &oo->tree, o);
free((void *)o->name); free((void *)o->name);
free(o); free(o);
@@ -371,6 +374,18 @@ options_owner(struct options_entry *o)
return (o->owner); 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 * const struct options_table_entry *
options_table_entry(struct options_entry *o) options_table_entry(struct options_entry *o)
{ {

41
tmux.1
View File

@@ -6395,6 +6395,7 @@ Hooks are managed with these commands:
.Bl -tag -width Ds .Bl -tag -width Ds
.It Xo Ic set\-hook .It Xo Ic set\-hook
.Op Fl agpRuw .Op Fl agpRuw
.Op Fl B Ar name:what:format
.Op Fl t Ar target\-pane .Op Fl t Ar target\-pane
.Ar hook\-name .Ar hook\-name
.Op Ar command .Op Ar command
@@ -6411,18 +6412,53 @@ The flags are the same as for
.Ic set\-option . .Ic set\-option .
.Pp .Pp
With 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 , .Fl R ,
run run
.Ar hook\-name .Ar hook\-name
immediately. immediately.
.It Xo Ic show\-hooks .It Xo Ic show\-hooks
.Op Fl gpw .Op Fl Bgpw
.Op Fl t Ar target\-pane .Op Fl t Ar target\-pane
.Op Ar hook .Op Ar hook
.Xc .Xc
Shows hooks. Shows hooks.
The flags are the same as for The flags are the same as for
.Ic show\-options . .Ic show\-options .
.Pp
With
.Fl B ,
shows the subscriptions installed with
.Em set\-hook
.Fl B .
.El .El
.Sh MOUSE SUPPORT .Sh MOUSE SUPPORT
If the 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 "history_size" Ta "" Ta "Size of history in lines"
.It Li "hook" Ta "" Ta "Name of running hook, if any" .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_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_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" 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_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" 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 "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" Ta "#H" Ta "Hostname of local host"
.It Li "host_short" Ta "#h" Ta "Hostname of local host (no domain name)" .It Li "host_short" Ta "#h" Ta "Hostname of local host (no domain name)"

17
tmux.h
View File

@@ -2282,9 +2282,11 @@ enum monitor_type {
MONITOR_WINDOW, MONITOR_WINDOW,
MONITOR_ALL_WINDOWS MONITOR_ALL_WINDOWS
}; };
#define MONITOR_NOTIFY_INITIAL 0x1
struct monitor_change { struct monitor_change {
const char *name; const char *name;
const char *value; const char *value;
const char *last;
struct client *c; struct client *c;
struct session *s; struct session *s;
@@ -2612,6 +2614,12 @@ char *format_trim_right(const char *, u_int);
/* notify.c */ /* notify.c */
void notify_hook(struct cmdq_item *, const char *); 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_client(const char *, struct client *);
void notify_session(const char *, struct session *); void notify_session(const char *, struct session *);
void notify_winlink(const char *, struct winlink *); 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 *); char *options_default_to_string(const struct options_table_entry *);
const char *options_name(struct options_entry *); const char *options_name(struct options_entry *);
struct options *options_owner(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 *); 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_only(struct options *, const char *);
struct options_entry *options_get(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 *); char *parse_window_name(const char *);
/* monitor.c */ /* 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 *); 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, 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 *); void monitor_remove(struct monitor_set *, const char *);
/* control.c */ /* control.c */