xref: /netbsd-src/external/bsd/tmux/dist/server-fn.c (revision bdc22b2e01993381dcefeff2bc9b56ca75a4235c)
1 /* $OpenBSD$ */
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/uio.h>
21 
22 #include <stdlib.h>
23 #include <string.h>
24 #include <time.h>
25 #include <unistd.h>
26 
27 #include "tmux.h"
28 
29 static struct session	*server_next_session(struct session *);
30 static void		 server_destroy_session_group(struct session *);
31 
32 void
33 server_redraw_client(struct client *c)
34 {
35 	c->flags |= CLIENT_REDRAW;
36 }
37 
38 void
39 server_status_client(struct client *c)
40 {
41 	c->flags |= CLIENT_STATUS;
42 }
43 
44 void
45 server_redraw_session(struct session *s)
46 {
47 	struct client	*c;
48 
49 	TAILQ_FOREACH(c, &clients, entry) {
50 		if (c->session == s)
51 			server_redraw_client(c);
52 	}
53 }
54 
55 void
56 server_redraw_session_group(struct session *s)
57 {
58 	struct session_group	*sg;
59 
60 	if ((sg = session_group_contains(s)) == NULL)
61 		server_redraw_session(s);
62 	else {
63 		TAILQ_FOREACH(s, &sg->sessions, gentry)
64 			server_redraw_session(s);
65 	}
66 }
67 
68 void
69 server_status_session(struct session *s)
70 {
71 	struct client	*c;
72 
73 	TAILQ_FOREACH(c, &clients, entry) {
74 		if (c->session == s)
75 			server_status_client(c);
76 	}
77 }
78 
79 void
80 server_status_session_group(struct session *s)
81 {
82 	struct session_group	*sg;
83 
84 	if ((sg = session_group_contains(s)) == NULL)
85 		server_status_session(s);
86 	else {
87 		TAILQ_FOREACH(s, &sg->sessions, gentry)
88 			server_status_session(s);
89 	}
90 }
91 
92 void
93 server_redraw_window(struct window *w)
94 {
95 	struct client	*c;
96 
97 	TAILQ_FOREACH(c, &clients, entry) {
98 		if (c->session != NULL && c->session->curw->window == w)
99 			server_redraw_client(c);
100 	}
101 }
102 
103 void
104 server_redraw_window_borders(struct window *w)
105 {
106 	struct client	*c;
107 
108 	TAILQ_FOREACH(c, &clients, entry) {
109 		if (c->session != NULL && c->session->curw->window == w)
110 			c->flags |= CLIENT_BORDERS;
111 	}
112 }
113 
114 void
115 server_status_window(struct window *w)
116 {
117 	struct session	*s;
118 
119 	/*
120 	 * This is slightly different. We want to redraw the status line of any
121 	 * clients containing this window rather than anywhere it is the
122 	 * current window.
123 	 */
124 
125 	RB_FOREACH(s, sessions, &sessions) {
126 		if (session_has(s, w))
127 			server_status_session(s);
128 	}
129 }
130 
131 void
132 server_lock(void)
133 {
134 	struct client	*c;
135 
136 	TAILQ_FOREACH(c, &clients, entry) {
137 		if (c->session != NULL)
138 			server_lock_client(c);
139 	}
140 }
141 
142 void
143 server_lock_session(struct session *s)
144 {
145 	struct client	*c;
146 
147 	TAILQ_FOREACH(c, &clients, entry) {
148 		if (c->session == s)
149 			server_lock_client(c);
150 	}
151 }
152 
153 void
154 server_lock_client(struct client *c)
155 {
156 	const char	*cmd;
157 
158 	if (c->flags & CLIENT_CONTROL)
159 		return;
160 
161 	if (c->flags & CLIENT_SUSPENDED)
162 		return;
163 
164 	cmd = options_get_string(c->session->options, "lock-command");
165 	if (*cmd == '\0' || strlen(cmd) + 1 > MAX_IMSGSIZE - IMSG_HEADER_SIZE)
166 		return;
167 
168 	tty_stop_tty(&c->tty);
169 	tty_raw(&c->tty, tty_term_string(c->tty.term, TTYC_SMCUP));
170 	tty_raw(&c->tty, tty_term_string(c->tty.term, TTYC_CLEAR));
171 	tty_raw(&c->tty, tty_term_string(c->tty.term, TTYC_E3));
172 
173 	c->flags |= CLIENT_SUSPENDED;
174 	proc_send(c->peer, MSG_LOCK, -1, cmd, strlen(cmd) + 1);
175 }
176 
177 void
178 server_kill_window(struct window *w)
179 {
180 	struct session		*s, *next_s, *target_s;
181 	struct session_group	*sg;
182 	struct winlink		*wl;
183 
184 	next_s = RB_MIN(sessions, &sessions);
185 	while (next_s != NULL) {
186 		s = next_s;
187 		next_s = RB_NEXT(sessions, &sessions, s);
188 
189 		if (!session_has(s, w))
190 			continue;
191 		server_unzoom_window(w);
192 		while ((wl = winlink_find_by_window(&s->windows, w)) != NULL) {
193 			if (session_detach(s, wl)) {
194 				server_destroy_session_group(s);
195 				break;
196 			} else
197 				server_redraw_session_group(s);
198 		}
199 
200 		if (options_get_number(s->options, "renumber-windows")) {
201 			if ((sg = session_group_contains(s)) != NULL) {
202 				TAILQ_FOREACH(target_s, &sg->sessions, gentry)
203 					session_renumber_windows(target_s);
204 			} else
205 				session_renumber_windows(s);
206 		}
207 	}
208 	recalculate_sizes();
209 }
210 
211 int
212 server_link_window(struct session *src, struct winlink *srcwl,
213     struct session *dst, int dstidx, int killflag, int selectflag,
214     char **cause)
215 {
216 	struct winlink		*dstwl;
217 	struct session_group	*srcsg, *dstsg;
218 
219 	srcsg = session_group_contains(src);
220 	dstsg = session_group_contains(dst);
221 	if (src != dst && srcsg != NULL && dstsg != NULL && srcsg == dstsg) {
222 		xasprintf(cause, "sessions are grouped");
223 		return (-1);
224 	}
225 
226 	dstwl = NULL;
227 	if (dstidx != -1)
228 		dstwl = winlink_find_by_index(&dst->windows, dstidx);
229 	if (dstwl != NULL) {
230 		if (dstwl->window == srcwl->window) {
231 			xasprintf(cause, "same index: %d", dstidx);
232 			return (-1);
233 		}
234 		if (killflag) {
235 			/*
236 			 * Can't use session_detach as it will destroy session
237 			 * if this makes it empty.
238 			 */
239 			notify_session_window("window-unlinked", dst,
240 			    dstwl->window);
241 			dstwl->flags &= ~WINLINK_ALERTFLAGS;
242 			winlink_stack_remove(&dst->lastw, dstwl);
243 			winlink_remove(&dst->windows, dstwl);
244 
245 			/* Force select/redraw if current. */
246 			if (dstwl == dst->curw) {
247 				selectflag = 1;
248 				dst->curw = NULL;
249 			}
250 		}
251 	}
252 
253 	if (dstidx == -1)
254 		dstidx = -1 - options_get_number(dst->options, "base-index");
255 	dstwl = session_attach(dst, srcwl->window, dstidx, cause);
256 	if (dstwl == NULL)
257 		return (-1);
258 
259 	if (selectflag)
260 		session_select(dst, dstwl->idx);
261 	server_redraw_session_group(dst);
262 
263 	return (0);
264 }
265 
266 void
267 server_unlink_window(struct session *s, struct winlink *wl)
268 {
269 	if (session_detach(s, wl))
270 		server_destroy_session_group(s);
271 	else
272 		server_redraw_session_group(s);
273 }
274 
275 void
276 server_destroy_pane(struct window_pane *wp, int notify)
277 {
278 	struct window		*w = wp->window;
279 	int			 old_fd;
280 	struct screen_write_ctx	 ctx;
281 	struct grid_cell	 gc;
282 
283 	old_fd = wp->fd;
284 	if (wp->fd != -1) {
285 #ifdef HAVE_UTEMPTER
286 		utempter_remove_record(wp->fd);
287 #endif
288 		bufferevent_free(wp->event);
289 		close(wp->fd);
290 		wp->fd = -1;
291 	}
292 
293 	if (options_get_number(w->options, "remain-on-exit")) {
294 		if (old_fd == -1)
295 			return;
296 
297 		if (notify)
298 			notify_pane("pane-died", wp);
299 
300 		screen_write_start(&ctx, wp, &wp->base);
301 		screen_write_scrollregion(&ctx, 0, screen_size_y(ctx.s) - 1);
302 		screen_write_cursormove(&ctx, 0, screen_size_y(ctx.s) - 1);
303 		screen_write_linefeed(&ctx, 1, 8);
304 		memcpy(&gc, &grid_default_cell, sizeof gc);
305 		gc.attr |= GRID_ATTR_BRIGHT;
306 		screen_write_puts(&ctx, &gc, "Pane is dead");
307 		screen_write_stop(&ctx);
308 		wp->flags |= PANE_REDRAW;
309 
310 		return;
311 	}
312 
313 	if (notify)
314 		notify_pane("pane-exited", wp);
315 
316 	server_unzoom_window(w);
317 	layout_close_pane(wp);
318 	window_remove_pane(w, wp);
319 
320 	if (TAILQ_EMPTY(&w->panes))
321 		server_kill_window(w);
322 	else
323 		server_redraw_window(w);
324 }
325 
326 static void
327 server_destroy_session_group(struct session *s)
328 {
329 	struct session_group	*sg;
330 	struct session		*s1;
331 
332 	if ((sg = session_group_contains(s)) == NULL)
333 		server_destroy_session(s);
334 	else {
335 		TAILQ_FOREACH_SAFE(s, &sg->sessions, gentry, s1) {
336 			server_destroy_session(s);
337 			session_destroy(s, __func__);
338 		}
339 	}
340 }
341 
342 static struct session *
343 server_next_session(struct session *s)
344 {
345 	struct session *s_loop, *s_out;
346 
347 	s_out = NULL;
348 	RB_FOREACH(s_loop, sessions, &sessions) {
349 		if (s_loop == s)
350 			continue;
351 		if (s_out == NULL ||
352 		    timercmp(&s_loop->activity_time, &s_out->activity_time, <))
353 			s_out = s_loop;
354 	}
355 	return (s_out);
356 }
357 
358 void
359 server_destroy_session(struct session *s)
360 {
361 	struct client	*c;
362 	struct session	*s_new;
363 
364 	if (!options_get_number(s->options, "detach-on-destroy"))
365 		s_new = server_next_session(s);
366 	else
367 		s_new = NULL;
368 
369 	TAILQ_FOREACH(c, &clients, entry) {
370 		if (c->session != s)
371 			continue;
372 		if (s_new == NULL) {
373 			c->session = NULL;
374 			c->flags |= CLIENT_EXIT;
375 		} else {
376 			c->last_session = NULL;
377 			c->session = s_new;
378 			server_client_set_key_table(c, NULL);
379 			status_timer_start(c);
380 			notify_client("client-session-changed", c);
381 			session_update_activity(s_new, NULL);
382 			gettimeofday(&s_new->last_attached_time, NULL);
383 			server_redraw_client(c);
384 			alerts_check_session(s_new);
385 		}
386 	}
387 	recalculate_sizes();
388 }
389 
390 void
391 server_check_unattached(void)
392 {
393 	struct session	*s;
394 
395 	/*
396 	 * If any sessions are no longer attached and have destroy-unattached
397 	 * set, collect them.
398 	 */
399 	RB_FOREACH(s, sessions, &sessions) {
400 		if (!(s->flags & SESSION_UNATTACHED))
401 			continue;
402 		if (options_get_number (s->options, "destroy-unattached"))
403 			session_destroy(s, __func__);
404 	}
405 }
406 
407 /* Set stdin callback. */
408 int
409 server_set_stdin_callback(struct client *c, void (*cb)(struct client *, int,
410     void *), void *cb_data, char **cause)
411 {
412 	if (c == NULL || c->session != NULL) {
413 		*cause = xstrdup("no client with stdin");
414 		return (-1);
415 	}
416 	if (c->flags & CLIENT_TERMINAL) {
417 		*cause = xstrdup("stdin is a tty");
418 		return (-1);
419 	}
420 	if (c->stdin_callback != NULL) {
421 		*cause = xstrdup("stdin in use");
422 		return (-1);
423 	}
424 
425 	c->stdin_callback_data = cb_data;
426 	c->stdin_callback = cb;
427 
428 	c->references++;
429 
430 	if (c->stdin_closed)
431 		c->stdin_callback(c, 1, c->stdin_callback_data);
432 
433 	proc_send(c->peer, MSG_STDIN, -1, NULL, 0);
434 
435 	return (0);
436 }
437 
438 void
439 server_unzoom_window(struct window *w)
440 {
441 	if (window_unzoom(w) == 0) {
442 		server_redraw_window(w);
443 		server_status_window(w);
444 	}
445 }
446