summaryrefslogtreecommitdiffstats
path: root/control.c
diff options
context:
space:
mode:
authornicm <nicm>2020-07-06 09:14:20 +0000
committernicm <nicm>2020-07-06 09:14:20 +0000
commit66d5e5de7ac6a81f638d1a2b2f5262368a68fee2 (patch)
treea633b354a3514ed0fff68aea74fd806327039458 /control.c
parent2bf612a8066ab75725eeb09adf1ebc0f4fe851ab (diff)
Add a way for control mode clients to subscribe to a format and be
notified of changes rather than having to poll. GitHub issue 2242.
Diffstat (limited to 'control.c')
-rw-r--r--control.c391
1 files changed, 391 insertions, 0 deletions
diff --git a/control.c b/control.c
index 5681d2dc..c52f2020 100644
--- a/control.c
+++ b/control.c
@@ -76,6 +76,42 @@ 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;
@@ -87,6 +123,9 @@ struct control_state {
struct bufferevent *read_event;
struct bufferevent *write_event;
+
+ struct control_subs subs;
+ struct event subs_timer;
};
/* Low and high watermarks. */
@@ -116,6 +155,73 @@ 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);
+ }
+ RB_FOREACH_SAFE(csw, control_sub_windows, &csub->windows, csw1) {
+ RB_REMOVE(control_sub_windows, &csub->windows, csw);
+ 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)
@@ -666,6 +772,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->read_event = bufferevent_new(c->fd, control_read_callback,
control_write_callback, control_error_callback, c);
@@ -704,14 +811,298 @@ control_stop(struct client *c)
{
struct control_state *cs = c->control_state;
struct control_block *cb, *cb1;
+ struct control_sub *csub, *csub1;
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);
+
TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1)
control_free_block(cs, cb);
control_reset_offsets(c);
free(cs);
}
+
+/* Check session subscription. */
+static void
+control_check_subs_session(struct client *c, struct control_sub *csub)
+{
+ struct session *s = c->session;
+ struct format_tree *ft;
+ char *value;
+
+ ft = format_create_defaults(NULL, c, s, NULL, NULL);
+ value = format_expand(ft, csub->format);
+ format_free(ft);
+
+ 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)
+ 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. */
+static void
+control_check_subs_all_panes(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;
+
+ RB_FOREACH(wl, winlinks, &s->windows) {
+ w = wl->window;
+ TAILQ_FOREACH(wp, &w->panes, entry) {
+ 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 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. */
+static void
+control_check_subs_all_windows(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;
+
+ RB_FOREACH(wl, winlinks, &s->windows) {
+ w = wl->window;
+
+ 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 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 timeval tv = { .tv_sec = 1 };
+
+ log_debug("%s: timer fired", __func__);
+ evtimer_add(&cs->subs_timer, &tv);
+
+ RB_FOREACH_SAFE(csub, control_subs, &cs->subs, csub1) {
+ switch (csub->type) {
+ case CONTROL_SUB_SESSION:
+ control_check_subs_session(c, csub);
+ break;
+ case CONTROL_SUB_PANE:
+ control_check_subs_pane(c, csub);
+ break;
+ case CONTROL_SUB_ALL_PANES:
+ control_check_subs_all_panes(c, csub);
+ break;
+ case CONTROL_SUB_WINDOW:
+ control_check_subs_window(c, csub);
+ break;
+ case CONTROL_SUB_ALL_WINDOWS:
+ control_check_subs_all_windows(c, csub);
+ break;
+ }
+ }
+}
+
+/* Add a subscription. */
+void
+control_add_sub(struct client *c, const char *name, enum control_sub_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);
+}
+
+/* Remove a subscription. */
+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);
+}