/* $OpenBSD: control.c,v 1.45 2020/09/18 11:20:59 nicm Exp $ */ /* * Copyright (c) 2012 Nicholas Marriott * Copyright (c) 2012 George Nachman * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include "tmux.h" /* * Block of data to output. Each client has one "all" queue of blocks and * another queue for each pane (in struct client_offset). %output blocks are * added to both queues and other output lines (notifications) added only to * the client queue. * * When a client becomes writeable, data from blocks on the pane queue are sent * up to the maximum size (CLIENT_BUFFER_HIGH). If a block is entirely written, * it is removed from both pane and client queues and if this means non-%output * blocks are now at the head of the client queue, they are written. * * This means a %output block holds up any subsequent non-%output blocks until * it is written which enforces ordering even if the client cannot accept the * entire block in one go. */ struct control_block { size_t size; char *line; uint64_t t; TAILQ_ENTRY(control_block) entry; TAILQ_ENTRY(control_block) all_entry; }; /* Control client pane. */ struct control_pane { u_int pane; /* * Offsets into the pane data. The first (offset) is the data we have * written; the second (queued) the data we have queued (pointed to by * a block). */ struct window_pane_offset offset; struct window_pane_offset queued; int flags; #define CONTROL_PANE_OFF 0x1 #define CONTROL_PANE_PAUSED 0x2 int pending_flag; TAILQ_ENTRY(control_pane) pending_entry; TAILQ_HEAD(, control_block) blocks; RB_ENTRY(control_pane) entry; }; 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; TAILQ_HEAD(, control_pane) pending_list; u_int pending_count; TAILQ_HEAD(, control_block) all_blocks; struct bufferevent *read_event; struct bufferevent *write_event; struct control_subs subs; struct event subs_timer; }; /* Low and high watermarks. */ #define CONTROL_BUFFER_LOW 512 #define CONTROL_BUFFER_HIGH 8192 /* Minimum to write to each client. */ #define CONTROL_WRITE_MINIMUM 32 /* Maximum age for clients that are not using pause mode. */ #define CONTROL_MAXIMUM_AGE 300000 /* Flags to ignore client. */ #define CONTROL_IGNORE_FLAGS \ (CLIENT_CONTROL_NOOUTPUT| \ CLIENT_UNATTACHEDFLAGS) /* Compare client panes. */ static int control_pane_cmp(struct control_pane *cp1, struct control_pane *cp2) { if (cp1->pane < cp2->pane) return (-1); if (cp1->pane > cp2->pane) return (1); return (0); } 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) { free(cb->line); TAILQ_REMOVE(&cs->all_blocks, cb, all_entry); free(cb); } /* Get pane offsets for this client. */ static struct control_pane * control_get_pane(struct client *c, struct window_pane *wp) { struct control_state *cs = c->control_state; struct control_pane cp = { .pane = wp->id }; return (RB_FIND(control_panes, &cs->panes, &cp)); } /* Add pane offsets for this client. */ static struct control_pane * control_add_pane(struct client *c, struct window_pane *wp) { struct control_state *cs = c->control_state; struct control_pane *cp; cp = control_get_pane(c, wp); if (cp != NULL) return (cp); cp = xcalloc(1, sizeof *cp); cp->pane = wp->id; RB_INSERT(control_panes, &cs->panes, cp); memcpy(&cp->offset, &wp->offset, sizeof cp->offset); memcpy(&cp->queued, &wp->offset, sizeof cp->queued); TAILQ_INIT(&cp->blocks); return (cp); } /* Discard output for a pane. */ static void control_discard_pane(struct client *c, struct control_pane *cp) { struct control_state *cs = c->control_state; struct control_block *cb, *cb1; TAILQ_FOREACH_SAFE(cb, &cp->blocks, entry, cb1) { TAILQ_REMOVE(&cp->blocks, cb, entry); control_free_block(cs, cb); } } /* Get actual pane for this client. */ static struct window_pane * control_window_pane(struct client *c, u_int pane) { struct window_pane *wp; if (c->session == NULL) return (NULL); if ((wp = window_pane_find_by_id(pane)) == NULL) return (NULL); if (winlink_find_by_window(&c->session->windows, wp->window) == NULL) return (NULL); return (wp); } /* Reset control offsets. */ void control_reset_offsets(struct client *c) { struct control_state *cs = c->control_state; struct control_pane *cp, *cp1; RB_FOREACH_SAFE(cp, control_panes, &cs->panes, cp1) { RB_REMOVE(control_panes, &cs->panes, cp); free(cp); } TAILQ_INIT(&cs->pending_list); cs->pending_count = 0; } /* Get offsets for client. */ struct window_pane_offset * control_pane_offset(struct client *c, struct window_pane *wp, int *off) { struct control_state *cs = c->control_state; struct control_pane *cp; if (c->flags & CLIENT_CONTROL_NOOUTPUT) { *off = 0; return (NULL); } cp = control_get_pane(c, wp); if (cp == NULL || (cp->flags & CONTROL_PANE_PAUSED)) { *off = 0; return (NULL); } if (cp->flags & CONTROL_PANE_OFF) { *off = 1; return (NULL); } *off = (EVBUFFER_LENGTH(cs->write_event->output) >= CONTROL_BUFFER_LOW); return (&cp->offset); } /* Set pane as on. */ void control_set_pane_on(struct client *c, struct window_pane *wp) { struct control_pane *cp; cp = control_get_pane(c, wp); if (cp != NULL && (cp->flags & CONTROL_PANE_OFF)) { cp->flags &= ~CONTROL_PANE_OFF; memcpy(&cp->offset, &wp->offset, sizeof cp->offset); memcpy(&cp->queued, &wp->offset, sizeof cp->queued); } } /* Set pane as off. */ void control_set_pane_off(struct client *c, struct window_pane *wp) { struct control_pane *cp; cp = control_add_pane(c, wp); cp->flags |= CONTROL_PANE_OFF; } /* Continue a paused pane. */ void control_continue_pane(struct client *c, struct window_pane *wp) { struct control_pane *cp; cp = control_get_pane(c, wp); if (cp != NULL && (cp->flags & CONTROL_PANE_PAUSED)) { cp->flags &= ~CONTROL_PANE_PAUSED; memcpy(&cp->offset, &wp->offset, sizeof cp->offset); memcpy(&cp->queued, &wp->offset, sizeof cp->queued); control_write(c, "%%continue %%%u", wp->id); } } /* Pause a pane. */ void control_pause_pane(struct client *c, struct window_pane *wp) { struct control_pane *cp; cp = control_add_pane(c, wp); if (~cp->flags & CONTROL_PANE_PAUSED) { cp->flags |= CONTROL_PANE_PAUSED; control_discard_pane(c, cp); control_write(c, "%%pause %%%u", wp->id); } } /* Write a line. */ static void control_vwrite(struct client *c, const char *fmt, va_list ap) { struct control_state *cs = c->control_state; char *s; xvasprintf(&s, fmt, ap); log_debug("%s: %s: writing line: %s", __func__, c->name, s); bufferevent_write(cs->write_event, s, strlen(s)); bufferevent_write(cs->write_event, "\n", 1); bufferevent_enable(cs->write_event, EV_WRITE); free(s); } /* Write a line. */ void control_write(struct client *c, const char *fmt, ...) { struct control_state *cs = c->control_state; struct control_block *cb; va_list ap; va_start(ap, fmt); if (TAILQ_EMPTY(&cs->all_blocks)) { control_vwrite(c, fmt, ap); va_end(ap); return; } cb = xcalloc(1, sizeof *cb); xvasprintf(&cb->line, fmt, ap); TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry); cb->t = get_timer(); log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line); bufferevent_enable(cs->write_event, EV_WRITE); va_end(ap); } /* Check age for this pane. */ static int control_check_age(struct client *c, struct window_pane *wp, struct control_pane *cp) { struct control_block *cb; uint64_t t, age; cb = TAILQ_FIRST(&cp->blocks); if (cb == NULL) return (0); t = get_timer(); if (cb->t >= t) return (0); age = t - cb->t; log_debug("%s: %s: %%%u is %llu behind", __func__, c->name, wp->id, (unsigned long long)age); if (c->flags & CLIENT_CONTROL_PAUSEAFTER) { if (age < c->pause_age) return (0); cp->flags |= CONTROL_PANE_PAUSED; control_discard_pane(c, cp); control_write(c, "%%pause %%%u", wp->id); } else { if (age < CONTROL_MAXIMUM_AGE) return (0); c->exit_message = xstrdup("too far behind"); c->flags |= CLIENT_EXIT; control_discard(c); } return (1); } /* Write output from a pane. */ void control_write_output(struct client *c, struct window_pane *wp) { struct control_state *cs = c->control_state; struct control_pane *cp; struct control_block *cb; size_t new_size; if (winlink_find_by_window(&c->session->windows, wp->window) == NULL) return; if (c->flags & CONTROL_IGNORE_FLAGS) { cp = control_get_pane(c, wp); if (cp != NULL) goto ignore; return; } cp = control_add_pane(c, wp); if (cp->flags & (CONTROL_PANE_OFF|CONTROL_PANE_PAUSED)) goto ignore; if (control_check_age(c, wp, cp)) return; window_pane_get_new_data(wp, &cp->queued, &new_size); if (new_size == 0) return; window_pane_update_used_data(wp, &cp->queued, new_size); cb = xcalloc(1, sizeof *cb); cb->size = new_size; TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry); cb->t = get_timer(); TAILQ_INSERT_TAIL(&cp->blocks, cb, entry); log_debug("%s: %s: new output block of %zu for %%%u", __func__, c->name, cb->size, wp->id); if (!cp->pending_flag) { log_debug("%s: %s: %%%u now pending", __func__, c->name, wp->id); TAILQ_INSERT_TAIL(&cs->pending_list, cp, pending_entry); cp->pending_flag = 1; cs->pending_count++; } bufferevent_enable(cs->write_event, EV_WRITE); return; ignore: log_debug("%s: %s: ignoring pane %%%u", __func__, c->name, wp->id); window_pane_update_used_data(wp, &cp->offset, SIZE_MAX); window_pane_update_used_data(wp, &cp->queued, SIZE_MAX); } /* Control client error callback. */ static enum cmd_retval control_error(struct cmdq_item *item, void *data) { struct client *c = cmdq_get_client(item); char *error = data; cmdq_guard(item, "begin", 1); control_write(c, "parse error: %s", error); cmdq_guard(item, "error", 1); free(error); return (CMD_RETURN_NORMAL); } /* Control client error callback. */ static void control_error_callback(__unused struct bufferevent *bufev, __unused short what, void *data) { struct client *c = data; c->flags |= CLIENT_EXIT; } /* Control client input callback. Read lines and fire commands. */ static void control_read_callback(__unused struct bufferevent *bufev, void *data) { struct client *c = data; struct control_state *cs = c->control_state; struct evbuffer *buffer = cs->read_event->input; char *line, *error; struct cmdq_state *state; enum cmd_parse_status status; for (;;) { line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_LF); if (line == NULL) break; log_debug("%s: %s: %s", __func__, c->name, line); if (*line == '\0') { /* empty line detach */ free(line); c->flags |= CLIENT_EXIT; break; } state = cmdq_new_state(NULL, NULL, CMDQ_STATE_CONTROL); status = cmd_parse_and_append(line, NULL, c, state, &error); if (status == CMD_PARSE_ERROR) cmdq_append(c, cmdq_get_callback(control_error, error)); cmdq_free_state(state); free(line); } } /* Does this control client have outstanding data to write? */ int control_all_done(struct client *c) { struct control_state *cs = c->control_state; if (!TAILQ_EMPTY(&cs->all_blocks)) return (0); return (EVBUFFER_LENGTH(cs->write_event->output) == 0); } /* Flush all blocks until output. */ static void control_flush_all_blocks(struct client *c) { struct control_state *cs = c->control_state; struct control_block *cb, *cb1; TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1) { if (cb->size != 0) break; log_debug("%s: %s: flushing line: %s", __func__, c->name, cb->line); bufferevent_write(cs->write_event, cb->line, strlen(cb->line)); bufferevent_write(cs->write_event, "\n", 1); control_free_block(cs, cb); } } /* Append data to buffer. */ static struct evbuffer * control_append_data(struct client *c, struct control_pane *cp, uint64_t age, struct evbuffer *message, struct window_pane *wp, size_t size) { u_char *new_data; size_t new_size; u_int i; if (message == NULL) { message = evbuffer_new(); if (message == NULL) fatalx("out of memory"); if (c->flags & CLIENT_CONTROL_PAUSEAFTER) { evbuffer_add_printf(message, "%%extended-output %%%u %llu : ", wp->id, (unsigned long long)age); } else evbuffer_add_printf(message, "%%output %%%u ", wp->id); } new_data = window_pane_get_new_data(wp, &cp->offset, &new_size); if (new_size < size) fatalx("not enough data: %zu < %zu", new_size, size); for (i = 0; i < size; i++) { if (new_data[i] < ' ' || new_data[i] == '\\') evbuffer_add_printf(message, "\\%03o", new_data[i]); else evbuffer_add_printf(message, "%c", new_data[i]); } window_pane_update_used_data(wp, &cp->offset, size); return (message); } /* Write buffer. */ static void control_write_data(struct client *c, struct evbuffer *message) { struct control_state *cs = c->control_state; log_debug("%s: %s: %.*s", __func__, c->name, (int)EVBUFFER_LENGTH(message), EVBUFFER_DATA(message)); evbuffer_add(message, "\n", 1); bufferevent_write_buffer(cs->write_event, message); evbuffer_free(message); } /* Write output to client. */ static int control_write_pending(struct client *c, struct control_pane *cp, size_t limit) { struct control_state *cs = c->control_state; struct window_pane *wp = NULL; struct evbuffer *message = NULL; size_t used = 0, size; struct control_block *cb, *cb1; uint64_t age, t = get_timer(); wp = control_window_pane(c, cp->pane); if (wp == NULL) { TAILQ_FOREACH_SAFE(cb, &cp->blocks, entry, cb1) { TAILQ_REMOVE(&cp->blocks, cb, entry); control_free_block(cs, cb); } control_flush_all_blocks(c); return (0); } while (used != limit && !TAILQ_EMPTY(&cp->blocks)) { if (control_check_age(c, wp, cp)) { if (message != NULL) evbuffer_free(message); message = NULL; break; } cb = TAILQ_FIRST(&cp->blocks); if (cb->t < t) age = t - cb->t; else age = 0; log_debug("%s: %s: output block %zu (age %llu) for %%%u " "(used %zu/%zu)", __func__, c->name, cb->size, (unsigned long long)age, cp->pane, used, limit); size = cb->size; if (size > limit - used) size = limit - used; used += size; message = control_append_data(c, cp, age, message, wp, size); cb->size -= size; if (cb->size == 0) { TAILQ_REMOVE(&cp->blocks, cb, entry); control_free_block(cs, cb); cb = TAILQ_FIRST(&cs->all_blocks); if (cb != NULL && cb->size == 0) { if (wp != NULL && message != NULL) { control_write_data(c, message); message = NULL; } control_flush_all_blocks(c); } } } if (message != NULL) control_write_data(c, message); return (!TAILQ_EMPTY(&cp->blocks)); } /* Control client write callback. */ static void control_write_callback(__unused struct bufferevent *bufev, void *data) { struct client *c = data; struct control_state *cs = c->control_state; struct control_pane *cp, *cp1; struct evbuffer *evb = cs->write_event->output; size_t space, limit; control_flush_all_blocks(c); while (EVBUFFER_LENGTH(evb) < CONTROL_BUFFER_HIGH) { if (cs->pending_count == 0) break; space = CONTROL_BUFFER_HIGH - EVBUFFER_LENGTH(evb); log_debug("%s: %s: %zu bytes available, %u panes", __func__, c->name, space, cs->pending_count); limit = (space / cs->pending_count / 3); /* 3 bytes for \xxx */ if (limit < CONTROL_WRITE_MINIMUM) limit = CONTROL_WRITE_MINIMUM; TAILQ_FOREACH_SAFE(cp, &cs->pending_list, pending_entry, cp1) { if (EVBUFFER_LENGTH(evb) >= CONTROL_BUFFER_HIGH) break; if (control_write_pending(c, cp, limit)) continue; TAILQ_REMOVE(&cs->pending_list, cp, pending_entry); cp->pending_flag = 0; cs->pending_count--; } } if (EVBUFFER_LENGTH(evb) == 0) bufferevent_disable(cs->write_event, EV_WRITE); } /* Initialize for control mode. */ void control_start(struct client *c) { struct control_state *cs; if (c->flags & CLIENT_CONTROLCONTROL) { close(c->out_fd); c->out_fd = -1; } else setblocking(c->out_fd, 0); setblocking(c->fd, 0); cs = c->control_state = xcalloc(1, sizeof *cs); 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); bufferevent_enable(cs->read_event, EV_READ); if (c->flags & CLIENT_CONTROLCONTROL) cs->write_event = cs->read_event; else { cs->write_event = bufferevent_new(c->out_fd, NULL, control_write_callback, control_error_callback, c); } bufferevent_setwatermark(cs->write_event, EV_WRITE, CONTROL_BUFFER_LOW, 0); if (c->flags & CLIENT_CONTROLCONTROL) { bufferevent_write(cs->write_event, "\033P1000p", 7); bufferevent_enable(cs->write_event, EV_WRITE); } } /* Discard all output for a client. */ void control_discard(struct client *c) { struct control_state *cs = c->control_state; struct control_pane *cp; RB_FOREACH(cp, control_panes, &cs->panes) control_discard_pane(c, cp); bufferevent_disable(cs->read_event, EV_READ); } /* Stop control mode. */ void 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); }