1*7c8808e4Snicm /* $OpenBSD: control.c,v 1.49 2022/08/24 07:22:30 nicm Exp $ */
290896992Snicm
390896992Snicm /*
498ca8272Snicm * Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
590896992Snicm * Copyright (c) 2012 George Nachman <tmux@georgester.com>
690896992Snicm *
790896992Snicm * Permission to use, copy, modify, and distribute this software for any
890896992Snicm * purpose with or without fee is hereby granted, provided that the above
990896992Snicm * copyright notice and this permission notice appear in all copies.
1090896992Snicm *
1190896992Snicm * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
1290896992Snicm * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
1390896992Snicm * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
1490896992Snicm * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
1590896992Snicm * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
1690896992Snicm * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
1790896992Snicm * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
1890896992Snicm */
1990896992Snicm
2090896992Snicm #include <sys/types.h>
2190896992Snicm
2290896992Snicm #include <event.h>
237d053cf9Snicm #include <stdlib.h>
2490896992Snicm #include <string.h>
256ba5a684Snicm #include <time.h>
2654e3eca9Snicm #include <unistd.h>
2790896992Snicm
2890896992Snicm #include "tmux.h"
2990896992Snicm
30a34cf9c8Snicm /*
31a34cf9c8Snicm * Block of data to output. Each client has one "all" queue of blocks and
32a34cf9c8Snicm * another queue for each pane (in struct client_offset). %output blocks are
33a34cf9c8Snicm * added to both queues and other output lines (notifications) added only to
34a34cf9c8Snicm * the client queue.
35a34cf9c8Snicm *
36a34cf9c8Snicm * When a client becomes writeable, data from blocks on the pane queue are sent
37a34cf9c8Snicm * up to the maximum size (CLIENT_BUFFER_HIGH). If a block is entirely written,
38a34cf9c8Snicm * it is removed from both pane and client queues and if this means non-%output
39a34cf9c8Snicm * blocks are now at the head of the client queue, they are written.
40a34cf9c8Snicm *
41a34cf9c8Snicm * This means a %output block holds up any subsequent non-%output blocks until
42a34cf9c8Snicm * it is written which enforces ordering even if the client cannot accept the
43a34cf9c8Snicm * entire block in one go.
44a34cf9c8Snicm */
45a34cf9c8Snicm struct control_block {
46a34cf9c8Snicm size_t size;
47a34cf9c8Snicm char *line;
48cf8132c5Snicm uint64_t t;
49a34cf9c8Snicm
50a34cf9c8Snicm TAILQ_ENTRY(control_block) entry;
51a34cf9c8Snicm TAILQ_ENTRY(control_block) all_entry;
52a34cf9c8Snicm };
53a34cf9c8Snicm
54a34cf9c8Snicm /* Control client pane. */
55a34cf9c8Snicm struct control_pane {
56b271fc7fSnicm u_int pane;
57b271fc7fSnicm
58a34cf9c8Snicm /*
59a34cf9c8Snicm * Offsets into the pane data. The first (offset) is the data we have
60a34cf9c8Snicm * written; the second (queued) the data we have queued (pointed to by
61a34cf9c8Snicm * a block).
62a34cf9c8Snicm */
63b271fc7fSnicm struct window_pane_offset offset;
64a34cf9c8Snicm struct window_pane_offset queued;
65b271fc7fSnicm
66a34cf9c8Snicm int flags;
67a34cf9c8Snicm #define CONTROL_PANE_OFF 0x1
683e796c5aSnicm #define CONTROL_PANE_PAUSED 0x2
69a34cf9c8Snicm
70a34cf9c8Snicm int pending_flag;
71a34cf9c8Snicm TAILQ_ENTRY(control_pane) pending_entry;
72a34cf9c8Snicm
73a34cf9c8Snicm TAILQ_HEAD(, control_block) blocks;
74a34cf9c8Snicm
75a34cf9c8Snicm RB_ENTRY(control_pane) entry;
76b271fc7fSnicm };
77a34cf9c8Snicm RB_HEAD(control_panes, control_pane);
78b271fc7fSnicm
79d3c4e94bSnicm /* Subscription pane. */
80d3c4e94bSnicm struct control_sub_pane {
81d3c4e94bSnicm u_int pane;
82d3c4e94bSnicm u_int idx;
83d3c4e94bSnicm char *last;
84d3c4e94bSnicm
85d3c4e94bSnicm RB_ENTRY(control_sub_pane) entry;
86d3c4e94bSnicm };
87d3c4e94bSnicm RB_HEAD(control_sub_panes, control_sub_pane);
88d3c4e94bSnicm
89d3c4e94bSnicm /* Subscription window. */
90d3c4e94bSnicm struct control_sub_window {
91d3c4e94bSnicm u_int window;
92d3c4e94bSnicm u_int idx;
93d3c4e94bSnicm char *last;
94d3c4e94bSnicm
95d3c4e94bSnicm RB_ENTRY(control_sub_window) entry;
96d3c4e94bSnicm };
97d3c4e94bSnicm RB_HEAD(control_sub_windows, control_sub_window);
98d3c4e94bSnicm
99d3c4e94bSnicm /* Control client subscription. */
100d3c4e94bSnicm struct control_sub {
101d3c4e94bSnicm char *name;
102d3c4e94bSnicm char *format;
103d3c4e94bSnicm
104d3c4e94bSnicm enum control_sub_type type;
105d3c4e94bSnicm u_int id;
106d3c4e94bSnicm
107d3c4e94bSnicm char *last;
108d3c4e94bSnicm struct control_sub_panes panes;
109d3c4e94bSnicm struct control_sub_windows windows;
110d3c4e94bSnicm
111d3c4e94bSnicm RB_ENTRY(control_sub) entry;
112d3c4e94bSnicm };
113d3c4e94bSnicm RB_HEAD(control_subs, control_sub);
114d3c4e94bSnicm
11554e3eca9Snicm /* Control client state. */
11612bad209Snicm struct control_state {
117a34cf9c8Snicm struct control_panes panes;
118a34cf9c8Snicm
119a34cf9c8Snicm TAILQ_HEAD(, control_pane) pending_list;
120a34cf9c8Snicm u_int pending_count;
121a34cf9c8Snicm
122a34cf9c8Snicm TAILQ_HEAD(, control_block) all_blocks;
12354e3eca9Snicm
12454e3eca9Snicm struct bufferevent *read_event;
12554e3eca9Snicm struct bufferevent *write_event;
126d3c4e94bSnicm
127d3c4e94bSnicm struct control_subs subs;
128d3c4e94bSnicm struct event subs_timer;
12912bad209Snicm };
13012bad209Snicm
131e09158d2Snicm /* Low and high watermarks. */
132a34cf9c8Snicm #define CONTROL_BUFFER_LOW 512
133a34cf9c8Snicm #define CONTROL_BUFFER_HIGH 8192
134a34cf9c8Snicm
135a34cf9c8Snicm /* Minimum to write to each client. */
136a34cf9c8Snicm #define CONTROL_WRITE_MINIMUM 32
137a34cf9c8Snicm
138e09158d2Snicm /* Maximum age for clients that are not using pause mode. */
139e09158d2Snicm #define CONTROL_MAXIMUM_AGE 300000
140e09158d2Snicm
141a34cf9c8Snicm /* Flags to ignore client. */
142a34cf9c8Snicm #define CONTROL_IGNORE_FLAGS \
143a34cf9c8Snicm (CLIENT_CONTROL_NOOUTPUT| \
144a34cf9c8Snicm CLIENT_UNATTACHEDFLAGS)
145a34cf9c8Snicm
146a34cf9c8Snicm /* Compare client panes. */
147b271fc7fSnicm static int
control_pane_cmp(struct control_pane * cp1,struct control_pane * cp2)148a34cf9c8Snicm control_pane_cmp(struct control_pane *cp1, struct control_pane *cp2)
149b271fc7fSnicm {
150a34cf9c8Snicm if (cp1->pane < cp2->pane)
151b271fc7fSnicm return (-1);
152a34cf9c8Snicm if (cp1->pane > cp2->pane)
153b271fc7fSnicm return (1);
154b271fc7fSnicm return (0);
155b271fc7fSnicm }
156a34cf9c8Snicm RB_GENERATE_STATIC(control_panes, control_pane, entry, control_pane_cmp);
157a34cf9c8Snicm
158d3c4e94bSnicm /* Compare client subs. */
159d3c4e94bSnicm static int
control_sub_cmp(struct control_sub * csub1,struct control_sub * csub2)160d3c4e94bSnicm control_sub_cmp(struct control_sub *csub1, struct control_sub *csub2)
161d3c4e94bSnicm {
162d3c4e94bSnicm return (strcmp(csub1->name, csub2->name));
163d3c4e94bSnicm }
164d3c4e94bSnicm RB_GENERATE_STATIC(control_subs, control_sub, entry, control_sub_cmp);
165d3c4e94bSnicm
166d3c4e94bSnicm /* Compare client subscription panes. */
167d3c4e94bSnicm static int
control_sub_pane_cmp(struct control_sub_pane * csp1,struct control_sub_pane * csp2)168d3c4e94bSnicm control_sub_pane_cmp(struct control_sub_pane *csp1,
169d3c4e94bSnicm struct control_sub_pane *csp2)
170d3c4e94bSnicm {
171d3c4e94bSnicm if (csp1->pane < csp2->pane)
172d3c4e94bSnicm return (-1);
173d3c4e94bSnicm if (csp1->pane > csp2->pane)
174d3c4e94bSnicm return (1);
175d3c4e94bSnicm if (csp1->idx < csp2->idx)
176d3c4e94bSnicm return (-1);
177d3c4e94bSnicm if (csp1->idx > csp2->idx)
178d3c4e94bSnicm return (1);
179d3c4e94bSnicm return (0);
180d3c4e94bSnicm }
181d3c4e94bSnicm RB_GENERATE_STATIC(control_sub_panes, control_sub_pane, entry,
182d3c4e94bSnicm control_sub_pane_cmp);
183d3c4e94bSnicm
184d3c4e94bSnicm /* Compare client subscription windows. */
185d3c4e94bSnicm static int
control_sub_window_cmp(struct control_sub_window * csw1,struct control_sub_window * csw2)186d3c4e94bSnicm control_sub_window_cmp(struct control_sub_window *csw1,
187d3c4e94bSnicm struct control_sub_window *csw2)
188d3c4e94bSnicm {
189d3c4e94bSnicm if (csw1->window < csw2->window)
190d3c4e94bSnicm return (-1);
191d3c4e94bSnicm if (csw1->window > csw2->window)
192d3c4e94bSnicm return (1);
193d3c4e94bSnicm if (csw1->idx < csw2->idx)
194d3c4e94bSnicm return (-1);
195d3c4e94bSnicm if (csw1->idx > csw2->idx)
196d3c4e94bSnicm return (1);
197d3c4e94bSnicm return (0);
198d3c4e94bSnicm }
199d3c4e94bSnicm RB_GENERATE_STATIC(control_sub_windows, control_sub_window, entry,
200d3c4e94bSnicm control_sub_window_cmp);
201d3c4e94bSnicm
202d3c4e94bSnicm /* Free a subscription. */
203d3c4e94bSnicm static void
control_free_sub(struct control_state * cs,struct control_sub * csub)204d3c4e94bSnicm control_free_sub(struct control_state *cs, struct control_sub *csub)
205d3c4e94bSnicm {
206d3c4e94bSnicm struct control_sub_pane *csp, *csp1;
207d3c4e94bSnicm struct control_sub_window *csw, *csw1;
208d3c4e94bSnicm
209d3c4e94bSnicm RB_FOREACH_SAFE(csp, control_sub_panes, &csub->panes, csp1) {
210d3c4e94bSnicm RB_REMOVE(control_sub_panes, &csub->panes, csp);
211d3c4e94bSnicm free(csp);
212d3c4e94bSnicm }
213d3c4e94bSnicm RB_FOREACH_SAFE(csw, control_sub_windows, &csub->windows, csw1) {
214d3c4e94bSnicm RB_REMOVE(control_sub_windows, &csub->windows, csw);
215d3c4e94bSnicm free(csw);
216d3c4e94bSnicm }
217d3c4e94bSnicm free(csub->last);
218d3c4e94bSnicm
219d3c4e94bSnicm RB_REMOVE(control_subs, &cs->subs, csub);
220d3c4e94bSnicm free(csub->name);
221d3c4e94bSnicm free(csub->format);
222d3c4e94bSnicm free(csub);
223d3c4e94bSnicm }
224d3c4e94bSnicm
225a34cf9c8Snicm /* Free a block. */
226a34cf9c8Snicm static void
control_free_block(struct control_state * cs,struct control_block * cb)227a34cf9c8Snicm control_free_block(struct control_state *cs, struct control_block *cb)
228a34cf9c8Snicm {
229a34cf9c8Snicm free(cb->line);
230a34cf9c8Snicm TAILQ_REMOVE(&cs->all_blocks, cb, all_entry);
231a34cf9c8Snicm free(cb);
232a34cf9c8Snicm }
233b271fc7fSnicm
234b271fc7fSnicm /* Get pane offsets for this client. */
235a34cf9c8Snicm static struct control_pane *
control_get_pane(struct client * c,struct window_pane * wp)236a34cf9c8Snicm control_get_pane(struct client *c, struct window_pane *wp)
237b271fc7fSnicm {
23812bad209Snicm struct control_state *cs = c->control_state;
239a34cf9c8Snicm struct control_pane cp = { .pane = wp->id };
240b271fc7fSnicm
241a34cf9c8Snicm return (RB_FIND(control_panes, &cs->panes, &cp));
242b271fc7fSnicm }
243b271fc7fSnicm
244b271fc7fSnicm /* Add pane offsets for this client. */
245a34cf9c8Snicm static struct control_pane *
control_add_pane(struct client * c,struct window_pane * wp)246a34cf9c8Snicm control_add_pane(struct client *c, struct window_pane *wp)
247b271fc7fSnicm {
24812bad209Snicm struct control_state *cs = c->control_state;
249a34cf9c8Snicm struct control_pane *cp;
250b271fc7fSnicm
251a34cf9c8Snicm cp = control_get_pane(c, wp);
252a34cf9c8Snicm if (cp != NULL)
253a34cf9c8Snicm return (cp);
254b271fc7fSnicm
255a34cf9c8Snicm cp = xcalloc(1, sizeof *cp);
256a34cf9c8Snicm cp->pane = wp->id;
257a34cf9c8Snicm RB_INSERT(control_panes, &cs->panes, cp);
258a34cf9c8Snicm
259a34cf9c8Snicm memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
260a34cf9c8Snicm memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
261a34cf9c8Snicm TAILQ_INIT(&cp->blocks);
262a34cf9c8Snicm
263a34cf9c8Snicm return (cp);
264b271fc7fSnicm }
265b271fc7fSnicm
2663e796c5aSnicm /* Discard output for a pane. */
2673e796c5aSnicm static void
control_discard_pane(struct client * c,struct control_pane * cp)2683e796c5aSnicm control_discard_pane(struct client *c, struct control_pane *cp)
2693e796c5aSnicm {
2703e796c5aSnicm struct control_state *cs = c->control_state;
2713e796c5aSnicm struct control_block *cb, *cb1;
2723e796c5aSnicm
2733e796c5aSnicm TAILQ_FOREACH_SAFE(cb, &cp->blocks, entry, cb1) {
2743e796c5aSnicm TAILQ_REMOVE(&cp->blocks, cb, entry);
2753e796c5aSnicm control_free_block(cs, cb);
2763e796c5aSnicm }
2773e796c5aSnicm }
2783e796c5aSnicm
279cf8132c5Snicm /* Get actual pane for this client. */
280cf8132c5Snicm static struct window_pane *
control_window_pane(struct client * c,u_int pane)281cf8132c5Snicm control_window_pane(struct client *c, u_int pane)
282cf8132c5Snicm {
283cf8132c5Snicm struct window_pane *wp;
284cf8132c5Snicm
285cf8132c5Snicm if (c->session == NULL)
286cf8132c5Snicm return (NULL);
287cf8132c5Snicm if ((wp = window_pane_find_by_id(pane)) == NULL)
288cf8132c5Snicm return (NULL);
289cf8132c5Snicm if (winlink_find_by_window(&c->session->windows, wp->window) == NULL)
290cf8132c5Snicm return (NULL);
291cf8132c5Snicm return (wp);
292cf8132c5Snicm }
293cf8132c5Snicm
294a34cf9c8Snicm /* Reset control offsets. */
295b271fc7fSnicm void
control_reset_offsets(struct client * c)296a34cf9c8Snicm control_reset_offsets(struct client *c)
297b271fc7fSnicm {
29812bad209Snicm struct control_state *cs = c->control_state;
299a34cf9c8Snicm struct control_pane *cp, *cp1;
300b271fc7fSnicm
301a34cf9c8Snicm RB_FOREACH_SAFE(cp, control_panes, &cs->panes, cp1) {
302a34cf9c8Snicm RB_REMOVE(control_panes, &cs->panes, cp);
303a34cf9c8Snicm free(cp);
304b271fc7fSnicm }
305a34cf9c8Snicm
306a34cf9c8Snicm TAILQ_INIT(&cs->pending_list);
307a34cf9c8Snicm cs->pending_count = 0;
308b271fc7fSnicm }
309b271fc7fSnicm
310b271fc7fSnicm /* Get offsets for client. */
311b271fc7fSnicm struct window_pane_offset *
control_pane_offset(struct client * c,struct window_pane * wp,int * off)312b271fc7fSnicm control_pane_offset(struct client *c, struct window_pane *wp, int *off)
313b271fc7fSnicm {
314a34cf9c8Snicm struct control_state *cs = c->control_state;
315a34cf9c8Snicm struct control_pane *cp;
316b271fc7fSnicm
317b271fc7fSnicm if (c->flags & CLIENT_CONTROL_NOOUTPUT) {
318b271fc7fSnicm *off = 0;
319b271fc7fSnicm return (NULL);
320b271fc7fSnicm }
321b271fc7fSnicm
322a34cf9c8Snicm cp = control_get_pane(c, wp);
3233e796c5aSnicm if (cp == NULL || (cp->flags & CONTROL_PANE_PAUSED)) {
324b271fc7fSnicm *off = 0;
325b271fc7fSnicm return (NULL);
326b271fc7fSnicm }
327a34cf9c8Snicm if (cp->flags & CONTROL_PANE_OFF) {
328b271fc7fSnicm *off = 1;
329b271fc7fSnicm return (NULL);
330b271fc7fSnicm }
331a34cf9c8Snicm *off = (EVBUFFER_LENGTH(cs->write_event->output) >= CONTROL_BUFFER_LOW);
332a34cf9c8Snicm return (&cp->offset);
333b271fc7fSnicm }
334b271fc7fSnicm
335b271fc7fSnicm /* Set pane as on. */
336b271fc7fSnicm void
control_set_pane_on(struct client * c,struct window_pane * wp)337b271fc7fSnicm control_set_pane_on(struct client *c, struct window_pane *wp)
338b271fc7fSnicm {
339a34cf9c8Snicm struct control_pane *cp;
340b271fc7fSnicm
341a34cf9c8Snicm cp = control_get_pane(c, wp);
3423e796c5aSnicm if (cp != NULL && (cp->flags & CONTROL_PANE_OFF)) {
343a34cf9c8Snicm cp->flags &= ~CONTROL_PANE_OFF;
344a34cf9c8Snicm memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
345a34cf9c8Snicm memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
346b271fc7fSnicm }
347b271fc7fSnicm }
348b271fc7fSnicm
349b271fc7fSnicm /* Set pane as off. */
350b271fc7fSnicm void
control_set_pane_off(struct client * c,struct window_pane * wp)351b271fc7fSnicm control_set_pane_off(struct client *c, struct window_pane *wp)
352b271fc7fSnicm {
353a34cf9c8Snicm struct control_pane *cp;
354b271fc7fSnicm
355a34cf9c8Snicm cp = control_add_pane(c, wp);
356a34cf9c8Snicm cp->flags |= CONTROL_PANE_OFF;
357a34cf9c8Snicm }
358a34cf9c8Snicm
3593e796c5aSnicm /* Continue a paused pane. */
3603e796c5aSnicm void
control_continue_pane(struct client * c,struct window_pane * wp)3613e796c5aSnicm control_continue_pane(struct client *c, struct window_pane *wp)
3623e796c5aSnicm {
3633e796c5aSnicm struct control_pane *cp;
3643e796c5aSnicm
3653e796c5aSnicm cp = control_get_pane(c, wp);
3663e796c5aSnicm if (cp != NULL && (cp->flags & CONTROL_PANE_PAUSED)) {
3673e796c5aSnicm cp->flags &= ~CONTROL_PANE_PAUSED;
3683e796c5aSnicm memcpy(&cp->offset, &wp->offset, sizeof cp->offset);
3693e796c5aSnicm memcpy(&cp->queued, &wp->offset, sizeof cp->queued);
3703e796c5aSnicm control_write(c, "%%continue %%%u", wp->id);
3713e796c5aSnicm }
3723e796c5aSnicm }
3733e796c5aSnicm
3741ec116cfSnicm /* Pause a pane. */
3751ec116cfSnicm void
control_pause_pane(struct client * c,struct window_pane * wp)3761ec116cfSnicm control_pause_pane(struct client *c, struct window_pane *wp)
3771ec116cfSnicm {
3781ec116cfSnicm struct control_pane *cp;
3791ec116cfSnicm
3801ec116cfSnicm cp = control_add_pane(c, wp);
3811ec116cfSnicm if (~cp->flags & CONTROL_PANE_PAUSED) {
3821ec116cfSnicm cp->flags |= CONTROL_PANE_PAUSED;
3831ec116cfSnicm control_discard_pane(c, cp);
3841ec116cfSnicm control_write(c, "%%pause %%%u", wp->id);
3851ec116cfSnicm }
3861ec116cfSnicm }
3871ec116cfSnicm
388a34cf9c8Snicm /* Write a line. */
389ceb4a27fSnicm static void printflike(2, 0)
control_vwrite(struct client * c,const char * fmt,va_list ap)390a34cf9c8Snicm control_vwrite(struct client *c, const char *fmt, va_list ap)
391a34cf9c8Snicm {
392a34cf9c8Snicm struct control_state *cs = c->control_state;
393a34cf9c8Snicm char *s;
394a34cf9c8Snicm
395a34cf9c8Snicm xvasprintf(&s, fmt, ap);
396a34cf9c8Snicm log_debug("%s: %s: writing line: %s", __func__, c->name, s);
397a34cf9c8Snicm
398a34cf9c8Snicm bufferevent_write(cs->write_event, s, strlen(s));
399a34cf9c8Snicm bufferevent_write(cs->write_event, "\n", 1);
400a34cf9c8Snicm
401a34cf9c8Snicm bufferevent_enable(cs->write_event, EV_WRITE);
402a34cf9c8Snicm free(s);
403b271fc7fSnicm }
404b271fc7fSnicm
40590896992Snicm /* Write a line. */
40674d4b937Snicm void
control_write(struct client * c,const char * fmt,...)40790896992Snicm control_write(struct client *c, const char *fmt, ...)
40890896992Snicm {
40954e3eca9Snicm struct control_state *cs = c->control_state;
410a34cf9c8Snicm struct control_block *cb;
41190896992Snicm va_list ap;
41290896992Snicm
41390896992Snicm va_start(ap, fmt);
41454e3eca9Snicm
415a34cf9c8Snicm if (TAILQ_EMPTY(&cs->all_blocks)) {
416a34cf9c8Snicm control_vwrite(c, fmt, ap);
417a34cf9c8Snicm va_end(ap);
418a34cf9c8Snicm return;
419a34cf9c8Snicm }
420a34cf9c8Snicm
421a34cf9c8Snicm cb = xcalloc(1, sizeof *cb);
422a34cf9c8Snicm xvasprintf(&cb->line, fmt, ap);
423a34cf9c8Snicm TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
424cf8132c5Snicm cb->t = get_timer();
425a34cf9c8Snicm
426a34cf9c8Snicm log_debug("%s: %s: storing line: %s", __func__, c->name, cb->line);
427a34cf9c8Snicm bufferevent_enable(cs->write_event, EV_WRITE);
428a34cf9c8Snicm
429a34cf9c8Snicm va_end(ap);
430c98b4ad2Snicm }
431c98b4ad2Snicm
432e09158d2Snicm /* Check age for this pane. */
433e09158d2Snicm static int
control_check_age(struct client * c,struct window_pane * wp,struct control_pane * cp)434e09158d2Snicm control_check_age(struct client *c, struct window_pane *wp,
435e09158d2Snicm struct control_pane *cp)
436e09158d2Snicm {
437e09158d2Snicm struct control_block *cb;
438e09158d2Snicm uint64_t t, age;
439e09158d2Snicm
440e09158d2Snicm cb = TAILQ_FIRST(&cp->blocks);
441e09158d2Snicm if (cb == NULL)
442e09158d2Snicm return (0);
443e09158d2Snicm t = get_timer();
444e09158d2Snicm if (cb->t >= t)
445e09158d2Snicm return (0);
446e09158d2Snicm
447e09158d2Snicm age = t - cb->t;
448e09158d2Snicm log_debug("%s: %s: %%%u is %llu behind", __func__, c->name, wp->id,
449e09158d2Snicm (unsigned long long)age);
450e09158d2Snicm
451e09158d2Snicm if (c->flags & CLIENT_CONTROL_PAUSEAFTER) {
452e09158d2Snicm if (age < c->pause_age)
453e09158d2Snicm return (0);
454e09158d2Snicm cp->flags |= CONTROL_PANE_PAUSED;
455e09158d2Snicm control_discard_pane(c, cp);
456e09158d2Snicm control_write(c, "%%pause %%%u", wp->id);
457e09158d2Snicm } else {
458e09158d2Snicm if (age < CONTROL_MAXIMUM_AGE)
459e09158d2Snicm return (0);
460e09158d2Snicm c->exit_message = xstrdup("too far behind");
461e09158d2Snicm c->flags |= CLIENT_EXIT;
462e09158d2Snicm control_discard(c);
463e09158d2Snicm }
464e09158d2Snicm return (1);
465e09158d2Snicm }
466e09158d2Snicm
4672920028dSnicm /* Write output from a pane. */
4682920028dSnicm void
control_write_output(struct client * c,struct window_pane * wp)4692920028dSnicm control_write_output(struct client *c, struct window_pane *wp)
4702920028dSnicm {
47154e3eca9Snicm struct control_state *cs = c->control_state;
472a34cf9c8Snicm struct control_pane *cp;
473a34cf9c8Snicm struct control_block *cb;
474a34cf9c8Snicm size_t new_size;
4752920028dSnicm
4762920028dSnicm if (winlink_find_by_window(&c->session->windows, wp->window) == NULL)
4772920028dSnicm return;
4782920028dSnicm
479a34cf9c8Snicm if (c->flags & CONTROL_IGNORE_FLAGS) {
480a34cf9c8Snicm cp = control_get_pane(c, wp);
481a34cf9c8Snicm if (cp != NULL)
482a34cf9c8Snicm goto ignore;
4832920028dSnicm return;
4842920028dSnicm }
485a34cf9c8Snicm cp = control_add_pane(c, wp);
4863e796c5aSnicm if (cp->flags & (CONTROL_PANE_OFF|CONTROL_PANE_PAUSED))
487a34cf9c8Snicm goto ignore;
488e09158d2Snicm if (control_check_age(c, wp, cp))
4893e796c5aSnicm return;
490a34cf9c8Snicm
491a34cf9c8Snicm window_pane_get_new_data(wp, &cp->queued, &new_size);
4922920028dSnicm if (new_size == 0)
4932920028dSnicm return;
494a34cf9c8Snicm window_pane_update_used_data(wp, &cp->queued, new_size);
4952920028dSnicm
496a34cf9c8Snicm cb = xcalloc(1, sizeof *cb);
497a34cf9c8Snicm cb->size = new_size;
498a34cf9c8Snicm TAILQ_INSERT_TAIL(&cs->all_blocks, cb, all_entry);
499cf8132c5Snicm cb->t = get_timer();
5002920028dSnicm
501a34cf9c8Snicm TAILQ_INSERT_TAIL(&cp->blocks, cb, entry);
502a34cf9c8Snicm log_debug("%s: %s: new output block of %zu for %%%u", __func__, c->name,
503a34cf9c8Snicm cb->size, wp->id);
504a34cf9c8Snicm
505a34cf9c8Snicm if (!cp->pending_flag) {
506a34cf9c8Snicm log_debug("%s: %s: %%%u now pending", __func__, c->name,
507a34cf9c8Snicm wp->id);
508a34cf9c8Snicm TAILQ_INSERT_TAIL(&cs->pending_list, cp, pending_entry);
509a34cf9c8Snicm cp->pending_flag = 1;
510a34cf9c8Snicm cs->pending_count++;
5112920028dSnicm }
512a34cf9c8Snicm bufferevent_enable(cs->write_event, EV_WRITE);
513a34cf9c8Snicm return;
5142920028dSnicm
515a34cf9c8Snicm ignore:
516a34cf9c8Snicm log_debug("%s: %s: ignoring pane %%%u", __func__, c->name, wp->id);
517a34cf9c8Snicm window_pane_update_used_data(wp, &cp->offset, SIZE_MAX);
518a34cf9c8Snicm window_pane_update_used_data(wp, &cp->queued, SIZE_MAX);
5192920028dSnicm }
5202920028dSnicm
52154e3eca9Snicm /* Control client error callback. */
522765b9a58Snicm static enum cmd_retval
control_error(struct cmdq_item * item,void * data)52368e0a7f2Snicm control_error(struct cmdq_item *item, void *data)
524765b9a58Snicm {
525040343aeSnicm struct client *c = cmdq_get_client(item);
526765b9a58Snicm char *error = data;
527765b9a58Snicm
52868e0a7f2Snicm cmdq_guard(item, "begin", 1);
529765b9a58Snicm control_write(c, "parse error: %s", error);
53068e0a7f2Snicm cmdq_guard(item, "error", 1);
531765b9a58Snicm
532765b9a58Snicm free(error);
533765b9a58Snicm return (CMD_RETURN_NORMAL);
534765b9a58Snicm }
535765b9a58Snicm
53654e3eca9Snicm /* Control client error callback. */
537f4bc7c7aSnicm static void
control_error_callback(__unused struct bufferevent * bufev,__unused short what,void * data)53854e3eca9Snicm control_error_callback(__unused struct bufferevent *bufev,
53954e3eca9Snicm __unused short what, void *data)
54090896992Snicm {
54154e3eca9Snicm struct client *c = data;
54254e3eca9Snicm
54354e3eca9Snicm c->flags |= CLIENT_EXIT;
54454e3eca9Snicm }
54554e3eca9Snicm
54654e3eca9Snicm /* Control client input callback. Read lines and fire commands. */
54754e3eca9Snicm static void
control_read_callback(__unused struct bufferevent * bufev,void * data)54854e3eca9Snicm control_read_callback(__unused struct bufferevent *bufev, void *data)
54954e3eca9Snicm {
55054e3eca9Snicm struct client *c = data;
55154e3eca9Snicm struct control_state *cs = c->control_state;
55254e3eca9Snicm struct evbuffer *buffer = cs->read_event->input;
5531c43462cSnicm char *line, *error;
554c1e0bdabSnicm struct cmdq_state *state;
5551c43462cSnicm enum cmd_parse_status status;
55690896992Snicm
55790896992Snicm for (;;) {
558f4bc7c7aSnicm line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_LF);
55990896992Snicm if (line == NULL)
56090896992Snicm break;
561a34cf9c8Snicm log_debug("%s: %s: %s", __func__, c->name, line);
562a34cf9c8Snicm if (*line == '\0') { /* empty line detach */
563becc3e90Snicm free(line);
56490896992Snicm c->flags |= CLIENT_EXIT;
56590896992Snicm break;
56690896992Snicm }
56790896992Snicm
568c1e0bdabSnicm state = cmdq_new_state(NULL, NULL, CMDQ_STATE_CONTROL);
5691c43462cSnicm status = cmd_parse_and_append(line, NULL, c, state, &error);
5701c43462cSnicm if (status == CMD_PARSE_ERROR)
5711c43462cSnicm cmdq_append(c, cmdq_get_callback(control_error, error));
572c1e0bdabSnicm cmdq_free_state(state);
57390896992Snicm
5747d053cf9Snicm free(line);
57590896992Snicm }
57690896992Snicm }
577f4bc7c7aSnicm
578a34cf9c8Snicm /* Does this control client have outstanding data to write? */
579a34cf9c8Snicm int
control_all_done(struct client * c)580a34cf9c8Snicm control_all_done(struct client *c)
581a34cf9c8Snicm {
582a34cf9c8Snicm struct control_state *cs = c->control_state;
583a34cf9c8Snicm
584a34cf9c8Snicm if (!TAILQ_EMPTY(&cs->all_blocks))
585a34cf9c8Snicm return (0);
586a34cf9c8Snicm return (EVBUFFER_LENGTH(cs->write_event->output) == 0);
587a34cf9c8Snicm }
588a34cf9c8Snicm
589a34cf9c8Snicm /* Flush all blocks until output. */
590a34cf9c8Snicm static void
control_flush_all_blocks(struct client * c)591a34cf9c8Snicm control_flush_all_blocks(struct client *c)
592a34cf9c8Snicm {
593a34cf9c8Snicm struct control_state *cs = c->control_state;
594a34cf9c8Snicm struct control_block *cb, *cb1;
595a34cf9c8Snicm
596a34cf9c8Snicm TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1) {
597a34cf9c8Snicm if (cb->size != 0)
598a34cf9c8Snicm break;
599a34cf9c8Snicm log_debug("%s: %s: flushing line: %s", __func__, c->name,
600a34cf9c8Snicm cb->line);
601a34cf9c8Snicm
602a34cf9c8Snicm bufferevent_write(cs->write_event, cb->line, strlen(cb->line));
603a34cf9c8Snicm bufferevent_write(cs->write_event, "\n", 1);
604a34cf9c8Snicm control_free_block(cs, cb);
605a34cf9c8Snicm }
606a34cf9c8Snicm }
607a34cf9c8Snicm
608a34cf9c8Snicm /* Append data to buffer. */
609a34cf9c8Snicm 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)61026066ee4Snicm control_append_data(struct client *c, struct control_pane *cp, uint64_t age,
61126066ee4Snicm struct evbuffer *message, struct window_pane *wp, size_t size)
612a34cf9c8Snicm {
613a34cf9c8Snicm u_char *new_data;
614a34cf9c8Snicm size_t new_size;
615a34cf9c8Snicm u_int i;
616a34cf9c8Snicm
617a34cf9c8Snicm if (message == NULL) {
618a34cf9c8Snicm message = evbuffer_new();
619a34cf9c8Snicm if (message == NULL)
620a34cf9c8Snicm fatalx("out of memory");
62126066ee4Snicm if (c->flags & CLIENT_CONTROL_PAUSEAFTER) {
62226066ee4Snicm evbuffer_add_printf(message,
62326066ee4Snicm "%%extended-output %%%u %llu : ", wp->id,
62426066ee4Snicm (unsigned long long)age);
62526066ee4Snicm } else
626a34cf9c8Snicm evbuffer_add_printf(message, "%%output %%%u ", wp->id);
627a34cf9c8Snicm }
628a34cf9c8Snicm
629a34cf9c8Snicm new_data = window_pane_get_new_data(wp, &cp->offset, &new_size);
630a34cf9c8Snicm if (new_size < size)
631a34cf9c8Snicm fatalx("not enough data: %zu < %zu", new_size, size);
632a34cf9c8Snicm for (i = 0; i < size; i++) {
633a34cf9c8Snicm if (new_data[i] < ' ' || new_data[i] == '\\')
634a34cf9c8Snicm evbuffer_add_printf(message, "\\%03o", new_data[i]);
635a34cf9c8Snicm else
636a34cf9c8Snicm evbuffer_add_printf(message, "%c", new_data[i]);
637a34cf9c8Snicm }
638a34cf9c8Snicm window_pane_update_used_data(wp, &cp->offset, size);
639a34cf9c8Snicm return (message);
640a34cf9c8Snicm }
641a34cf9c8Snicm
642a34cf9c8Snicm /* Write buffer. */
643a34cf9c8Snicm static void
control_write_data(struct client * c,struct evbuffer * message)644a34cf9c8Snicm control_write_data(struct client *c, struct evbuffer *message)
645a34cf9c8Snicm {
646a34cf9c8Snicm struct control_state *cs = c->control_state;
647a34cf9c8Snicm
648a34cf9c8Snicm log_debug("%s: %s: %.*s", __func__, c->name,
649a34cf9c8Snicm (int)EVBUFFER_LENGTH(message), EVBUFFER_DATA(message));
650a34cf9c8Snicm
651a34cf9c8Snicm evbuffer_add(message, "\n", 1);
652a34cf9c8Snicm bufferevent_write_buffer(cs->write_event, message);
653a34cf9c8Snicm evbuffer_free(message);
654a34cf9c8Snicm }
655a34cf9c8Snicm
656a34cf9c8Snicm /* Write output to client. */
657a34cf9c8Snicm static int
control_write_pending(struct client * c,struct control_pane * cp,size_t limit)658a34cf9c8Snicm control_write_pending(struct client *c, struct control_pane *cp, size_t limit)
659a34cf9c8Snicm {
660a34cf9c8Snicm struct control_state *cs = c->control_state;
661a34cf9c8Snicm struct window_pane *wp = NULL;
662a34cf9c8Snicm struct evbuffer *message = NULL;
663a34cf9c8Snicm size_t used = 0, size;
664a34cf9c8Snicm struct control_block *cb, *cb1;
66526066ee4Snicm uint64_t age, t = get_timer();
666a34cf9c8Snicm
667cf8132c5Snicm wp = control_window_pane(c, cp->pane);
6680626b97fSnicm if (wp == NULL || wp->fd == -1) {
669ede976afSnicm TAILQ_FOREACH_SAFE(cb, &cp->blocks, entry, cb1) {
670ede976afSnicm TAILQ_REMOVE(&cp->blocks, cb, entry);
671a34cf9c8Snicm control_free_block(cs, cb);
672ede976afSnicm }
673a34cf9c8Snicm control_flush_all_blocks(c);
674a34cf9c8Snicm return (0);
675a34cf9c8Snicm }
676a34cf9c8Snicm
677a34cf9c8Snicm while (used != limit && !TAILQ_EMPTY(&cp->blocks)) {
67801c78be9Snicm if (control_check_age(c, wp, cp)) {
67901c78be9Snicm if (message != NULL)
68001c78be9Snicm evbuffer_free(message);
68101c78be9Snicm message = NULL;
68201c78be9Snicm break;
68301c78be9Snicm }
68401c78be9Snicm
685a34cf9c8Snicm cb = TAILQ_FIRST(&cp->blocks);
68626066ee4Snicm if (cb->t < t)
68726066ee4Snicm age = t - cb->t;
68826066ee4Snicm else
68926066ee4Snicm age = 0;
69026066ee4Snicm log_debug("%s: %s: output block %zu (age %llu) for %%%u "
691cf415eadSnicm "(used %zu/%zu)", __func__, c->name, cb->size,
692cf415eadSnicm (unsigned long long)age, cp->pane, used, limit);
693a34cf9c8Snicm
694a34cf9c8Snicm size = cb->size;
695a34cf9c8Snicm if (size > limit - used)
696a34cf9c8Snicm size = limit - used;
697a34cf9c8Snicm used += size;
698a34cf9c8Snicm
69926066ee4Snicm message = control_append_data(c, cp, age, message, wp, size);
700a34cf9c8Snicm
701a34cf9c8Snicm cb->size -= size;
702a34cf9c8Snicm if (cb->size == 0) {
703a34cf9c8Snicm TAILQ_REMOVE(&cp->blocks, cb, entry);
704a34cf9c8Snicm control_free_block(cs, cb);
705a34cf9c8Snicm
706a34cf9c8Snicm cb = TAILQ_FIRST(&cs->all_blocks);
707a34cf9c8Snicm if (cb != NULL && cb->size == 0) {
708a34cf9c8Snicm if (wp != NULL && message != NULL) {
709a34cf9c8Snicm control_write_data(c, message);
710a34cf9c8Snicm message = NULL;
711a34cf9c8Snicm }
712a34cf9c8Snicm control_flush_all_blocks(c);
713a34cf9c8Snicm }
714a34cf9c8Snicm }
715a34cf9c8Snicm }
716a34cf9c8Snicm if (message != NULL)
717a34cf9c8Snicm control_write_data(c, message);
718a34cf9c8Snicm return (!TAILQ_EMPTY(&cp->blocks));
719a34cf9c8Snicm }
720a34cf9c8Snicm
721a34cf9c8Snicm /* Control client write callback. */
722a34cf9c8Snicm static void
control_write_callback(__unused struct bufferevent * bufev,void * data)723a34cf9c8Snicm control_write_callback(__unused struct bufferevent *bufev, void *data)
724a34cf9c8Snicm {
725a34cf9c8Snicm struct client *c = data;
726a34cf9c8Snicm struct control_state *cs = c->control_state;
727a34cf9c8Snicm struct control_pane *cp, *cp1;
728a34cf9c8Snicm struct evbuffer *evb = cs->write_event->output;
729a34cf9c8Snicm size_t space, limit;
730a34cf9c8Snicm
731a34cf9c8Snicm control_flush_all_blocks(c);
732a34cf9c8Snicm
733a34cf9c8Snicm while (EVBUFFER_LENGTH(evb) < CONTROL_BUFFER_HIGH) {
734a34cf9c8Snicm if (cs->pending_count == 0)
735a34cf9c8Snicm break;
736a34cf9c8Snicm space = CONTROL_BUFFER_HIGH - EVBUFFER_LENGTH(evb);
737a34cf9c8Snicm log_debug("%s: %s: %zu bytes available, %u panes", __func__,
738a34cf9c8Snicm c->name, space, cs->pending_count);
739a34cf9c8Snicm
740a34cf9c8Snicm limit = (space / cs->pending_count / 3); /* 3 bytes for \xxx */
741a34cf9c8Snicm if (limit < CONTROL_WRITE_MINIMUM)
742a34cf9c8Snicm limit = CONTROL_WRITE_MINIMUM;
743a34cf9c8Snicm
744a34cf9c8Snicm TAILQ_FOREACH_SAFE(cp, &cs->pending_list, pending_entry, cp1) {
745a34cf9c8Snicm if (EVBUFFER_LENGTH(evb) >= CONTROL_BUFFER_HIGH)
746a34cf9c8Snicm break;
747a34cf9c8Snicm if (control_write_pending(c, cp, limit))
748a34cf9c8Snicm continue;
749a34cf9c8Snicm TAILQ_REMOVE(&cs->pending_list, cp, pending_entry);
750a34cf9c8Snicm cp->pending_flag = 0;
751a34cf9c8Snicm cs->pending_count--;
752a34cf9c8Snicm }
753a34cf9c8Snicm }
754a34cf9c8Snicm if (EVBUFFER_LENGTH(evb) == 0)
755a34cf9c8Snicm bufferevent_disable(cs->write_event, EV_WRITE);
756a34cf9c8Snicm }
757a34cf9c8Snicm
758b271fc7fSnicm /* Initialize for control mode. */
759f4bc7c7aSnicm void
control_start(struct client * c)760f4bc7c7aSnicm control_start(struct client *c)
761f4bc7c7aSnicm {
76212bad209Snicm struct control_state *cs;
76312bad209Snicm
76454e3eca9Snicm if (c->flags & CLIENT_CONTROLCONTROL) {
76554e3eca9Snicm close(c->out_fd);
76654e3eca9Snicm c->out_fd = -1;
76754e3eca9Snicm } else
76854e3eca9Snicm setblocking(c->out_fd, 0);
76954e3eca9Snicm setblocking(c->fd, 0);
77054e3eca9Snicm
77112bad209Snicm cs = c->control_state = xcalloc(1, sizeof *cs);
772a34cf9c8Snicm RB_INIT(&cs->panes);
773a34cf9c8Snicm TAILQ_INIT(&cs->pending_list);
774a34cf9c8Snicm TAILQ_INIT(&cs->all_blocks);
775d3c4e94bSnicm RB_INIT(&cs->subs);
77612bad209Snicm
777a34cf9c8Snicm cs->read_event = bufferevent_new(c->fd, control_read_callback,
778a34cf9c8Snicm control_write_callback, control_error_callback, c);
779*7c8808e4Snicm if (cs->read_event == NULL)
780*7c8808e4Snicm fatalx("out of memory");
781f4bc7c7aSnicm
782f4bc7c7aSnicm if (c->flags & CLIENT_CONTROLCONTROL)
78354e3eca9Snicm cs->write_event = cs->read_event;
78454e3eca9Snicm else {
785a34cf9c8Snicm cs->write_event = bufferevent_new(c->out_fd, NULL,
786a34cf9c8Snicm control_write_callback, control_error_callback, c);
787*7c8808e4Snicm if (cs->write_event == NULL)
788*7c8808e4Snicm fatalx("out of memory");
78954e3eca9Snicm }
790a34cf9c8Snicm bufferevent_setwatermark(cs->write_event, EV_WRITE, CONTROL_BUFFER_LOW,
791a34cf9c8Snicm 0);
79254e3eca9Snicm
793a34cf9c8Snicm if (c->flags & CLIENT_CONTROLCONTROL) {
794a34cf9c8Snicm bufferevent_write(cs->write_event, "\033P1000p", 7);
795a34cf9c8Snicm bufferevent_enable(cs->write_event, EV_WRITE);
796a34cf9c8Snicm }
797a34cf9c8Snicm }
798a34cf9c8Snicm
7996fea7318Snicm /* Control client ready. */
8006fea7318Snicm void
control_ready(struct client * c)8016fea7318Snicm control_ready(struct client *c)
8026fea7318Snicm {
8036fea7318Snicm bufferevent_enable(c->control_state->read_event, EV_READ);
8046fea7318Snicm }
8056fea7318Snicm
8063e796c5aSnicm /* Discard all output for a client. */
807a34cf9c8Snicm void
control_discard(struct client * c)8083e796c5aSnicm control_discard(struct client *c)
809a34cf9c8Snicm {
810a34cf9c8Snicm struct control_state *cs = c->control_state;
811a34cf9c8Snicm struct control_pane *cp;
812a34cf9c8Snicm
8133e796c5aSnicm RB_FOREACH(cp, control_panes, &cs->panes)
8143e796c5aSnicm control_discard_pane(c, cp);
815b3e4eeb8Snicm bufferevent_disable(cs->read_event, EV_READ);
816f4bc7c7aSnicm }
81712bad209Snicm
81812bad209Snicm /* Stop control mode. */
81912bad209Snicm void
control_stop(struct client * c)82012bad209Snicm control_stop(struct client *c)
82112bad209Snicm {
82212bad209Snicm struct control_state *cs = c->control_state;
823a34cf9c8Snicm struct control_block *cb, *cb1;
824d3c4e94bSnicm struct control_sub *csub, *csub1;
82512bad209Snicm
82654e3eca9Snicm if (~c->flags & CLIENT_CONTROLCONTROL)
82754e3eca9Snicm bufferevent_free(cs->write_event);
82854e3eca9Snicm bufferevent_free(cs->read_event);
82954e3eca9Snicm
830d3c4e94bSnicm RB_FOREACH_SAFE(csub, control_subs, &cs->subs, csub1)
831d3c4e94bSnicm control_free_sub(cs, csub);
832d3c4e94bSnicm if (evtimer_initialized(&cs->subs_timer))
833d3c4e94bSnicm evtimer_del(&cs->subs_timer);
834d3c4e94bSnicm
835a34cf9c8Snicm TAILQ_FOREACH_SAFE(cb, &cs->all_blocks, all_entry, cb1)
836a34cf9c8Snicm control_free_block(cs, cb);
837a34cf9c8Snicm control_reset_offsets(c);
838a34cf9c8Snicm
83912bad209Snicm free(cs);
84012bad209Snicm }
841d3c4e94bSnicm
842d3c4e94bSnicm /* Check session subscription. */
843d3c4e94bSnicm static void
control_check_subs_session(struct client * c,struct control_sub * csub)844d3c4e94bSnicm control_check_subs_session(struct client *c, struct control_sub *csub)
845d3c4e94bSnicm {
846d3c4e94bSnicm struct session *s = c->session;
847d3c4e94bSnicm struct format_tree *ft;
848d3c4e94bSnicm char *value;
849d3c4e94bSnicm
850d3c4e94bSnicm ft = format_create_defaults(NULL, c, s, NULL, NULL);
851d3c4e94bSnicm value = format_expand(ft, csub->format);
852d3c4e94bSnicm format_free(ft);
853d3c4e94bSnicm
854d3c4e94bSnicm if (csub->last != NULL && strcmp(value, csub->last) == 0) {
855d3c4e94bSnicm free(value);
856d3c4e94bSnicm return;
857d3c4e94bSnicm }
858d3c4e94bSnicm control_write(c,
859d3c4e94bSnicm "%%subscription-changed %s $%u - - - : %s",
860d3c4e94bSnicm csub->name, s->id, value);
861d3c4e94bSnicm free(csub->last);
862d3c4e94bSnicm csub->last = value;
863d3c4e94bSnicm }
864d3c4e94bSnicm
865d3c4e94bSnicm /* Check pane subscription. */
866d3c4e94bSnicm static void
control_check_subs_pane(struct client * c,struct control_sub * csub)867d3c4e94bSnicm control_check_subs_pane(struct client *c, struct control_sub *csub)
868d3c4e94bSnicm {
869d3c4e94bSnicm struct session *s = c->session;
870d3c4e94bSnicm struct window_pane *wp;
871d3c4e94bSnicm struct window *w;
872d3c4e94bSnicm struct winlink *wl;
873d3c4e94bSnicm struct format_tree *ft;
874d3c4e94bSnicm char *value;
875d3c4e94bSnicm struct control_sub_pane *csp, find;
876d3c4e94bSnicm
877d3c4e94bSnicm wp = window_pane_find_by_id(csub->id);
8780626b97fSnicm if (wp == NULL || wp->fd == -1)
879d3c4e94bSnicm return;
880d3c4e94bSnicm w = wp->window;
881d3c4e94bSnicm
882d3c4e94bSnicm TAILQ_FOREACH(wl, &w->winlinks, wentry) {
883d3c4e94bSnicm if (wl->session != s)
884d3c4e94bSnicm continue;
885d3c4e94bSnicm
886d3c4e94bSnicm ft = format_create_defaults(NULL, c, s, wl, wp);
887d3c4e94bSnicm value = format_expand(ft, csub->format);
888d3c4e94bSnicm format_free(ft);
889d3c4e94bSnicm
890d3c4e94bSnicm find.pane = wp->id;
891d3c4e94bSnicm find.idx = wl->idx;
892d3c4e94bSnicm
893d3c4e94bSnicm csp = RB_FIND(control_sub_panes, &csub->panes, &find);
894d3c4e94bSnicm if (csp == NULL) {
895d3c4e94bSnicm csp = xcalloc(1, sizeof *csp);
896d3c4e94bSnicm csp->pane = wp->id;
897d3c4e94bSnicm csp->idx = wl->idx;
898d3c4e94bSnicm RB_INSERT(control_sub_panes, &csub->panes, csp);
899d3c4e94bSnicm }
900d3c4e94bSnicm
901d3c4e94bSnicm if (csp->last != NULL && strcmp(value, csp->last) == 0) {
902d3c4e94bSnicm free(value);
903d3c4e94bSnicm continue;
904d3c4e94bSnicm }
905d3c4e94bSnicm control_write(c,
906d3c4e94bSnicm "%%subscription-changed %s $%u @%u %u %%%u : %s",
907d3c4e94bSnicm csub->name, s->id, w->id, wl->idx, wp->id, value);
908d3c4e94bSnicm free(csp->last);
909d3c4e94bSnicm csp->last = value;
910d3c4e94bSnicm }
911d3c4e94bSnicm }
912d3c4e94bSnicm
913d3c4e94bSnicm /* Check all panes subscription. */
914d3c4e94bSnicm static void
control_check_subs_all_panes(struct client * c,struct control_sub * csub)915d3c4e94bSnicm control_check_subs_all_panes(struct client *c, struct control_sub *csub)
916d3c4e94bSnicm {
917d3c4e94bSnicm struct session *s = c->session;
918d3c4e94bSnicm struct window_pane *wp;
919d3c4e94bSnicm struct window *w;
920d3c4e94bSnicm struct winlink *wl;
921d3c4e94bSnicm struct format_tree *ft;
922d3c4e94bSnicm char *value;
923d3c4e94bSnicm struct control_sub_pane *csp, find;
924d3c4e94bSnicm
925d3c4e94bSnicm RB_FOREACH(wl, winlinks, &s->windows) {
926d3c4e94bSnicm w = wl->window;
927d3c4e94bSnicm TAILQ_FOREACH(wp, &w->panes, entry) {
928d3c4e94bSnicm ft = format_create_defaults(NULL, c, s, wl, wp);
929d3c4e94bSnicm value = format_expand(ft, csub->format);
930d3c4e94bSnicm format_free(ft);
931d3c4e94bSnicm
932d3c4e94bSnicm find.pane = wp->id;
933d3c4e94bSnicm find.idx = wl->idx;
934d3c4e94bSnicm
935d3c4e94bSnicm csp = RB_FIND(control_sub_panes, &csub->panes, &find);
936d3c4e94bSnicm if (csp == NULL) {
937d3c4e94bSnicm csp = xcalloc(1, sizeof *csp);
938d3c4e94bSnicm csp->pane = wp->id;
939d3c4e94bSnicm csp->idx = wl->idx;
940d3c4e94bSnicm RB_INSERT(control_sub_panes, &csub->panes, csp);
941d3c4e94bSnicm }
942d3c4e94bSnicm
943d3c4e94bSnicm if (csp->last != NULL &&
944d3c4e94bSnicm strcmp(value, csp->last) == 0) {
945d3c4e94bSnicm free(value);
946d3c4e94bSnicm continue;
947d3c4e94bSnicm }
948d3c4e94bSnicm control_write(c,
949d3c4e94bSnicm "%%subscription-changed %s $%u @%u %u %%%u : %s",
950d3c4e94bSnicm csub->name, s->id, w->id, wl->idx, wp->id, value);
951d3c4e94bSnicm free(csp->last);
952d3c4e94bSnicm csp->last = value;
953d3c4e94bSnicm }
954d3c4e94bSnicm }
955d3c4e94bSnicm }
956d3c4e94bSnicm
957d3c4e94bSnicm /* Check window subscription. */
958d3c4e94bSnicm static void
control_check_subs_window(struct client * c,struct control_sub * csub)959d3c4e94bSnicm control_check_subs_window(struct client *c, struct control_sub *csub)
960d3c4e94bSnicm {
961d3c4e94bSnicm struct session *s = c->session;
962d3c4e94bSnicm struct window *w;
963d3c4e94bSnicm struct winlink *wl;
964d3c4e94bSnicm struct format_tree *ft;
965d3c4e94bSnicm char *value;
966d3c4e94bSnicm struct control_sub_window *csw, find;
967d3c4e94bSnicm
968d3c4e94bSnicm w = window_find_by_id(csub->id);
969d3c4e94bSnicm if (w == NULL)
970d3c4e94bSnicm return;
971d3c4e94bSnicm
972d3c4e94bSnicm TAILQ_FOREACH(wl, &w->winlinks, wentry) {
973d3c4e94bSnicm if (wl->session != s)
974d3c4e94bSnicm continue;
975d3c4e94bSnicm
976d3c4e94bSnicm ft = format_create_defaults(NULL, c, s, wl, NULL);
977d3c4e94bSnicm value = format_expand(ft, csub->format);
978d3c4e94bSnicm format_free(ft);
979d3c4e94bSnicm
980d3c4e94bSnicm find.window = w->id;
981d3c4e94bSnicm find.idx = wl->idx;
982d3c4e94bSnicm
983d3c4e94bSnicm csw = RB_FIND(control_sub_windows, &csub->windows, &find);
984d3c4e94bSnicm if (csw == NULL) {
985d3c4e94bSnicm csw = xcalloc(1, sizeof *csw);
986d3c4e94bSnicm csw->window = w->id;
987d3c4e94bSnicm csw->idx = wl->idx;
988d3c4e94bSnicm RB_INSERT(control_sub_windows, &csub->windows, csw);
989d3c4e94bSnicm }
990d3c4e94bSnicm
991d3c4e94bSnicm if (csw->last != NULL && strcmp(value, csw->last) == 0) {
992d3c4e94bSnicm free(value);
993d3c4e94bSnicm continue;
994d3c4e94bSnicm }
995d3c4e94bSnicm control_write(c,
996d3c4e94bSnicm "%%subscription-changed %s $%u @%u %u - : %s",
997d3c4e94bSnicm csub->name, s->id, w->id, wl->idx, value);
998d3c4e94bSnicm free(csw->last);
999d3c4e94bSnicm csw->last = value;
1000d3c4e94bSnicm }
1001d3c4e94bSnicm }
1002d3c4e94bSnicm
1003d3c4e94bSnicm /* Check all windows subscription. */
1004d3c4e94bSnicm static void
control_check_subs_all_windows(struct client * c,struct control_sub * csub)1005d3c4e94bSnicm control_check_subs_all_windows(struct client *c, struct control_sub *csub)
1006d3c4e94bSnicm {
1007d3c4e94bSnicm struct session *s = c->session;
1008d3c4e94bSnicm struct window *w;
1009d3c4e94bSnicm struct winlink *wl;
1010d3c4e94bSnicm struct format_tree *ft;
1011d3c4e94bSnicm char *value;
1012d3c4e94bSnicm struct control_sub_window *csw, find;
1013d3c4e94bSnicm
1014d3c4e94bSnicm RB_FOREACH(wl, winlinks, &s->windows) {
1015d3c4e94bSnicm w = wl->window;
1016d3c4e94bSnicm
1017d3c4e94bSnicm ft = format_create_defaults(NULL, c, s, wl, NULL);
1018d3c4e94bSnicm value = format_expand(ft, csub->format);
1019d3c4e94bSnicm format_free(ft);
1020d3c4e94bSnicm
1021d3c4e94bSnicm find.window = w->id;
1022d3c4e94bSnicm find.idx = wl->idx;
1023d3c4e94bSnicm
1024d3c4e94bSnicm csw = RB_FIND(control_sub_windows, &csub->windows, &find);
1025d3c4e94bSnicm if (csw == NULL) {
1026d3c4e94bSnicm csw = xcalloc(1, sizeof *csw);
1027d3c4e94bSnicm csw->window = w->id;
1028d3c4e94bSnicm csw->idx = wl->idx;
1029d3c4e94bSnicm RB_INSERT(control_sub_windows, &csub->windows, csw);
1030d3c4e94bSnicm }
1031d3c4e94bSnicm
1032d3c4e94bSnicm if (csw->last != NULL && strcmp(value, csw->last) == 0) {
1033d3c4e94bSnicm free(value);
1034d3c4e94bSnicm continue;
1035d3c4e94bSnicm }
1036d3c4e94bSnicm control_write(c,
1037d3c4e94bSnicm "%%subscription-changed %s $%u @%u %u - : %s",
1038d3c4e94bSnicm csub->name, s->id, w->id, wl->idx, value);
1039d3c4e94bSnicm free(csw->last);
1040d3c4e94bSnicm csw->last = value;
1041d3c4e94bSnicm }
1042d3c4e94bSnicm }
1043d3c4e94bSnicm
1044d3c4e94bSnicm /* Check subscriptions timer. */
1045d3c4e94bSnicm static void
control_check_subs_timer(__unused int fd,__unused short events,void * data)1046d3c4e94bSnicm control_check_subs_timer(__unused int fd, __unused short events, void *data)
1047d3c4e94bSnicm {
1048d3c4e94bSnicm struct client *c = data;
1049d3c4e94bSnicm struct control_state *cs = c->control_state;
1050d3c4e94bSnicm struct control_sub *csub, *csub1;
1051d3c4e94bSnicm struct timeval tv = { .tv_sec = 1 };
1052d3c4e94bSnicm
1053d3c4e94bSnicm log_debug("%s: timer fired", __func__);
1054d3c4e94bSnicm evtimer_add(&cs->subs_timer, &tv);
1055d3c4e94bSnicm
1056d3c4e94bSnicm RB_FOREACH_SAFE(csub, control_subs, &cs->subs, csub1) {
1057d3c4e94bSnicm switch (csub->type) {
1058d3c4e94bSnicm case CONTROL_SUB_SESSION:
1059d3c4e94bSnicm control_check_subs_session(c, csub);
1060d3c4e94bSnicm break;
1061d3c4e94bSnicm case CONTROL_SUB_PANE:
1062d3c4e94bSnicm control_check_subs_pane(c, csub);
1063d3c4e94bSnicm break;
1064d3c4e94bSnicm case CONTROL_SUB_ALL_PANES:
1065d3c4e94bSnicm control_check_subs_all_panes(c, csub);
1066d3c4e94bSnicm break;
1067d3c4e94bSnicm case CONTROL_SUB_WINDOW:
1068d3c4e94bSnicm control_check_subs_window(c, csub);
1069d3c4e94bSnicm break;
1070d3c4e94bSnicm case CONTROL_SUB_ALL_WINDOWS:
1071d3c4e94bSnicm control_check_subs_all_windows(c, csub);
1072d3c4e94bSnicm break;
1073d3c4e94bSnicm }
1074d3c4e94bSnicm }
1075d3c4e94bSnicm }
1076d3c4e94bSnicm
1077d3c4e94bSnicm /* Add a subscription. */
1078d3c4e94bSnicm void
control_add_sub(struct client * c,const char * name,enum control_sub_type type,int id,const char * format)1079d3c4e94bSnicm control_add_sub(struct client *c, const char *name, enum control_sub_type type,
1080d3c4e94bSnicm int id, const char *format)
1081d3c4e94bSnicm {
1082d3c4e94bSnicm struct control_state *cs = c->control_state;
1083d3c4e94bSnicm struct control_sub *csub, find;
1084d3c4e94bSnicm struct timeval tv = { .tv_sec = 1 };
1085d3c4e94bSnicm
1086d3c4e94bSnicm find.name = (char *)name;
1087d3c4e94bSnicm if ((csub = RB_FIND(control_subs, &cs->subs, &find)) != NULL)
1088d3c4e94bSnicm control_free_sub(cs, csub);
1089d3c4e94bSnicm
1090d3c4e94bSnicm csub = xcalloc(1, sizeof *csub);
1091d3c4e94bSnicm csub->name = xstrdup(name);
1092d3c4e94bSnicm csub->type = type;
1093d3c4e94bSnicm csub->id = id;
1094d3c4e94bSnicm csub->format = xstrdup(format);
1095d3c4e94bSnicm RB_INSERT(control_subs, &cs->subs, csub);
1096d3c4e94bSnicm
1097d3c4e94bSnicm RB_INIT(&csub->panes);
1098d3c4e94bSnicm RB_INIT(&csub->windows);
1099d3c4e94bSnicm
1100d3c4e94bSnicm if (!evtimer_initialized(&cs->subs_timer))
1101d3c4e94bSnicm evtimer_set(&cs->subs_timer, control_check_subs_timer, c);
1102d3c4e94bSnicm if (!evtimer_pending(&cs->subs_timer, NULL))
1103d3c4e94bSnicm evtimer_add(&cs->subs_timer, &tv);
1104d3c4e94bSnicm }
1105d3c4e94bSnicm
1106d3c4e94bSnicm /* Remove a subscription. */
1107d3c4e94bSnicm void
control_remove_sub(struct client * c,const char * name)1108d3c4e94bSnicm control_remove_sub(struct client *c, const char *name)
1109d3c4e94bSnicm {
1110d3c4e94bSnicm struct control_state *cs = c->control_state;
1111d3c4e94bSnicm struct control_sub *csub, find;
1112d3c4e94bSnicm
1113d3c4e94bSnicm find.name = (char *)name;
1114d3c4e94bSnicm if ((csub = RB_FIND(control_subs, &cs->subs, &find)) != NULL)
1115d3c4e94bSnicm control_free_sub(cs, csub);
1116d3c4e94bSnicm if (RB_EMPTY(&cs->subs))
1117d3c4e94bSnicm evtimer_del(&cs->subs_timer);
1118d3c4e94bSnicm }
1119