1 #include <sys/utsname.h>
2 #include "tmate.h"
3 #include "tmate-protocol.h"
4 #include "window-copy.h"
5 
6 #define pack(what, ...) _pack(&tmate_session.encoder, what, ##__VA_ARGS__)
7 
tmate_write_header(void)8 void tmate_write_header(void)
9 {
10 	pack(array, 3);
11 	pack(int, TMATE_OUT_HEADER);
12 	pack(int, TMATE_PROTOCOL_VERSION);
13 	pack(string, VERSION);
14 }
15 
tmate_write_uname(void)16 void tmate_write_uname(void)
17 {
18 	struct utsname name;
19 	if (uname(&name) < 0) {
20 		tmate_debug("uname() failed");
21 		return;
22 	}
23 
24 	pack(array, 6);
25 	pack(int, TMATE_OUT_UNAME);
26 	pack(string, name.sysname);
27 	pack(string, name.nodename);
28 	pack(string, name.release);
29 	pack(string, name.version);
30 	pack(string, name.machine);
31 }
32 
tmate_write_ready(void)33 void tmate_write_ready(void)
34 {
35 	pack(array, 1);
36 	pack(int, TMATE_OUT_READY);
37 }
38 
tmate_sync_layout(void)39 void tmate_sync_layout(void)
40 {
41 	struct session *s;
42 	struct winlink *wl;
43 	struct window *w;
44 	struct window_pane *wp;
45 	int num_panes = 0;
46 	int num_windows = 0;
47 	int active_pane_id;
48 	int active_window_idx = -1;
49 
50 	/*
51 	 * TODO this can get a little heavy.
52 	 * We are shipping the full layout whenever a window name changes,
53 	 * that is, at every shell command.
54 	 * Might be better to do something incremental.
55 	 */
56 
57 	/*
58 	 * We only allow one session, it makes our lives easier.
59 	 * Especially when the HTML5 client will come along.
60 	 * We make no distinction between a winlink and its window except
61 	 * that we send the winlink idx to draw the status bar properly.
62 	 */
63 
64 	s = RB_MIN(sessions, &sessions);
65 	if (!s)
66 		return;
67 
68 	num_windows = 0;
69 	RB_FOREACH(wl, winlinks, &s->windows) {
70 		if (wl->window)
71 			num_windows++;
72 	}
73 
74 	if (!num_windows)
75 		return;
76 
77 	pack(array, 5);
78 	pack(int, TMATE_OUT_SYNC_LAYOUT);
79 
80 	pack(int, s->sx);
81 	pack(int, s->sy);
82 
83 	pack(array, num_windows);
84 	RB_FOREACH(wl, winlinks, &s->windows) {
85 		w = wl->window;
86 		if (!w)
87 			continue;
88 
89 		w->tmate_last_sync_active_pane = NULL;
90 		active_pane_id = -1;
91 
92 		if (active_window_idx == -1)
93 			active_window_idx = wl->idx;
94 
95 		pack(array, 4);
96 		pack(int, wl->idx);
97 		pack(string, w->name);
98 
99 		num_panes = 0;
100 		TAILQ_FOREACH(wp, &w->panes, entry)
101 			num_panes++;
102 
103 		pack(array, num_panes);
104 		TAILQ_FOREACH(wp, &w->panes, entry) {
105 			pack(array, 5);
106 			pack(int, wp->id);
107 			pack(int, wp->sx);
108 			pack(int, wp->sy);
109 			pack(int, wp->xoff);
110 			pack(int, wp->yoff);
111 
112 			if (wp == w->active) {
113 				w->tmate_last_sync_active_pane = wp;
114 				active_pane_id = wp->id;
115 			}
116 
117 		}
118 		pack(int, active_pane_id);
119 	}
120 
121 	if (s->curw)
122 		active_window_idx = s->curw->idx;
123 
124 	pack(int, active_window_idx);
125 }
126 
127 /* TODO add a buffer for pty_data ? */
128 
129 #define TMATE_MAX_PTY_SIZE (16*1024)
130 
tmate_pty_data(struct window_pane * wp,const char * buf,size_t len)131 void tmate_pty_data(struct window_pane *wp, const char *buf, size_t len)
132 {
133 	size_t to_write;
134 
135 	while (len > 0) {
136 		to_write = len < TMATE_MAX_PTY_SIZE ? len : TMATE_MAX_PTY_SIZE;
137 
138 		pack(array, 3);
139 		pack(int, TMATE_OUT_PTY_DATA);
140 		pack(int, wp->id);
141 		pack(str, to_write);
142 		pack(str_body, buf, to_write);
143 
144 		buf += to_write;
145 		len -= to_write;
146 	}
147 }
148 
149 extern const struct cmd_entry cmd_bind_key_entry;
150 extern const struct cmd_entry cmd_unbind_key_entry;
151 extern const struct cmd_entry cmd_set_option_entry;
152 extern const struct cmd_entry cmd_set_window_option_entry;
153 
154 static const struct cmd_entry *replicated_cmds[] = {
155 	&cmd_bind_key_entry,
156 	&cmd_unbind_key_entry,
157 	&cmd_set_option_entry,
158 	&cmd_set_window_option_entry,
159 	NULL
160 };
161 
tmate_should_replicate_cmd(const struct cmd_entry * cmd)162 int tmate_should_replicate_cmd(const struct cmd_entry *cmd)
163 {
164 	const struct cmd_entry **ptr;
165 
166 	for (ptr = replicated_cmds; *ptr; ptr++)
167 		if (*ptr == cmd)
168 			return 1;
169 	return 0;
170 }
171 
172 #define sc (&session->saved_tmux_cmds)
173 #define SAVED_TMUX_CMD_INITIAL_SIZE 256
174 static void __tmate_exec_cmd_args(int argc, const char **argv);
175 
append_saved_cmd(struct tmate_session * session,int argc,const char ** argv)176 static void append_saved_cmd(struct tmate_session *session,
177 			     int argc, const char **argv)
178 {
179 	if (!sc->cmds) {
180 		sc->capacity = SAVED_TMUX_CMD_INITIAL_SIZE;
181 		sc->cmds = xmalloc(sizeof(*sc->cmds) * sc->capacity);
182 		sc->tail = 0;
183 	}
184 
185 	if (sc->tail == sc->capacity) {
186 		sc->capacity *= 2;
187 		sc->cmds = xrealloc(sc->cmds, sizeof(*sc->cmds) * sc->capacity);
188 	}
189 
190 	sc->cmds[sc->tail].argc = argc;
191 	sc->cmds[sc->tail].argv = cmd_copy_argv(argc, (char **)argv);
192 
193 	sc->tail++;
194 }
195 
replay_saved_cmd(struct tmate_session * session)196 static void replay_saved_cmd(struct tmate_session *session)
197 {
198 	unsigned int i;
199 	for (i = 0; i < sc->tail; i++)
200 		__tmate_exec_cmd_args(sc->cmds[i].argc, (const char **)sc->cmds[i].argv);
201 }
202 #undef sc
203 
204 struct args_entry {
205 	u_char			 flag;
206 	char			*value;
207 	RB_ENTRY(args_entry)	 entry;
208 };
209 
extract_cmd(struct cmd * cmd,int * _argc,char *** _argv)210 static void extract_cmd(struct cmd *cmd, int *_argc, char ***_argv)
211 {
212 	struct args_entry *entry;
213 	struct args* args = cmd->args;
214 	int argc = 0;
215 	char **argv;
216 	int next = 0, i;
217 
218 	argc++; /* cmd name */
219 	RB_FOREACH(entry, args_tree, &args->tree) {
220 		argc++;
221 		if (entry->value != NULL)
222 			argc++;
223 	}
224 	argc += args->argc;
225 	argv = xmalloc(sizeof(char *) * argc);
226 
227 	argv[next++] = xstrdup(cmd->entry->name);
228 
229 	RB_FOREACH(entry, args_tree, &args->tree) {
230 		xasprintf(&argv[next++], "-%c", entry->flag);
231 		if (entry->value != NULL)
232 			argv[next++] = xstrdup(entry->value);
233 	}
234 
235 	for (i = 0; i < args->argc; i++)
236 		argv[next++] = xstrdup(args->argv[i]);
237 
238 	*_argc = argc;
239 	*_argv = argv;
240 }
241 
__tmate_exec_cmd_args(int argc,const char ** argv)242 static void __tmate_exec_cmd_args(int argc, const char **argv)
243 {
244 	int i;
245 
246 	pack(array, argc + 1);
247 	pack(int, TMATE_OUT_EXEC_CMD);
248 
249 	for (i = 0; i < argc; i++)
250 		pack(string, argv[i]);
251 }
252 
tmate_exec_cmd_args(int argc,const char ** argv)253 void tmate_exec_cmd_args(int argc, const char **argv)
254 {
255 	__tmate_exec_cmd_args(argc, argv);
256 	append_saved_cmd(&tmate_session, argc, argv);
257 }
258 
tmate_set_val(const char * name,const char * value)259 void tmate_set_val(const char *name, const char *value)
260 {
261 	char *buf;
262 	xasprintf(&buf, "%s=%s", name, value);
263 	tmate_exec_cmd_args(3, (const char *[]){"set-option", "tmate-set", buf});
264 	free(buf);
265 }
266 
tmate_exec_cmd(struct cmd * cmd)267 void tmate_exec_cmd(struct cmd *cmd)
268 {
269 	int argc;
270 	char **argv;
271 
272 	extract_cmd(cmd, &argc, &argv);
273 	tmate_exec_cmd_args(argc, (const char **)argv);
274 	cmd_free_argv(argc, argv);
275 }
276 
tmate_failed_cmd(int client_id,const char * cause)277 void tmate_failed_cmd(int client_id, const char *cause)
278 {
279 	pack(array, 3);
280 	pack(int, TMATE_OUT_FAILED_CMD);
281 	pack(int, client_id);
282 	pack(string, cause);
283 }
284 
tmate_status(const char * left,const char * right)285 void tmate_status(const char *left, const char *right)
286 {
287 	static char *old_left, *old_right;
288 
289 	if (old_left  && !strcmp(old_left,  left) &&
290 	    old_right && !strcmp(old_right, right))
291 		return;
292 
293 	pack(array, 3);
294 	pack(int, TMATE_OUT_STATUS);
295 	pack(string, left);
296 	pack(string, right);
297 
298 	free(old_left);
299 	free(old_right);
300 	old_left = xstrdup(left);
301 	old_right = xstrdup(right);
302 }
303 
tmate_sync_copy_mode(struct window_pane * wp)304 void tmate_sync_copy_mode(struct window_pane *wp)
305 {
306 	struct window_copy_mode_data *data = wp->modedata;
307 
308 	pack(array, 3);
309 	pack(int, TMATE_OUT_SYNC_COPY_MODE);
310 
311 	pack(int, wp->id);
312 
313 	if (wp->mode != &window_copy_mode ||
314 	    data->inputtype == WINDOW_COPY_PASSWORD) {
315 		pack(array, 0);
316 		return;
317 	}
318 	pack(array, 6);
319 	pack(int, data->backing == &wp->base);
320 
321 	pack(int, data->oy);
322 	pack(int, data->cx);
323 	pack(int, data->cy);
324 
325 	if (data->screen.sel.flag) {
326 		pack(array, 3);
327 		pack(int, data->selx);
328 		pack(int, -data->sely + screen_hsize(data->backing)
329 				      + screen_size_y(data->backing) - 1);
330 		pack(int, data->rectflag);
331 	} else
332 		pack(array, 0);
333 
334 	if (data->inputprompt) {
335 		pack(array, 3);
336 		pack(int, data->inputtype);
337 		pack(string, data->inputprompt);
338 		pack(string, data->inputstr);
339 	} else
340 		pack(array, 0);
341 }
342 
tmate_write_copy_mode(struct window_pane * wp,const char * str)343 void tmate_write_copy_mode(struct window_pane *wp, const char *str)
344 {
345 	pack(array, 3);
346 	pack(int, TMATE_OUT_WRITE_COPY_MODE);
347 	pack(int, wp->id);
348 	pack(string, str);
349 }
350 
tmate_write_fin(void)351 void tmate_write_fin(void)
352 {
353 	pack(array, 1);
354 	pack(int, TMATE_OUT_FIN);
355 }
356 
do_snapshot_grid(struct grid * grid,unsigned int max_history_lines)357 static void do_snapshot_grid(struct grid *grid, unsigned int max_history_lines)
358 {
359 	struct grid_line *line;
360 	struct grid_cell gc;
361 	unsigned int line_i, i;
362 	unsigned int max_lines;
363 	size_t str_len;
364 
365 	max_lines = max_history_lines + grid->sy;
366 
367 #define grid_num_lines(grid) (grid->hsize + grid->sy)
368 
369 	if (grid_num_lines(grid) > max_lines)
370 		line_i = grid_num_lines(grid) - max_lines;
371 	else
372 		line_i = 0;
373 
374 	pack(array, grid_num_lines(grid) - line_i);
375 	for (; line_i < grid_num_lines(grid); line_i++) {
376 		line = &grid->linedata[line_i];
377 
378 		pack(array, 2);
379 		str_len = 0;
380 		for (i = 0; i < line->cellsize; i++) {
381 			grid_get_cell(grid, i, line_i, &gc);
382 			str_len += gc.data.size;
383 		}
384 
385 		pack(str, str_len);
386 		for (i = 0; i < line->cellsize; i++) {
387 			grid_get_cell(grid, i, line_i, &gc);
388 			pack(str_body, gc.data.data, gc.data.size);
389 		}
390 
391 		pack(array, line->cellsize);
392 		for (i = 0; i < line->cellsize; i++) {
393 			grid_get_cell(grid, i, line_i, &gc);
394 			pack(unsigned_int, ((gc.flags << 24) |
395 					    (gc.attr  << 16) |
396 					    (gc.bg    << 8)  |
397 					     gc.fg        ));
398 		}
399 	}
400 
401 }
402 
do_snapshot_pane(struct window_pane * wp,unsigned int max_history_lines)403 static void do_snapshot_pane(struct window_pane *wp, unsigned int max_history_lines)
404 {
405 	struct screen *screen = &wp->base;
406 
407 	pack(array, 4);
408 	pack(int, wp->id);
409 
410 	pack(unsigned_int, screen->mode);
411 
412 	pack(array, 3);
413 	pack(int, screen->cx);
414 	pack(int, screen->cy);
415 	do_snapshot_grid(screen->grid, max_history_lines);
416 
417 	if (wp->saved_grid) {
418 		pack(array, 3);
419 		pack(int, wp->saved_cx);
420 		pack(int, wp->saved_cy);
421 		do_snapshot_grid(wp->saved_grid, max_history_lines);
422 	} else {
423 		pack(nil);
424 	}
425 }
426 
tmate_send_session_snapshot(unsigned int max_history_lines)427 static void tmate_send_session_snapshot(unsigned int max_history_lines)
428 {
429 	struct session *s;
430 	struct winlink *wl;
431 	struct window *w;
432 	struct window_pane *pane;
433 	int num_panes;
434 
435 	pack(array, 2);
436 	pack(int, TMATE_OUT_SNAPSHOT);
437 
438 	s = RB_MIN(sessions, &sessions);
439 	if (!s)
440 		tmate_fatal("no session?");
441 
442 	num_panes = 0;
443 	RB_FOREACH(wl, winlinks, &s->windows) {
444 		w = wl->window;
445 		if (!w)
446 			continue;
447 
448 		TAILQ_FOREACH(pane, &w->panes, entry)
449 			num_panes++;
450 	}
451 
452 	pack(array, num_panes);
453 	RB_FOREACH(wl, winlinks, &s->windows) {
454 		w = wl->window;
455 		if (!w)
456 			continue;
457 
458 		TAILQ_FOREACH(pane, &w->panes, entry)
459 			do_snapshot_pane(pane, max_history_lines);
460 	}
461 }
462 
tmate_send_reconnection_data(struct tmate_session * session)463 static void tmate_send_reconnection_data(struct tmate_session *session)
464 {
465 	if (!session->reconnection_data)
466 		return;
467 
468 	pack(array, 2);
469 	pack(int, TMATE_OUT_RECONNECT);
470 	pack(string, session->reconnection_data);
471 }
472 
473 #define RECONNECTION_MAX_HISTORY_LINE 300
474 
tmate_send_reconnection_state(struct tmate_session * session)475 void tmate_send_reconnection_state(struct tmate_session *session)
476 {
477 	/* Start with a fresh encoder */
478 	tmate_encoder_destroy(&session->encoder);
479 	tmate_encoder_init(&session->encoder, NULL, session);
480 
481 	tmate_write_header();
482 	tmate_send_reconnection_data(session);
483 	replay_saved_cmd(session);
484 	/* TODO send all option variables */
485 	tmate_write_uname();
486 	tmate_write_ready();
487 
488 	tmate_sync_layout();
489 	tmate_send_session_snapshot(RECONNECTION_MAX_HISTORY_LINE);
490 }
491