xref: /openbsd-src/usr.bin/tmux/server-fn.c (revision 0b7734b3d77bb9b21afec6f4621cae6c805dbd45)
1 /* $OpenBSD: server-fn.c,v 1.99 2016/06/16 10:55:47 nicm Exp $ */
2 
3 /*
4  * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER
15  * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
16  * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 
19 #include <sys/types.h>
20 #include <sys/queue.h>
21 #include <sys/uio.h>
22 
23 #include <imsg.h>
24 #include <stdlib.h>
25 #include <string.h>
26 #include <time.h>
27 #include <unistd.h>
28 
29 #include "tmux.h"
30 
31 struct session *server_next_session(struct session *);
32 void		server_callback_identify(int, short, void *);
33 
34 void
35 server_fill_environ(struct session *s, struct environ *env)
36 {
37 	char	*term;
38 	u_int	 idx;
39 	long	 pid;
40 
41 	if (s != NULL) {
42 		term = options_get_string(global_options, "default-terminal");
43 		environ_set(env, "TERM", "%s", term);
44 
45 		idx = s->id;
46 	} else
47 		idx = (u_int)-1;
48 	pid = getpid();
49 	environ_set(env, "TMUX", "%s,%ld,%u", socket_path, pid, idx);
50 }
51 
52 void
53 server_redraw_client(struct client *c)
54 {
55 	c->flags |= CLIENT_REDRAW;
56 }
57 
58 void
59 server_status_client(struct client *c)
60 {
61 	c->flags |= CLIENT_STATUS;
62 }
63 
64 void
65 server_redraw_session(struct session *s)
66 {
67 	struct client	*c;
68 
69 	TAILQ_FOREACH(c, &clients, entry) {
70 		if (c->session == s)
71 			server_redraw_client(c);
72 	}
73 }
74 
75 void
76 server_redraw_session_group(struct session *s)
77 {
78 	struct session_group	*sg;
79 
80 	if ((sg = session_group_find(s)) == NULL)
81 		server_redraw_session(s);
82 	else {
83 		TAILQ_FOREACH(s, &sg->sessions, gentry)
84 			server_redraw_session(s);
85 	}
86 }
87 
88 void
89 server_status_session(struct session *s)
90 {
91 	struct client	*c;
92 
93 	TAILQ_FOREACH(c, &clients, entry) {
94 		if (c->session == s)
95 			server_status_client(c);
96 	}
97 }
98 
99 void
100 server_status_session_group(struct session *s)
101 {
102 	struct session_group	*sg;
103 
104 	if ((sg = session_group_find(s)) == NULL)
105 		server_status_session(s);
106 	else {
107 		TAILQ_FOREACH(s, &sg->sessions, gentry)
108 			server_status_session(s);
109 	}
110 }
111 
112 void
113 server_redraw_window(struct window *w)
114 {
115 	struct client	*c;
116 
117 	TAILQ_FOREACH(c, &clients, entry) {
118 		if (c->session != NULL && c->session->curw->window == w)
119 			server_redraw_client(c);
120 	}
121 	w->flags |= WINDOW_REDRAW;
122 }
123 
124 void
125 server_redraw_window_borders(struct window *w)
126 {
127 	struct client	*c;
128 
129 	TAILQ_FOREACH(c, &clients, entry) {
130 		if (c->session != NULL && c->session->curw->window == w)
131 			c->flags |= CLIENT_BORDERS;
132 	}
133 }
134 
135 void
136 server_status_window(struct window *w)
137 {
138 	struct session	*s;
139 
140 	/*
141 	 * This is slightly different. We want to redraw the status line of any
142 	 * clients containing this window rather than anywhere it is the
143 	 * current window.
144 	 */
145 
146 	RB_FOREACH(s, sessions, &sessions) {
147 		if (session_has(s, w))
148 			server_status_session(s);
149 	}
150 }
151 
152 void
153 server_lock(void)
154 {
155 	struct client	*c;
156 
157 	TAILQ_FOREACH(c, &clients, entry) {
158 		if (c->session != NULL)
159 			server_lock_client(c);
160 	}
161 }
162 
163 void
164 server_lock_session(struct session *s)
165 {
166 	struct client	*c;
167 
168 	TAILQ_FOREACH(c, &clients, entry) {
169 		if (c->session == s)
170 			server_lock_client(c);
171 	}
172 }
173 
174 void
175 server_lock_client(struct client *c)
176 {
177 	const char	*cmd;
178 
179 	if (c->flags & CLIENT_CONTROL)
180 		return;
181 
182 	if (c->flags & CLIENT_SUSPENDED)
183 		return;
184 
185 	cmd = options_get_string(c->session->options, "lock-command");
186 	if (strlen(cmd) + 1 > MAX_IMSGSIZE - IMSG_HEADER_SIZE)
187 		return;
188 
189 	tty_stop_tty(&c->tty);
190 	tty_raw(&c->tty, tty_term_string(c->tty.term, TTYC_SMCUP));
191 	tty_raw(&c->tty, tty_term_string(c->tty.term, TTYC_CLEAR));
192 	tty_raw(&c->tty, tty_term_string(c->tty.term, TTYC_E3));
193 
194 	c->flags |= CLIENT_SUSPENDED;
195 	proc_send_s(c->peer, MSG_LOCK, cmd);
196 }
197 
198 void
199 server_kill_window(struct window *w)
200 {
201 	struct session		*s, *next_s, *target_s;
202 	struct session_group	*sg;
203 	struct winlink		*wl;
204 
205 	next_s = RB_MIN(sessions, &sessions);
206 	while (next_s != NULL) {
207 		s = next_s;
208 		next_s = RB_NEXT(sessions, &sessions, s);
209 
210 		if (!session_has(s, w))
211 			continue;
212 		server_unzoom_window(w);
213 		while ((wl = winlink_find_by_window(&s->windows, w)) != NULL) {
214 			if (session_detach(s, wl)) {
215 				server_destroy_session_group(s);
216 				break;
217 			} else
218 				server_redraw_session_group(s);
219 		}
220 
221 		if (options_get_number(s->options, "renumber-windows")) {
222 			if ((sg = session_group_find(s)) != NULL) {
223 				TAILQ_FOREACH(target_s, &sg->sessions, gentry)
224 					session_renumber_windows(target_s);
225 			} else
226 				session_renumber_windows(s);
227 		}
228 	}
229 	recalculate_sizes();
230 }
231 
232 int
233 server_link_window(struct session *src, struct winlink *srcwl,
234     struct session *dst, int dstidx, int killflag, int selectflag,
235     char **cause)
236 {
237 	struct winlink		*dstwl;
238 	struct session_group	*srcsg, *dstsg;
239 
240 	srcsg = session_group_find(src);
241 	dstsg = session_group_find(dst);
242 	if (src != dst && srcsg != NULL && dstsg != NULL && srcsg == dstsg) {
243 		xasprintf(cause, "sessions are grouped");
244 		return (-1);
245 	}
246 
247 	dstwl = NULL;
248 	if (dstidx != -1)
249 		dstwl = winlink_find_by_index(&dst->windows, dstidx);
250 	if (dstwl != NULL) {
251 		if (dstwl->window == srcwl->window) {
252 			xasprintf(cause, "same index: %d", dstidx);
253 			return (-1);
254 		}
255 		if (killflag) {
256 			/*
257 			 * Can't use session_detach as it will destroy session
258 			 * if this makes it empty.
259 			 */
260 			notify_window_unlinked(dst, dstwl->window);
261 			dstwl->flags &= ~WINLINK_ALERTFLAGS;
262 			winlink_stack_remove(&dst->lastw, dstwl);
263 			winlink_remove(&dst->windows, dstwl);
264 
265 			/* Force select/redraw if current. */
266 			if (dstwl == dst->curw) {
267 				selectflag = 1;
268 				dst->curw = NULL;
269 			}
270 		}
271 	}
272 
273 	if (dstidx == -1)
274 		dstidx = -1 - options_get_number(dst->options, "base-index");
275 	dstwl = session_attach(dst, srcwl->window, dstidx, cause);
276 	if (dstwl == NULL)
277 		return (-1);
278 
279 	if (selectflag)
280 		session_select(dst, dstwl->idx);
281 	server_redraw_session_group(dst);
282 
283 	return (0);
284 }
285 
286 void
287 server_unlink_window(struct session *s, struct winlink *wl)
288 {
289 	if (session_detach(s, wl))
290 		server_destroy_session_group(s);
291 	else
292 		server_redraw_session_group(s);
293 }
294 
295 void
296 server_destroy_pane(struct window_pane *wp, int hooks)
297 {
298 	struct window		*w = wp->window;
299 	int			 old_fd;
300 	struct screen_write_ctx	 ctx;
301 	struct grid_cell	 gc;
302 	struct cmd_find_state	 fs;
303 
304 	old_fd = wp->fd;
305 	if (wp->fd != -1) {
306 		bufferevent_free(wp->event);
307 		close(wp->fd);
308 		wp->fd = -1;
309 	}
310 
311 	if (options_get_number(w->options, "remain-on-exit")) {
312 		if (old_fd == -1)
313 			return;
314 		screen_write_start(&ctx, wp, &wp->base);
315 		screen_write_scrollregion(&ctx, 0, screen_size_y(ctx.s) - 1);
316 		screen_write_cursormove(&ctx, 0, screen_size_y(ctx.s) - 1);
317 		screen_write_linefeed(&ctx, 1);
318 		memcpy(&gc, &grid_default_cell, sizeof gc);
319 		gc.attr |= GRID_ATTR_BRIGHT;
320 		screen_write_puts(&ctx, &gc, "Pane is dead");
321 		screen_write_stop(&ctx);
322 		wp->flags |= PANE_REDRAW;
323 
324 		if (hooks && cmd_find_from_pane(&fs, wp) == 0)
325 			hooks_run(hooks_get(fs.s), NULL, &fs, "pane-died");
326 		return;
327 	}
328 
329 	server_unzoom_window(w);
330 	layout_close_pane(wp);
331 	window_remove_pane(w, wp);
332 
333 	if (hooks && cmd_find_from_window(&fs, w) == 0)
334 		hooks_run(hooks_get(fs.s), NULL, &fs, "pane-exited");
335 
336 	if (TAILQ_EMPTY(&w->panes))
337 		server_kill_window(w);
338 	else
339 		server_redraw_window(w);
340 }
341 
342 void
343 server_destroy_session_group(struct session *s)
344 {
345 	struct session_group	*sg;
346 	struct session		*s1;
347 
348 	if ((sg = session_group_find(s)) == NULL)
349 		server_destroy_session(s);
350 	else {
351 		TAILQ_FOREACH_SAFE(s, &sg->sessions, gentry, s1) {
352 			server_destroy_session(s);
353 			session_destroy(s);
354 		}
355 	}
356 }
357 
358 struct session *
359 server_next_session(struct session *s)
360 {
361 	struct session *s_loop, *s_out;
362 
363 	s_out = NULL;
364 	RB_FOREACH(s_loop, sessions, &sessions) {
365 		if (s_loop == s)
366 			continue;
367 		if (s_out == NULL ||
368 		    timercmp(&s_loop->activity_time, &s_out->activity_time, <))
369 			s_out = s_loop;
370 	}
371 	return (s_out);
372 }
373 
374 void
375 server_destroy_session(struct session *s)
376 {
377 	struct client	*c;
378 	struct session	*s_new;
379 
380 	if (!options_get_number(s->options, "detach-on-destroy"))
381 		s_new = server_next_session(s);
382 	else
383 		s_new = NULL;
384 
385 	TAILQ_FOREACH(c, &clients, entry) {
386 		if (c->session != s)
387 			continue;
388 		if (s_new == NULL) {
389 			c->session = NULL;
390 			c->flags |= CLIENT_EXIT;
391 		} else {
392 			c->last_session = NULL;
393 			c->session = s_new;
394 			server_client_set_key_table(c, NULL);
395 			status_timer_start(c);
396 			notify_attached_session_changed(c);
397 			session_update_activity(s_new, NULL);
398 			gettimeofday(&s_new->last_attached_time, NULL);
399 			server_redraw_client(c);
400 			alerts_check_session(s_new);
401 		}
402 	}
403 	recalculate_sizes();
404 }
405 
406 void
407 server_check_unattached(void)
408 {
409 	struct session	*s;
410 
411 	/*
412 	 * If any sessions are no longer attached and have destroy-unattached
413 	 * set, collect them.
414 	 */
415 	RB_FOREACH(s, sessions, &sessions) {
416 		if (!(s->flags & SESSION_UNATTACHED))
417 			continue;
418 		if (options_get_number (s->options, "destroy-unattached"))
419 			session_destroy(s);
420 	}
421 }
422 
423 void
424 server_set_identify(struct client *c)
425 {
426 	struct timeval	tv;
427 	int		delay;
428 
429 	delay = options_get_number(c->session->options, "display-panes-time");
430 	tv.tv_sec = delay / 1000;
431 	tv.tv_usec = (delay % 1000) * 1000L;
432 
433 	if (event_initialized(&c->identify_timer))
434 		evtimer_del(&c->identify_timer);
435 	evtimer_set(&c->identify_timer, server_callback_identify, c);
436 	evtimer_add(&c->identify_timer, &tv);
437 
438 	c->flags |= CLIENT_IDENTIFY;
439 	c->tty.flags |= (TTY_FREEZE|TTY_NOCURSOR);
440 	server_redraw_client(c);
441 }
442 
443 void
444 server_clear_identify(struct client *c, struct window_pane *wp)
445 {
446 	if (~c->flags & CLIENT_IDENTIFY)
447 		return;
448 	c->flags &= ~CLIENT_IDENTIFY;
449 
450 	if (c->identify_callback != NULL)
451 		c->identify_callback(c, wp);
452 
453 	c->tty.flags &= ~(TTY_FREEZE|TTY_NOCURSOR);
454 	server_redraw_client(c);
455 }
456 
457 void
458 server_callback_identify(__unused int fd, __unused short events, void *data)
459 {
460 	server_clear_identify(data, NULL);
461 }
462 
463 /* Set stdin callback. */
464 int
465 server_set_stdin_callback(struct client *c, void (*cb)(struct client *, int,
466     void *), void *cb_data, char **cause)
467 {
468 	if (c == NULL || c->session != NULL) {
469 		*cause = xstrdup("no client with stdin");
470 		return (-1);
471 	}
472 	if (c->flags & CLIENT_TERMINAL) {
473 		*cause = xstrdup("stdin is a tty");
474 		return (-1);
475 	}
476 	if (c->stdin_callback != NULL) {
477 		*cause = xstrdup("stdin in use");
478 		return (-1);
479 	}
480 
481 	c->stdin_callback_data = cb_data;
482 	c->stdin_callback = cb;
483 
484 	c->references++;
485 
486 	if (c->stdin_closed)
487 		c->stdin_callback(c, 1, c->stdin_callback_data);
488 
489 	proc_send(c->peer, MSG_STDIN, -1, NULL, 0);
490 
491 	return (0);
492 }
493 
494 void
495 server_unzoom_window(struct window *w)
496 {
497 	if (window_unzoom(w) == 0) {
498 		server_redraw_window(w);
499 		server_status_window(w);
500 	}
501 }
502