Home | History | Annotate | Line # | Download | only in dist
      1 /* $OpenBSD$ */
      2 
      3 /*
      4  * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott (at) 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/wait.h>
     21 #include <sys/uio.h>
     22 
     23 #include <signal.h>
     24 #include <stdlib.h>
     25 #include <string.h>
     26 #include <time.h>
     27 #include <unistd.h>
     28 
     29 #include "tmux.h"
     30 
     31 static void	server_destroy_session_group(struct session *);
     32 
     33 void
     34 server_redraw_client(struct client *c)
     35 {
     36 	c->flags |= CLIENT_ALLREDRAWFLAGS;
     37 }
     38 
     39 void
     40 server_status_client(struct client *c)
     41 {
     42 	c->flags |= CLIENT_REDRAWSTATUS;
     43 }
     44 
     45 void
     46 server_redraw_session(struct session *s)
     47 {
     48 	struct client	*c;
     49 
     50 	TAILQ_FOREACH(c, &clients, entry) {
     51 		if (c->session == s)
     52 			server_redraw_client(c);
     53 	}
     54 }
     55 
     56 void
     57 server_redraw_session_group(struct session *s)
     58 {
     59 	struct session_group	*sg;
     60 
     61 	if ((sg = session_group_contains(s)) == NULL)
     62 		server_redraw_session(s);
     63 	else {
     64 		TAILQ_FOREACH(s, &sg->sessions, gentry)
     65 			server_redraw_session(s);
     66 	}
     67 }
     68 
     69 void
     70 server_status_session(struct session *s)
     71 {
     72 	struct client	*c;
     73 
     74 	TAILQ_FOREACH(c, &clients, entry) {
     75 		if (c->session == s)
     76 			server_status_client(c);
     77 	}
     78 }
     79 
     80 void
     81 server_status_session_group(struct session *s)
     82 {
     83 	struct session_group	*sg;
     84 
     85 	if ((sg = session_group_contains(s)) == NULL)
     86 		server_status_session(s);
     87 	else {
     88 		TAILQ_FOREACH(s, &sg->sessions, gentry)
     89 			server_status_session(s);
     90 	}
     91 }
     92 
     93 void
     94 server_redraw_window(struct window *w)
     95 {
     96 	struct client	*c;
     97 
     98 	TAILQ_FOREACH(c, &clients, entry) {
     99 		if (c->session != NULL && c->session->curw->window == w)
    100 			server_redraw_client(c);
    101 	}
    102 }
    103 
    104 void
    105 server_redraw_window_borders(struct window *w)
    106 {
    107 	struct client	*c;
    108 
    109 	TAILQ_FOREACH(c, &clients, entry) {
    110 		if (c->session != NULL && c->session->curw->window == w)
    111 			c->flags |= CLIENT_REDRAWBORDERS;
    112 	}
    113 }
    114 
    115 void
    116 server_status_window(struct window *w)
    117 {
    118 	struct session	*s;
    119 
    120 	/*
    121 	 * This is slightly different. We want to redraw the status line of any
    122 	 * clients containing this window rather than anywhere it is the
    123 	 * current window.
    124 	 */
    125 
    126 	RB_FOREACH(s, sessions, &sessions) {
    127 		if (session_has(s, w))
    128 			server_status_session(s);
    129 	}
    130 }
    131 
    132 void
    133 server_lock(void)
    134 {
    135 	struct client	*c;
    136 
    137 	TAILQ_FOREACH(c, &clients, entry) {
    138 		if (c->session != NULL)
    139 			server_lock_client(c);
    140 	}
    141 }
    142 
    143 void
    144 server_lock_session(struct session *s)
    145 {
    146 	struct client	*c;
    147 
    148 	TAILQ_FOREACH(c, &clients, entry) {
    149 		if (c->session == s)
    150 			server_lock_client(c);
    151 	}
    152 }
    153 
    154 void
    155 server_lock_client(struct client *c)
    156 {
    157 	const char	*cmd;
    158 
    159 	if (c->flags & CLIENT_CONTROL)
    160 		return;
    161 
    162 	if (c->flags & CLIENT_SUSPENDED)
    163 		return;
    164 
    165 	cmd = options_get_string(c->session->options, "lock-command");
    166 	if (*cmd == '\0' || strlen(cmd) + 1 > MAX_IMSGSIZE - IMSG_HEADER_SIZE)
    167 		return;
    168 
    169 	tty_stop_tty(&c->tty);
    170 	tty_raw(&c->tty, tty_term_string(c->tty.term, TTYC_SMCUP));
    171 	tty_raw(&c->tty, tty_term_string(c->tty.term, TTYC_CLEAR));
    172 	tty_raw(&c->tty, tty_term_string(c->tty.term, TTYC_E3));
    173 
    174 	c->flags |= CLIENT_SUSPENDED;
    175 	proc_send(c->peer, MSG_LOCK, -1, cmd, strlen(cmd) + 1);
    176 }
    177 
    178 void
    179 server_kill_pane(struct window_pane *wp)
    180 {
    181 	struct window	*w = wp->window;
    182 
    183 	if (window_count_panes(w) == 1) {
    184 		server_kill_window(w, 1);
    185 		recalculate_sizes();
    186 	} else {
    187 		server_unzoom_window(w);
    188 		server_client_remove_pane(wp);
    189 		layout_close_pane(wp);
    190 		window_remove_pane(w, wp);
    191 		server_redraw_window(w);
    192 	}
    193 }
    194 
    195 void
    196 server_kill_window(struct window *w, int renumber)
    197 {
    198 	struct session	*s, *s1;
    199 	struct winlink	*wl;
    200 
    201 	RB_FOREACH_SAFE(s, sessions, &sessions, s1) {
    202 		if (!session_has(s, w))
    203 			continue;
    204 
    205 		server_unzoom_window(w);
    206 		while ((wl = winlink_find_by_window(&s->windows, w)) != NULL) {
    207 			if (session_detach(s, wl)) {
    208 				server_destroy_session_group(s);
    209 				break;
    210 			}
    211 			server_redraw_session_group(s);
    212 		}
    213 
    214 		if (renumber)
    215 			server_renumber_session(s);
    216 	}
    217 	recalculate_sizes();
    218 }
    219 
    220 void
    221 server_renumber_session(struct session *s)
    222 {
    223 	struct session_group	*sg;
    224 
    225 	if (options_get_number(s->options, "renumber-windows")) {
    226 		if ((sg = session_group_contains(s)) != NULL) {
    227 			TAILQ_FOREACH(s, &sg->sessions, gentry)
    228 			    session_renumber_windows(s);
    229 		} else
    230 			session_renumber_windows(s);
    231 	}
    232 }
    233 
    234 void
    235 server_renumber_all(void)
    236 {
    237 	struct session	*s;
    238 
    239 	RB_FOREACH(s, sessions, &sessions)
    240 		server_renumber_session(s);
    241 }
    242 
    243 int
    244 server_link_window(struct session *src, struct winlink *srcwl,
    245     struct session *dst, int dstidx, int killflag, int selectflag,
    246     char **cause)
    247 {
    248 	struct winlink		*dstwl;
    249 	struct session_group	*srcsg, *dstsg;
    250 
    251 	srcsg = session_group_contains(src);
    252 	dstsg = session_group_contains(dst);
    253 	if (src != dst && srcsg != NULL && dstsg != NULL && srcsg == dstsg) {
    254 		xasprintf(cause, "sessions are grouped");
    255 		return (-1);
    256 	}
    257 
    258 	dstwl = NULL;
    259 	if (dstidx != -1)
    260 		dstwl = winlink_find_by_index(&dst->windows, dstidx);
    261 	if (dstwl != NULL) {
    262 		if (dstwl->window == srcwl->window) {
    263 			xasprintf(cause, "same index: %d", dstidx);
    264 			return (-1);
    265 		}
    266 		if (killflag) {
    267 			/*
    268 			 * Can't use session_detach as it will destroy session
    269 			 * if this makes it empty.
    270 			 */
    271 			notify_session_window("window-unlinked", dst,
    272 			    dstwl->window);
    273 			dstwl->flags &= ~WINLINK_ALERTFLAGS;
    274 			winlink_stack_remove(&dst->lastw, dstwl);
    275 			winlink_remove(&dst->windows, dstwl);
    276 
    277 			/* Force select/redraw if current. */
    278 			if (dstwl == dst->curw) {
    279 				selectflag = 1;
    280 				dst->curw = NULL;
    281 			}
    282 		}
    283 	}
    284 
    285 	if (dstidx == -1)
    286 		dstidx = -1 - options_get_number(dst->options, "base-index");
    287 	dstwl = session_attach(dst, srcwl->window, dstidx, cause);
    288 	if (dstwl == NULL)
    289 		return (-1);
    290 
    291 	if (marked_pane.wl == srcwl)
    292 		marked_pane.wl = dstwl;
    293 	if (selectflag)
    294 		session_select(dst, dstwl->idx);
    295 	server_redraw_session_group(dst);
    296 
    297 	return (0);
    298 }
    299 
    300 void
    301 server_unlink_window(struct session *s, struct winlink *wl)
    302 {
    303 	if (session_detach(s, wl))
    304 		server_destroy_session_group(s);
    305 	else
    306 		server_redraw_session_group(s);
    307 }
    308 
    309 void
    310 server_destroy_pane(struct window_pane *wp, int notify)
    311 {
    312 	struct window		*w = wp->window;
    313 	struct screen_write_ctx	 ctx;
    314 	struct grid_cell	 gc;
    315 	int			 remain_on_exit;
    316 	const char		*s;
    317 	char			*expanded;
    318 	u_int			 sx = screen_size_x(&wp->base);
    319 	u_int			 sy = screen_size_y(&wp->base);
    320 
    321 	if (wp->fd != -1) {
    322 #ifdef HAVE_UTEMPTER
    323 		utempter_remove_record(wp->fd);
    324 		kill(getpid(), SIGCHLD);
    325 #endif
    326 		bufferevent_free(wp->event);
    327 		wp->event = NULL;
    328 		close(wp->fd);
    329 		wp->fd = -1;
    330 	}
    331 
    332 	remain_on_exit = options_get_number(wp->options, "remain-on-exit");
    333 	if (remain_on_exit != 0 && (~wp->flags & PANE_STATUSREADY))
    334 		return;
    335 	switch (remain_on_exit) {
    336 	case 0:
    337 		break;
    338 	case 2:
    339 		if (WIFEXITED(wp->status) && WEXITSTATUS(wp->status) == 0)
    340 			break;
    341 		/* FALLTHROUGH */
    342 	case 1:
    343 		if (wp->flags & PANE_STATUSDRAWN)
    344 			return;
    345 		wp->flags |= PANE_STATUSDRAWN;
    346 
    347 		gettimeofday(&wp->dead_time, NULL);
    348 		if (notify)
    349 			notify_pane("pane-died", wp);
    350 
    351 		s = options_get_string(wp->options, "remain-on-exit-format");
    352 		if (*s != '\0') {
    353 			screen_write_start_pane(&ctx, wp, &wp->base);
    354 			screen_write_scrollregion(&ctx, 0, sy - 1);
    355 			screen_write_cursormove(&ctx, 0, sy - 1, 0);
    356 			screen_write_linefeed(&ctx, 1, 8);
    357 			memcpy(&gc, &grid_default_cell, sizeof gc);
    358 
    359 			expanded = format_single(NULL, s, NULL, NULL, NULL, wp);
    360 			format_draw(&ctx, &gc, sx, expanded, NULL, 0);
    361 			free(expanded);
    362 
    363 			screen_write_stop(&ctx);
    364 		}
    365 		wp->base.mode &= ~MODE_CURSOR;
    366 
    367 		wp->flags |= PANE_REDRAW;
    368 		return;
    369 	}
    370 
    371 	if (notify)
    372 		notify_pane("pane-exited", wp);
    373 
    374 	server_unzoom_window(w);
    375 	server_client_remove_pane(wp);
    376 	layout_close_pane(wp);
    377 	window_remove_pane(w, wp);
    378 
    379 	if (TAILQ_EMPTY(&w->panes))
    380 		server_kill_window(w, 1);
    381 	else
    382 		server_redraw_window(w);
    383 }
    384 
    385 static void
    386 server_destroy_session_group(struct session *s)
    387 {
    388 	struct session_group	*sg;
    389 	struct session		*s1;
    390 
    391 	if ((sg = session_group_contains(s)) == NULL) {
    392 		server_destroy_session(s);
    393 		session_destroy(s, 1, __func__);
    394 	} else {
    395 		TAILQ_FOREACH_SAFE(s, &sg->sessions, gentry, s1) {
    396 			server_destroy_session(s);
    397 			session_destroy(s, 1, __func__);
    398 		}
    399 	}
    400 }
    401 
    402 static struct session *
    403 server_find_session(struct session *s,
    404     int (*f)(struct session *, struct session *))
    405 {
    406 	struct session *s_loop, *s_out = NULL;
    407 
    408 	RB_FOREACH(s_loop, sessions, &sessions) {
    409 		if (s_loop != s && f(s_loop, s_out))
    410 			s_out = s_loop;
    411 	}
    412 	return (s_out);
    413 }
    414 
    415 static int
    416 server_newer_session(struct session *s_loop, struct session *s_out)
    417 {
    418 	if (s_out == NULL)
    419 		return (1);
    420 	return (timercmp(&s_loop->activity_time, &s_out->activity_time, >));
    421 }
    422 
    423 static int
    424 server_newer_detached_session(struct session *s_loop, struct session *s_out)
    425 {
    426 	if (s_loop->attached)
    427 		return (0);
    428 	return (server_newer_session(s_loop, s_out));
    429 }
    430 
    431 void
    432 server_destroy_session(struct session *s)
    433 {
    434 	struct client	*c;
    435 	struct session	*s_new = NULL, *cs_new = NULL, *use_s;
    436 	int		 detach_on_destroy;
    437 
    438 	detach_on_destroy = options_get_number(s->options, "detach-on-destroy");
    439 	if (detach_on_destroy == 0)
    440 		s_new = server_find_session(s, server_newer_session);
    441 	else if (detach_on_destroy == 2)
    442 		s_new = server_find_session(s, server_newer_detached_session);
    443 	else if (detach_on_destroy == 3)
    444 		s_new = session_previous_session(s);
    445 	else if (detach_on_destroy == 4)
    446 		s_new = session_next_session(s);
    447 
    448 	/*
    449 	 * If no suitable new session was found above, then look for any
    450 	 * session as an alternative in case a client needs it.
    451 	 */
    452 	if (s_new == NULL &&
    453 	    (detach_on_destroy == 1 || detach_on_destroy == 2))
    454 		cs_new = server_find_session(s, server_newer_session);
    455 
    456 	TAILQ_FOREACH(c, &clients, entry) {
    457 		if (c->session != s)
    458 			continue;
    459 		use_s = s_new;
    460 		if (use_s == NULL && (c->flags & CLIENT_NO_DETACH_ON_DESTROY))
    461 			use_s = cs_new;
    462 
    463 		c->session = NULL;
    464 		c->last_session = NULL;
    465 		server_client_set_session(c, use_s);
    466 		if (use_s == NULL)
    467 			c->flags |= CLIENT_EXIT;
    468 	}
    469 	recalculate_sizes();
    470 }
    471 
    472 void
    473 server_check_unattached(void)
    474 {
    475 	struct session		*s;
    476 	struct session_group	*sg;
    477 
    478 	/*
    479 	 * If any sessions are no longer attached and have destroy-unattached
    480 	 * set, collect them.
    481 	 */
    482 	RB_FOREACH(s, sessions, &sessions) {
    483 		if (s->attached != 0)
    484 			continue;
    485 		switch (options_get_number(s->options, "destroy-unattached")) {
    486 		case 0: /* off */
    487 			continue;
    488 		case 1: /* on */
    489 			break;
    490 		case 2: /* keep-last */
    491 			sg = session_group_contains(s);
    492 			if (sg == NULL || session_group_count(sg) <= 1)
    493 				continue;
    494 			break;
    495 		case 3: /* keep-group */
    496 			sg = session_group_contains(s);
    497 			if (sg != NULL && session_group_count(sg) == 1)
    498 				continue;
    499 			break;
    500 		}
    501 		session_destroy(s, 1, __func__);
    502 	}
    503 }
    504 
    505 void
    506 server_unzoom_window(struct window *w)
    507 {
    508 	if (window_unzoom(w, 1) == 0)
    509 		server_redraw_window(w);
    510 }
    511