xref: /openbsd-src/usr.bin/tmux/control.c (revision 7c8808e4d13e4890d5dede2d9829b4f61e113e95)
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