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