diff --git a/Makefile b/Makefile index ec1f2ff56..165d0d47c 100644 --- a/Makefile +++ b/Makefile @@ -95,6 +95,7 @@ SRCS= alerts.c \ log.c \ menu.c \ mode-tree.c \ + monitor.c \ names.c \ notify.c \ options-table.c \ diff --git a/cmd-refresh-client.c b/cmd-refresh-client.c index 13fcbb1dd..16269e249 100644 --- a/cmd-refresh-client.c +++ b/cmd-refresh-client.c @@ -47,7 +47,7 @@ static void cmd_refresh_client_update_subscription(struct client *tc, const char *value) { char *copy, *split, *name, *what; - enum control_sub_type subtype; + enum monitor_type subtype; int subid = -1; copy = name = xstrdup(value); @@ -63,15 +63,15 @@ cmd_refresh_client_update_subscription(struct client *tc, const char *value) *split++ = '\0'; if (strcmp(what, "%*") == 0) - subtype = CONTROL_SUB_ALL_PANES; + subtype = MONITOR_ALL_PANES; else if (sscanf(what, "%%%d", &subid) == 1 && subid >= 0) - subtype = CONTROL_SUB_PANE; + subtype = MONITOR_PANE; else if (strcmp(what, "@*") == 0) - subtype = CONTROL_SUB_ALL_WINDOWS; + subtype = MONITOR_ALL_WINDOWS; else if (sscanf(what, "@%d", &subid) == 1 && subid >= 0) - subtype = CONTROL_SUB_WINDOW; + subtype = MONITOR_WINDOW; else - subtype = CONTROL_SUB_SESSION; + subtype = MONITOR_SESSION; control_add_sub(tc, name, subtype, subid, split); out: diff --git a/control.c b/control.c index ab461ff10..eb2db98ef 100644 --- a/control.c +++ b/control.c @@ -76,42 +76,6 @@ struct control_pane { }; RB_HEAD(control_panes, control_pane); -/* Subscription pane. */ -struct control_sub_pane { - u_int pane; - u_int idx; - char *last; - - RB_ENTRY(control_sub_pane) entry; -}; -RB_HEAD(control_sub_panes, control_sub_pane); - -/* Subscription window. */ -struct control_sub_window { - u_int window; - u_int idx; - char *last; - - RB_ENTRY(control_sub_window) entry; -}; -RB_HEAD(control_sub_windows, control_sub_window); - -/* Control client subscription. */ -struct control_sub { - char *name; - char *format; - - enum control_sub_type type; - u_int id; - - char *last; - struct control_sub_panes panes; - struct control_sub_windows windows; - - RB_ENTRY(control_sub) entry; -}; -RB_HEAD(control_subs, control_sub); - /* Control client state. */ struct control_state { struct control_panes panes; @@ -124,8 +88,7 @@ struct control_state { struct bufferevent *read_event; struct bufferevent *write_event; - struct control_subs subs; - struct event subs_timer; + struct monitor_set *subs; }; /* Low and high watermarks. */ @@ -155,75 +118,6 @@ control_pane_cmp(struct control_pane *cp1, struct control_pane *cp2) } RB_GENERATE_STATIC(control_panes, control_pane, entry, control_pane_cmp); -/* Compare client subs. */ -static int -control_sub_cmp(struct control_sub *csub1, struct control_sub *csub2) -{ - return (strcmp(csub1->name, csub2->name)); -} -RB_GENERATE_STATIC(control_subs, control_sub, entry, control_sub_cmp); - -/* Compare client subscription panes. */ -static int -control_sub_pane_cmp(struct control_sub_pane *csp1, - struct control_sub_pane *csp2) -{ - if (csp1->pane < csp2->pane) - return (-1); - if (csp1->pane > csp2->pane) - return (1); - if (csp1->idx < csp2->idx) - return (-1); - if (csp1->idx > csp2->idx) - return (1); - return (0); -} -RB_GENERATE_STATIC(control_sub_panes, control_sub_pane, entry, - control_sub_pane_cmp); - -/* Compare client subscription windows. */ -static int -control_sub_window_cmp(struct control_sub_window *csw1, - struct control_sub_window *csw2) -{ - if (csw1->window < csw2->window) - return (-1); - if (csw1->window > csw2->window) - return (1); - if (csw1->idx < csw2->idx) - return (-1); - if (csw1->idx > csw2->idx) - return (1); - return (0); -} -RB_GENERATE_STATIC(control_sub_windows, control_sub_window, entry, - control_sub_window_cmp); - -/* Free a subscription. */ -static void -control_free_sub(struct control_state *cs, struct control_sub *csub) -{ - struct control_sub_pane *csp, *csp1; - struct control_sub_window *csw, *csw1; - - RB_FOREACH_SAFE(csp, control_sub_panes, &csub->panes, csp1) { - RB_REMOVE(control_sub_panes, &csub->panes, csp); - free(csp->last); - free(csp); - } - RB_FOREACH_SAFE(csw, control_sub_windows, &csub->windows, csw1) { - RB_REMOVE(control_sub_windows, &csub->windows, csw); - free(csw->last); - free(csw); - } - free(csub->last); - - RB_REMOVE(control_subs, &cs->subs, csub); - free(csub->name); - free(csub->format); - free(csub); -} - /* Free a block. */ static void control_free_block(struct control_state *cs, struct control_block *cb) @@ -767,6 +661,30 @@ control_write_callback(__unused struct bufferevent *bufev, void *data) bufferevent_disable(cs->write_event, EV_WRITE); } +/* Write a subscription change. */ +static void +control_sub_change(struct monitor_change *change, __unused void *data) +{ + struct client *c = change->c; + struct session *s = change->s; + struct winlink *wl = change->wl; + struct window_pane *wp = change->wp; + struct window *w; + + if (wp != NULL) { + w = wp->window; + control_write(c, "%%subscription-changed %s $%u @%u %u %%%u : %s", + change->name, s->id, w->id, wl->idx, wp->id, change->value); + } else if (wl != NULL) { + w = wl->window; + control_write(c, "%%subscription-changed %s $%u @%u %u - : %s", + change->name, s->id, w->id, wl->idx, change->value); + } else { + control_write(c, "%%subscription-changed %s $%u - - - : %s", + change->name, s->id, change->value); + } +} + /* Initialize for control mode. */ void control_start(struct client *c) @@ -784,7 +702,7 @@ control_start(struct client *c) RB_INIT(&cs->panes); TAILQ_INIT(&cs->pending_list); TAILQ_INIT(&cs->all_blocks); - RB_INIT(&cs->subs); + cs->subs = monitor_create(c, control_sub_change, NULL); cs->read_event = bufferevent_new(c->fd, control_read_callback, control_write_callback, control_error_callback, c); @@ -833,20 +751,16 @@ control_stop(struct client *c) { struct control_state *cs = c->control_state; struct control_block *cb, *cb1; - struct control_sub *csub, *csub1; if (cs == NULL) return; + monitor_destroy(cs->subs); + if (~c->flags & CLIENT_CONTROLCONTROL) bufferevent_free(cs->write_event); bufferevent_free(cs->read_event); - RB_FOREACH_SAFE(csub, control_subs, &cs->subs, csub1) - control_free_sub(cs, csub); - if (evtimer_initialized(&cs->subs_timer)) - evtimer_del(&cs->subs_timer); - control_reset_offsets(c); TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1) control_free_block(cs, cb); @@ -855,313 +769,14 @@ control_stop(struct client *c) free(cs); } -/* Check session subscription. */ -static void -control_check_subs_session(struct client *c, struct control_sub *csub, - struct format_tree *ft) -{ - struct session *s = c->session; - char *value; - - value = format_expand(ft, csub->format); - - if (csub->last != NULL && strcmp(value, csub->last) == 0) { - free(value); - return; - } - control_write(c, - "%%subscription-changed %s $%u - - - : %s", - csub->name, s->id, value); - free(csub->last); - csub->last = value; -} - -/* Check pane subscription. */ -static void -control_check_subs_pane(struct client *c, struct control_sub *csub) -{ - struct session *s = c->session; - struct window_pane *wp; - struct window *w; - struct winlink *wl; - struct format_tree *ft; - char *value; - struct control_sub_pane *csp, find; - - wp = window_pane_find_by_id(csub->id); - if (wp == NULL || wp->fd == -1) - return; - w = wp->window; - - TAILQ_FOREACH(wl, &w->winlinks, wentry) { - if (wl->session != s) - continue; - - ft = format_create_defaults(NULL, c, s, wl, wp); - value = format_expand(ft, csub->format); - format_free(ft); - - find.pane = wp->id; - find.idx = wl->idx; - - csp = RB_FIND(control_sub_panes, &csub->panes, &find); - if (csp == NULL) { - csp = xcalloc(1, sizeof *csp); - csp->pane = wp->id; - csp->idx = wl->idx; - RB_INSERT(control_sub_panes, &csub->panes, csp); - } - - if (csp->last != NULL && strcmp(value, csp->last) == 0) { - free(value); - continue; - } - control_write(c, - "%%subscription-changed %s $%u @%u %u %%%u : %s", - csub->name, s->id, w->id, wl->idx, wp->id, value); - free(csp->last); - csp->last = value; - } -} - -/* Check all-panes subscription for a pane. */ -static void -control_check_subs_all_panes_one(struct client *c, struct control_sub *csub, - struct format_tree *ft, struct winlink *wl, struct window_pane *wp) -{ - struct session *s = c->session; - struct window *w = wl->window; - char *value; - struct control_sub_pane *csp, find; - - value = format_expand(ft, csub->format); - - find.pane = wp->id; - find.idx = wl->idx; - - csp = RB_FIND(control_sub_panes, &csub->panes, &find); - if (csp == NULL) { - csp = xcalloc(1, sizeof *csp); - csp->pane = wp->id; - csp->idx = wl->idx; - RB_INSERT(control_sub_panes, &csub->panes, csp); - } - - if (csp->last != NULL && strcmp(value, csp->last) == 0) { - free(value); - return; - } - control_write(c, - "%%subscription-changed %s $%u @%u %u %%%u : %s", - csub->name, s->id, w->id, wl->idx, wp->id, value); - free(csp->last); - csp->last = value; -} - -/* Check window subscription. */ -static void -control_check_subs_window(struct client *c, struct control_sub *csub) -{ - struct session *s = c->session; - struct window *w; - struct winlink *wl; - struct format_tree *ft; - char *value; - struct control_sub_window *csw, find; - - w = window_find_by_id(csub->id); - if (w == NULL) - return; - - TAILQ_FOREACH(wl, &w->winlinks, wentry) { - if (wl->session != s) - continue; - - ft = format_create_defaults(NULL, c, s, wl, NULL); - value = format_expand(ft, csub->format); - format_free(ft); - - find.window = w->id; - find.idx = wl->idx; - - csw = RB_FIND(control_sub_windows, &csub->windows, &find); - if (csw == NULL) { - csw = xcalloc(1, sizeof *csw); - csw->window = w->id; - csw->idx = wl->idx; - RB_INSERT(control_sub_windows, &csub->windows, csw); - } - - if (csw->last != NULL && strcmp(value, csw->last) == 0) { - free(value); - continue; - } - control_write(c, - "%%subscription-changed %s $%u @%u %u - : %s", - csub->name, s->id, w->id, wl->idx, value); - free(csw->last); - csw->last = value; - } -} - -/* Check all-windows subscription for a window. */ -static void -control_check_subs_all_windows_one(struct client *c, struct control_sub *csub, - struct format_tree *ft, struct winlink *wl) -{ - struct session *s = c->session; - struct window *w = wl->window; - char *value; - struct control_sub_window *csw, find; - - value = format_expand(ft, csub->format); - - find.window = w->id; - find.idx = wl->idx; - - csw = RB_FIND(control_sub_windows, &csub->windows, &find); - if (csw == NULL) { - csw = xcalloc(1, sizeof *csw); - csw->window = w->id; - csw->idx = wl->idx; - RB_INSERT(control_sub_windows, &csub->windows, csw); - } - - if (csw->last != NULL && strcmp(value, csw->last) == 0) { - free(value); - return; - } - control_write(c, - "%%subscription-changed %s $%u @%u %u - : %s", - csub->name, s->id, w->id, wl->idx, value); - free(csw->last); - csw->last = value; -} - -/* Check subscriptions timer. */ -static void -control_check_subs_timer(__unused int fd, __unused short events, void *data) -{ - struct client *c = data; - struct control_state *cs = c->control_state; - struct control_sub *csub, *csub1; - struct session *s = c->session; - struct format_tree *ft; - struct winlink *wl; - struct window_pane *wp; - struct timeval tv = { .tv_sec = 1 }; - int have_session = 0, have_all_panes = 0; - int have_all_windows = 0; - - log_debug("%s: timer fired", __func__); - evtimer_add(&cs->subs_timer, &tv); - - if (s == NULL) - return; - - /* Find which subscription types are present. */ - RB_FOREACH(csub, control_subs, &cs->subs) { - switch (csub->type) { - case CONTROL_SUB_SESSION: - have_session = 1; - break; - case CONTROL_SUB_ALL_PANES: - have_all_panes = 1; - break; - case CONTROL_SUB_ALL_WINDOWS: - have_all_windows = 1; - break; - default: - break; - } - } - - /* Check session subscriptions. */ - if (have_session) { - ft = format_create_defaults(NULL, c, s, NULL, NULL); - RB_FOREACH_SAFE(csub, control_subs, &cs->subs, csub1) { - if (csub->type == CONTROL_SUB_SESSION) - control_check_subs_session(c, csub, ft); - } - format_free(ft); - } - - /* Check pane and window subscriptions. */ - RB_FOREACH_SAFE(csub, control_subs, &cs->subs, csub1) { - switch (csub->type) { - case CONTROL_SUB_PANE: - control_check_subs_pane(c, csub); - break; - case CONTROL_SUB_WINDOW: - control_check_subs_window(c, csub); - break; - case CONTROL_SUB_SESSION: - case CONTROL_SUB_ALL_PANES: - case CONTROL_SUB_ALL_WINDOWS: - break; - } - } - - /* Check all-panes subscriptions. */ - if (have_all_panes) { - RB_FOREACH(wl, winlinks, &s->windows) { - TAILQ_FOREACH(wp, &wl->window->panes, entry) { - ft = format_create_defaults(NULL, c, s, wl, wp); - RB_FOREACH_SAFE(csub, control_subs, &cs->subs, - csub1) { - if (csub->type != CONTROL_SUB_ALL_PANES) - continue; - control_check_subs_all_panes_one(c, - csub, ft, wl, wp); - } - format_free(ft); - } - } - } - - /* Check all-windows subscriptions. */ - if (have_all_windows) { - RB_FOREACH(wl, winlinks, &s->windows) { - ft = format_create_defaults(NULL, c, s, wl, NULL); - RB_FOREACH_SAFE(csub, control_subs, &cs->subs, - csub1) { - if (csub->type != CONTROL_SUB_ALL_WINDOWS) - continue; - control_check_subs_all_windows_one(c, csub, ft, - wl); - } - format_free(ft); - } - } -} - /* Add a subscription. */ void -control_add_sub(struct client *c, const char *name, enum control_sub_type type, +control_add_sub(struct client *c, const char *name, enum monitor_type type, int id, const char *format) { struct control_state *cs = c->control_state; - struct control_sub *csub, find; - struct timeval tv = { .tv_sec = 1 }; - find.name = (char *)name; - if ((csub = RB_FIND(control_subs, &cs->subs, &find)) != NULL) - control_free_sub(cs, csub); - - csub = xcalloc(1, sizeof *csub); - csub->name = xstrdup(name); - csub->type = type; - csub->id = id; - csub->format = xstrdup(format); - RB_INSERT(control_subs, &cs->subs, csub); - - RB_INIT(&csub->panes); - RB_INIT(&csub->windows); - - if (!evtimer_initialized(&cs->subs_timer)) - evtimer_set(&cs->subs_timer, control_check_subs_timer, c); - if (!evtimer_pending(&cs->subs_timer, NULL)) - evtimer_add(&cs->subs_timer, &tv); + monitor_add(cs->subs, name, type, id, format); } /* Remove a subscription. */ @@ -1169,11 +784,6 @@ void control_remove_sub(struct client *c, const char *name) { struct control_state *cs = c->control_state; - struct control_sub *csub, find; - find.name = (char *)name; - if ((csub = RB_FIND(control_subs, &cs->subs, &find)) != NULL) - control_free_sub(cs, csub); - if (RB_EMPTY(&cs->subs)) - evtimer_del(&cs->subs_timer); + monitor_remove(cs->subs, name); } diff --git a/monitor.c b/monitor.c new file mode 100644 index 000000000..512331e6a --- /dev/null +++ b/monitor.c @@ -0,0 +1,526 @@ +/* $OpenBSD$ */ + +/* + * 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 + * 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 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 + +#include "tmux.h" + +/* Subscription pane. */ +struct monitor_pane { + u_int pane; + u_int idx; + char *last; + + RB_ENTRY(monitor_pane) entry; +}; +RB_HEAD(monitor_panes, monitor_pane); + +/* Subscription window. */ +struct monitor_window { + u_int window; + u_int idx; + char *last; + + RB_ENTRY(monitor_window) entry; +}; +RB_HEAD(monitor_windows, monitor_window); + +/* Subscription. */ +struct monitor_item { + char *name; + char *format; + + enum monitor_type type; + u_int id; + + char *last; + struct monitor_panes panes; + struct monitor_windows windows; + + RB_ENTRY(monitor_item) entry; +}; +RB_HEAD(monitor_items, monitor_item); + +/* Monitored subscription set. */ +struct monitor_set { + struct client *client; + monitor_cb cb; + void *data; + + struct monitor_items items; + struct event timer; +}; + +static void monitor_timer(__unused int, __unused short, void *); + +/* Compare subscriptions. */ +static int +monitor_item_cmp(struct monitor_item *m1, struct monitor_item *m2) +{ + return (strcmp(m1->name, m2->name)); +} +RB_GENERATE_STATIC(monitor_items, monitor_item, entry, monitor_item_cmp); + +/* Compare subscription panes. */ +static int +monitor_pane_cmp(struct monitor_pane *mp1, struct monitor_pane *mp2) +{ + if (mp1->pane < mp2->pane) + return (-1); + if (mp1->pane > mp2->pane) + return (1); + if (mp1->idx < mp2->idx) + return (-1); + if (mp1->idx > mp2->idx) + return (1); + return (0); +} +RB_GENERATE_STATIC(monitor_panes, monitor_pane, entry, monitor_pane_cmp); + +/* Compare subscription windows. */ +static int +monitor_window_cmp(struct monitor_window *mw1, struct monitor_window *mw2) +{ + if (mw1->window < mw2->window) + return (-1); + if (mw1->window > mw2->window) + return (1); + if (mw1->idx < mw2->idx) + return (-1); + if (mw1->idx > mw2->idx) + return (1); + return (0); +} +RB_GENERATE_STATIC(monitor_windows, monitor_window, entry, monitor_window_cmp); + +/* Free a subscription. */ +static void +monitor_free_item(struct monitor_set *ms, struct monitor_item *me) +{ + struct monitor_pane *mp, *mp1; + struct monitor_window *mw, *mw1; + + RB_FOREACH_SAFE(mp, monitor_panes, &me->panes, mp1) { + RB_REMOVE(monitor_panes, &me->panes, mp); + free(mp->last); + free(mp); + } + RB_FOREACH_SAFE(mw, monitor_windows, &me->windows, mw1) { + RB_REMOVE(monitor_windows, &me->windows, mw); + free(mw->last); + free(mw); + } + free(me->last); + + RB_REMOVE(monitor_items, &ms->items, me); + free(me->name); + free(me->format); + free(me); +} + +/* Report a changed value. */ +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) +{ + struct monitor_change change; + + change.name = me->name; + change.value = value; + 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 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; + 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; +} + +/* Check pane subscription. */ +static void +monitor_check_pane(struct monitor_set *ms, struct monitor_item *me) +{ + struct client *c = ms->client; + struct session *s = c->session; + struct window_pane *wp; + struct window *w; + struct winlink *wl; + struct format_tree *ft; + char *value; + struct monitor_pane *mp, find; + + wp = window_pane_find_by_id(me->id); + if (wp == NULL || wp->fd == -1) + return; + w = wp->window; + + TAILQ_FOREACH(wl, &w->winlinks, wentry) { + if (wl->session != s) + continue; + + ft = format_create_defaults(NULL, c, s, wl, wp); + value = format_expand(ft, me->format); + format_free(ft); + + find.pane = wp->id; + find.idx = wl->idx; + mp = RB_FIND(monitor_panes, &me->panes, &find); + if (mp == NULL) { + mp = xcalloc(1, sizeof *mp); + mp->pane = wp->id; + mp->idx = wl->idx; + 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; + } +} + +/* Check one all-panes subscription. */ +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; + char *value; + struct monitor_pane *mp, find; + + value = format_expand(ft, me->format); + + find.pane = wp->id; + find.idx = wl->idx; + mp = RB_FIND(monitor_panes, &me->panes, &find); + if (mp == NULL) { + mp = xcalloc(1, sizeof *mp); + mp->pane = wp->id; + mp->idx = wl->idx; + RB_INSERT(monitor_panes, &me->panes, mp); + } + + if (mp->last != NULL && strcmp(value, mp->last) == 0) { + free(value); + return; + } + + monitor_report(ms, me, s, wl, wp, value); + free(mp->last); + mp->last = value; +} + +/* Check window subscription. */ +static void +monitor_check_window(struct monitor_set *ms, struct monitor_item *me) +{ + struct client *c = ms->client; + struct session *s = c->session; + struct window *w; + struct winlink *wl; + struct format_tree *ft; + char *value; + struct monitor_window *mw, find; + + w = window_find_by_id(me->id); + if (w == NULL) + return; + + TAILQ_FOREACH(wl, &w->winlinks, wentry) { + if (wl->session != s) + continue; + + ft = format_create_defaults(NULL, c, s, wl, NULL); + value = format_expand(ft, me->format); + format_free(ft); + + find.window = w->id; + find.idx = wl->idx; + mw = RB_FIND(monitor_windows, &me->windows, &find); + if (mw == NULL) { + mw = xcalloc(1, sizeof *mw); + mw->window = w->id; + mw->idx = wl->idx; + 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; + } +} + +/* Check one all-windows subscription. */ +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 window *w = wl->window; + char *value; + struct monitor_window *mw, find; + + value = format_expand(ft, me->format); + + find.window = w->id; + find.idx = wl->idx; + mw = RB_FIND(monitor_windows, &me->windows, &find); + if (mw == NULL) { + mw = xcalloc(1, sizeof *mw); + mw->window = w->id; + mw->idx = wl->idx; + RB_INSERT(monitor_windows, &me->windows, mw); + } + + if (mw->last != NULL && strcmp(value, mw->last) == 0) { + free(value); + return; + } + + monitor_report(ms, me, s, wl, NULL, value); + free(mw->last); + mw->last = value; +} + +/* Check session subscriptions. */ +static void +monitor_check_sessions(struct monitor_set *ms) +{ + struct client *c = ms->client; + struct session *s = c->session; + struct monitor_item *me, *me1; + struct format_tree *ft; + + ft = format_create_defaults(NULL, c, s, NULL, NULL); + RB_FOREACH_SAFE(me, monitor_items, &ms->items, me1) { + if (me->type == MONITOR_SESSION) + monitor_check_session(ms, me, ft); + } + format_free(ft); +} + +/* Check pane and window subscriptions. */ +static void +monitor_check_panes_windows(struct monitor_set *ms) +{ + struct monitor_item *me, *me1; + + RB_FOREACH_SAFE(me, monitor_items, &ms->items, me1) { + switch (me->type) { + case MONITOR_PANE: + monitor_check_pane(ms, me); + break; + case MONITOR_WINDOW: + monitor_check_window(ms, me); + break; + case MONITOR_SESSION: + case MONITOR_ALL_PANES: + case MONITOR_ALL_WINDOWS: + break; + } + } +} + +/* Check all-panes subscriptions. */ +static void +monitor_check_all_panes(struct monitor_set *ms) +{ + struct client *c = ms->client; + struct session *s = c->session; + struct monitor_item *me, *me1; + struct window_pane *wp; + struct format_tree *ft; + struct winlink *wl; + + RB_FOREACH(wl, winlinks, &s->windows) { + TAILQ_FOREACH(wp, &wl->window->panes, entry) { + ft = format_create_defaults(NULL, c, s, wl, wp); + RB_FOREACH_SAFE(me, monitor_items, &ms->items, me1) { + if (me->type != MONITOR_ALL_PANES) + continue; + monitor_check_all_panes_one(ms, me, ft, wl, wp); + } + format_free(ft); + } + } +} + +/* Check all-windows subscriptions. */ +static void +monitor_check_all_windows(struct monitor_set *ms) +{ + struct client *c = ms->client; + struct session *s = c->session; + struct monitor_item *me, *me1; + struct format_tree *ft; + struct winlink *wl; + + RB_FOREACH(wl, winlinks, &s->windows) { + ft = format_create_defaults(NULL, c, s, wl, NULL); + RB_FOREACH_SAFE(me, monitor_items, &ms->items, me1) { + if (me->type != MONITOR_ALL_WINDOWS) + continue; + monitor_check_all_windows_one(ms, me, ft, wl); + } + format_free(ft); + } +} + +/* Check subscriptions. */ +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; + int have_all_windows = 0; + + log_debug("%s: timer fired", __func__); + evtimer_add(&ms->timer, &tv); + + if (c->session == NULL) + return; + + RB_FOREACH(me, monitor_items, &ms->items) { + switch (me->type) { + case MONITOR_SESSION: + have_session = 1; + break; + case MONITOR_ALL_PANES: + have_all_panes = 1; + break; + case MONITOR_ALL_WINDOWS: + have_all_windows = 1; + break; + case MONITOR_PANE: + case MONITOR_WINDOW: + break; + } + } + + if (have_session) + monitor_check_sessions(ms); + monitor_check_panes_windows(ms); + if (have_all_panes) + monitor_check_all_panes(ms); + if (have_all_windows) + monitor_check_all_windows(ms); +} + +/* Create a monitor set. */ +struct monitor_set * +monitor_create(struct client *c, 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); +} + +/* Destroy a monitor set. */ +void +monitor_destroy(struct monitor_set *ms) +{ + struct monitor_item *me, *me1; + + 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); + free(ms); +} + +/* Add a subscription. */ +void +monitor_add(struct monitor_set *ms, const char *name, enum monitor_type type, + int id, const char *format) +{ + struct monitor_item *me, find = { .name = (char *)name }; + struct timeval tv = { .tv_sec = 1 }; + + if ((me = RB_FIND(monitor_items, &ms->items, &find)) != NULL) + monitor_free_item(ms, me); + + me = xcalloc(1, sizeof *me); + me->name = xstrdup(name); + me->format = xstrdup(format); + me->type = type; + me->id = id; + RB_INIT(&me->panes); + RB_INIT(&me->windows); + RB_INSERT(monitor_items, &ms->items, me); + + if (!evtimer_initialized(&ms->timer)) + evtimer_set(&ms->timer, monitor_timer, ms); + if (!evtimer_pending(&ms->timer, NULL)) + evtimer_add(&ms->timer, &tv); +} + +/* Remove a subscription. */ +void +monitor_remove(struct monitor_set *ms, const char *name) +{ + struct monitor_item *me, find = { .name = (char *)name }; + + if ((me = RB_FIND(monitor_items, &ms->items, &find)) != NULL) + monitor_free_item(ms, me); + if (RB_EMPTY(&ms->items) && evtimer_initialized(&ms->timer)) + evtimer_del(&ms->timer); +} diff --git a/tmux.h b/tmux.h index 62763a6f4..9fa174a82 100644 --- a/tmux.h +++ b/tmux.h @@ -2274,14 +2274,24 @@ struct client { }; TAILQ_HEAD(clients, client); -/* Control mode subscription type. */ -enum control_sub_type { - CONTROL_SUB_SESSION, - CONTROL_SUB_PANE, - CONTROL_SUB_ALL_PANES, - CONTROL_SUB_WINDOW, - CONTROL_SUB_ALL_WINDOWS +/* Monitor. */ +enum monitor_type { + MONITOR_SESSION, + MONITOR_PANE, + MONITOR_ALL_PANES, + MONITOR_WINDOW, + MONITOR_ALL_WINDOWS }; +struct monitor_change { + const char *name; + const char *value; + + struct client *c; + struct session *s; + struct winlink *wl; + struct window_pane *wp; +}; +typedef void (*monitor_cb)(struct monitor_change *, void *); /* Key binding and key table. */ struct key_binding { @@ -3794,6 +3804,13 @@ void check_window_name(struct window *); char *default_window_name(struct window *); char *parse_window_name(const char *); +/* monitor.c */ +struct monitor_set *monitor_create(struct client *, monitor_cb, void *); +void monitor_destroy(struct monitor_set *); +void monitor_add(struct monitor_set *, const char *, enum monitor_type, int, + const char *); +void monitor_remove(struct monitor_set *, const char *); + /* control.c */ void control_discard(struct client *); void control_start(struct client *); @@ -3809,8 +3826,8 @@ void control_reset_offsets(struct client *); void printflike(2, 3) control_write(struct client *, const char *, ...); void control_write_output(struct client *, struct window_pane *); int control_all_done(struct client *); -void control_add_sub(struct client *, const char *, enum control_sub_type, - int, const char *); +void control_add_sub(struct client *, const char *, enum monitor_type, int, + const char *); void control_remove_sub(struct client *, const char *); /* control-notify.c */