control.c revision 1.33
1/* $OpenBSD: control.c,v 1.33 2020/05/24 09:40:17 nicm Exp $ */
2
3/*
4 * Copyright (c) 2012 Nicholas Marriott <nicholas.marriott@gmail.com>
5 * Copyright (c) 2012 George Nachman <tmux@georgester.com>
6 *
7 * Permission to use, copy, modify, and distribute this software for any
8 * purpose with or without fee is hereby granted, provided that the above
9 * copyright notice and this permission notice appear in all copies.
10 *
11 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15 * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
16 * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
17 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 */
19
20#include <sys/types.h>
21
22#include <event.h>
23#include <stdlib.h>
24#include <string.h>
25#include <time.h>
26
27#include "tmux.h"
28
29/* Control offsets. */
30struct control_offset {
31	u_int				pane;
32
33	struct window_pane_offset	offset;
34	int				flags;
35#define CONTROL_OFFSET_OFF 0x1
36
37	RB_ENTRY(control_offset)		entry;
38};
39RB_HEAD(control_offsets, control_offset);
40
41/* Control state. */
42struct control_state {
43	struct control_offsets	offsets;
44};
45
46/* Compare client offsets. */
47static int
48control_offset_cmp(struct control_offset *co1, struct control_offset *co2)
49{
50	if (co1->pane < co2->pane)
51		return (-1);
52	if (co1->pane > co2->pane)
53		return (1);
54	return (0);
55}
56RB_GENERATE_STATIC(control_offsets, control_offset, entry, control_offset_cmp);
57
58/* Get pane offsets for this client. */
59static struct control_offset *
60control_get_offset(struct client *c, struct window_pane *wp)
61{
62	struct control_state	*cs = c->control_state;
63	struct control_offset	 co = { .pane = wp->id };
64
65	return (RB_FIND(control_offsets, &cs->offsets, &co));
66}
67
68/* Add pane offsets for this client. */
69static struct control_offset *
70control_add_offset(struct client *c, struct window_pane *wp)
71{
72	struct control_state	*cs = c->control_state;
73	struct control_offset	*co;
74
75	co = control_get_offset(c, wp);
76	if (co != NULL)
77		return (co);
78
79	co = xcalloc(1, sizeof *co);
80	co->pane = wp->id;
81	RB_INSERT(control_offsets, &cs->offsets, co);
82	memcpy(&co->offset, &wp->offset, sizeof co->offset);
83	return (co);
84}
85
86/* Free control offsets. */
87void
88control_free_offsets(struct client *c)
89{
90	struct control_state	*cs = c->control_state;
91	struct control_offset	*co, *co1;
92
93	RB_FOREACH_SAFE(co, control_offsets, &cs->offsets, co1) {
94		RB_REMOVE(control_offsets, &cs->offsets, co);
95		free(co);
96	}
97}
98
99/* Get offsets for client. */
100struct window_pane_offset *
101control_pane_offset(struct client *c, struct window_pane *wp, int *off)
102{
103	struct control_offset	*co;
104
105	if (c->flags & CLIENT_CONTROL_NOOUTPUT) {
106		*off = 0;
107		return (NULL);
108	}
109
110	co = control_get_offset(c, wp);
111	if (co == NULL) {
112		*off = 0;
113		return (NULL);
114	}
115	if (co->flags & CONTROL_OFFSET_OFF) {
116		*off = 1;
117		return (NULL);
118	}
119	return (&co->offset);
120}
121
122/* Set pane as on. */
123void
124control_set_pane_on(struct client *c, struct window_pane *wp)
125{
126	struct control_offset	*co;
127
128	co = control_get_offset(c, wp);
129	if (co != NULL) {
130		co->flags &= ~CONTROL_OFFSET_OFF;
131		memcpy(&co->offset, &wp->offset, sizeof co->offset);
132	}
133}
134
135/* Set pane as off. */
136void
137control_set_pane_off(struct client *c, struct window_pane *wp)
138{
139	struct control_offset	*co;
140
141	co = control_add_offset(c, wp);
142	co->flags |= CONTROL_OFFSET_OFF;
143}
144
145/* Write a line. */
146void
147control_write(struct client *c, const char *fmt, ...)
148{
149	va_list	ap;
150
151	va_start(ap, fmt);
152	file_vprint(c, fmt, ap);
153	file_print(c, "\n");
154	va_end(ap);
155}
156
157/* Write output from a pane. */
158void
159control_write_output(struct client *c, struct window_pane *wp)
160{
161	struct control_offset	*co;
162	struct evbuffer		*message;
163	u_char			*new_data;
164	size_t			 new_size, i;
165
166	if (c->flags & CLIENT_CONTROL_NOOUTPUT)
167		return;
168
169	/*
170	 * Only write input if the window pane is linked to a window belonging
171	 * to the client's session.
172	 */
173	if (winlink_find_by_window(&c->session->windows, wp->window) == NULL)
174		return;
175
176	co = control_add_offset(c, wp);
177	if (co->flags & CONTROL_OFFSET_OFF) {
178		window_pane_update_used_data(wp, &co->offset, SIZE_MAX, 1);
179		return;
180	}
181	new_data = window_pane_get_new_data(wp, &co->offset, &new_size);
182	if (new_size == 0)
183		return;
184
185	message = evbuffer_new();
186	if (message == NULL)
187		fatalx("out of memory");
188	evbuffer_add_printf(message, "%%output %%%u ", wp->id);
189
190	for (i = 0; i < new_size; i++) {
191		if (new_data[i] < ' ' || new_data[i] == '\\')
192			evbuffer_add_printf(message, "\\%03o", new_data[i]);
193		else
194			evbuffer_add_printf(message, "%c", new_data[i]);
195	}
196	evbuffer_add(message, "", 1);
197
198	control_write(c, "%s", EVBUFFER_DATA(message));
199	evbuffer_free(message);
200
201	window_pane_update_used_data(wp, &co->offset, new_size, 1);
202}
203
204/* Control error callback. */
205static enum cmd_retval
206control_error(struct cmdq_item *item, void *data)
207{
208	struct client	*c = cmdq_get_client(item);
209	char		*error = data;
210
211	cmdq_guard(item, "begin", 1);
212	control_write(c, "parse error: %s", error);
213	cmdq_guard(item, "error", 1);
214
215	free(error);
216	return (CMD_RETURN_NORMAL);
217}
218
219/* Control input callback. Read lines and fire commands. */
220static void
221control_callback(__unused struct client *c, __unused const char *path,
222    int read_error, int closed, struct evbuffer *buffer, __unused void *data)
223{
224	char			*line, *error;
225	struct cmdq_state	*state;
226	enum cmd_parse_status	 status;
227
228	if (closed || read_error != 0)
229		c->flags |= CLIENT_EXIT;
230
231	for (;;) {
232		line = evbuffer_readln(buffer, NULL, EVBUFFER_EOL_LF);
233		if (line == NULL)
234			break;
235		log_debug("%s: %s", __func__, line);
236		if (*line == '\0') { /* empty line exit */
237			free(line);
238			c->flags |= CLIENT_EXIT;
239			break;
240		}
241
242		state = cmdq_new_state(NULL, NULL, CMDQ_STATE_CONTROL);
243		status = cmd_parse_and_append(line, NULL, c, state, &error);
244		if (status == CMD_PARSE_ERROR)
245			cmdq_append(c, cmdq_get_callback(control_error, error));
246		cmdq_free_state(state);
247
248		free(line);
249	}
250}
251
252/* Initialize for control mode. */
253void
254control_start(struct client *c)
255{
256	struct control_state	*cs;
257
258	cs = c->control_state = xcalloc(1, sizeof *cs);
259	RB_INIT(&cs->offsets);
260
261	file_read(c, "-", control_callback, c);
262
263	if (c->flags & CLIENT_CONTROLCONTROL)
264		file_print(c, "\033P1000p");
265}
266
267/* Stop control mode. */
268void
269control_stop(struct client *c)
270{
271	struct control_state	*cs = c->control_state;
272
273	control_free_offsets(c);
274	free(cs);
275}
276