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/wait.h> 21 #include <sys/uio.h> 22 23 #include <stdlib.h> 24 #include <string.h> 25 #include <time.h> 26 #include <unistd.h> 27 28 #include "tmux.h" 29 30 static void server_destroy_session_group(struct session *); 31 32 void 33 server_redraw_client(struct client *c) 34 { 35 c->flags |= CLIENT_ALLREDRAWFLAGS; 36 } 37 38 void 39 server_status_client(struct client *c) 40 { 41 c->flags |= CLIENT_REDRAWSTATUS; 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_REDRAWBORDERS; 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_pane(struct window_pane *wp) 179 { 180 struct window *w = wp->window; 181 182 if (window_count_panes(w) == 1) { 183 server_kill_window(w, 1); 184 recalculate_sizes(); 185 } else { 186 server_unzoom_window(w); 187 server_client_remove_pane(wp); 188 layout_close_pane(wp); 189 window_remove_pane(w, wp); 190 server_redraw_window(w); 191 } 192 } 193 194 void 195 server_kill_window(struct window *w, int renumber) 196 { 197 struct session *s, *s1; 198 struct winlink *wl; 199 200 RB_FOREACH_SAFE(s, sessions, &sessions, s1) { 201 if (!session_has(s, w)) 202 continue; 203 204 server_unzoom_window(w); 205 while ((wl = winlink_find_by_window(&s->windows, w)) != NULL) { 206 if (session_detach(s, wl)) { 207 server_destroy_session_group(s); 208 break; 209 } 210 server_redraw_session_group(s); 211 } 212 213 if (renumber) 214 server_renumber_session(s); 215 } 216 recalculate_sizes(); 217 } 218 219 void 220 server_renumber_session(struct session *s) 221 { 222 struct session_group *sg; 223 224 if (options_get_number(s->options, "renumber-windows")) { 225 if ((sg = session_group_contains(s)) != NULL) { 226 TAILQ_FOREACH(s, &sg->sessions, gentry) 227 session_renumber_windows(s); 228 } else 229 session_renumber_windows(s); 230 } 231 } 232 233 void 234 server_renumber_all(void) 235 { 236 struct session *s; 237 238 RB_FOREACH(s, sessions, &sessions) 239 server_renumber_session(s); 240 } 241 242 int 243 server_link_window(struct session *src, struct winlink *srcwl, 244 struct session *dst, int dstidx, int killflag, int selectflag, 245 char **cause) 246 { 247 struct winlink *dstwl; 248 struct session_group *srcsg, *dstsg; 249 250 srcsg = session_group_contains(src); 251 dstsg = session_group_contains(dst); 252 if (src != dst && srcsg != NULL && dstsg != NULL && srcsg == dstsg) { 253 xasprintf(cause, "sessions are grouped"); 254 return (-1); 255 } 256 257 dstwl = NULL; 258 if (dstidx != -1) 259 dstwl = winlink_find_by_index(&dst->windows, dstidx); 260 if (dstwl != NULL) { 261 if (dstwl->window == srcwl->window) { 262 xasprintf(cause, "same index: %d", dstidx); 263 return (-1); 264 } 265 if (killflag) { 266 /* 267 * Can't use session_detach as it will destroy session 268 * if this makes it empty. 269 */ 270 notify_session_window("window-unlinked", dst, 271 dstwl->window); 272 dstwl->flags &= ~WINLINK_ALERTFLAGS; 273 winlink_stack_remove(&dst->lastw, dstwl); 274 winlink_remove(&dst->windows, dstwl); 275 276 /* Force select/redraw if current. */ 277 if (dstwl == dst->curw) { 278 selectflag = 1; 279 dst->curw = NULL; 280 } 281 } 282 } 283 284 if (dstidx == -1) 285 dstidx = -1 - options_get_number(dst->options, "base-index"); 286 dstwl = session_attach(dst, srcwl->window, dstidx, cause); 287 if (dstwl == NULL) 288 return (-1); 289 290 if (selectflag) 291 session_select(dst, dstwl->idx); 292 server_redraw_session_group(dst); 293 294 return (0); 295 } 296 297 void 298 server_unlink_window(struct session *s, struct winlink *wl) 299 { 300 if (session_detach(s, wl)) 301 server_destroy_session_group(s); 302 else 303 server_redraw_session_group(s); 304 } 305 306 void 307 server_destroy_pane(struct window_pane *wp, int notify) 308 { 309 struct window *w = wp->window; 310 struct screen_write_ctx ctx; 311 struct grid_cell gc; 312 int remain_on_exit; 313 const char *s; 314 char *expanded; 315 u_int sx = screen_size_x(&wp->base); 316 u_int sy = screen_size_y(&wp->base); 317 318 if (wp->fd != -1) { 319 #ifdef HAVE_UTEMPTER 320 utempter_remove_record(wp->fd); 321 #endif 322 bufferevent_free(wp->event); 323 wp->event = NULL; 324 close(wp->fd); 325 wp->fd = -1; 326 } 327 328 remain_on_exit = options_get_number(wp->options, "remain-on-exit"); 329 if (remain_on_exit != 0 && (~wp->flags & PANE_STATUSREADY)) 330 return; 331 switch (remain_on_exit) { 332 case 0: 333 break; 334 case 2: 335 if (WIFEXITED(wp->status) && WEXITSTATUS(wp->status) == 0) 336 break; 337 /* FALLTHROUGH */ 338 case 1: 339 if (wp->flags & PANE_STATUSDRAWN) 340 return; 341 wp->flags |= PANE_STATUSDRAWN; 342 343 gettimeofday(&wp->dead_time, NULL); 344 if (notify) 345 notify_pane("pane-died", wp); 346 347 s = options_get_string(wp->options, "remain-on-exit-format"); 348 if (*s != '\0') { 349 screen_write_start_pane(&ctx, wp, &wp->base); 350 screen_write_scrollregion(&ctx, 0, sy - 1); 351 screen_write_cursormove(&ctx, 0, sy - 1, 0); 352 screen_write_linefeed(&ctx, 1, 8); 353 memcpy(&gc, &grid_default_cell, sizeof gc); 354 355 expanded = format_single(NULL, s, NULL, NULL, NULL, wp); 356 format_draw(&ctx, &gc, sx, expanded, NULL, 0); 357 free(expanded); 358 359 screen_write_stop(&ctx); 360 } 361 wp->base.mode &= ~MODE_CURSOR; 362 363 wp->flags |= PANE_REDRAW; 364 return; 365 } 366 367 if (notify) 368 notify_pane("pane-exited", wp); 369 370 server_unzoom_window(w); 371 server_client_remove_pane(wp); 372 layout_close_pane(wp); 373 window_remove_pane(w, wp); 374 375 if (TAILQ_EMPTY(&w->panes)) 376 server_kill_window(w, 1); 377 else 378 server_redraw_window(w); 379 } 380 381 static void 382 server_destroy_session_group(struct session *s) 383 { 384 struct session_group *sg; 385 struct session *s1; 386 387 if ((sg = session_group_contains(s)) == NULL) { 388 server_destroy_session(s); 389 session_destroy(s, 1, __func__); 390 } else { 391 TAILQ_FOREACH_SAFE(s, &sg->sessions, gentry, s1) { 392 server_destroy_session(s); 393 session_destroy(s, 1, __func__); 394 } 395 } 396 } 397 398 static struct session * 399 server_find_session(struct session *s, 400 int (*f)(struct session *, struct session *)) 401 { 402 struct session *s_loop, *s_out = NULL; 403 404 RB_FOREACH(s_loop, sessions, &sessions) { 405 if (s_loop != s && (s_out == NULL || f(s_loop, s_out))) 406 s_out = s_loop; 407 } 408 return (s_out); 409 } 410 411 static int 412 server_newer_session(struct session *s_loop, struct session *s_out) 413 { 414 return (timercmp(&s_loop->activity_time, &s_out->activity_time, >)); 415 } 416 417 static int 418 server_newer_detached_session(struct session *s_loop, struct session *s_out) 419 { 420 if (s_loop->attached) 421 return (0); 422 return (server_newer_session(s_loop, s_out)); 423 } 424 425 void 426 server_destroy_session(struct session *s) 427 { 428 struct client *c; 429 struct session *s_new = NULL; 430 int detach_on_destroy; 431 432 detach_on_destroy = options_get_number(s->options, "detach-on-destroy"); 433 if (detach_on_destroy == 0) 434 s_new = server_find_session(s, server_newer_session); 435 else if (detach_on_destroy == 2) 436 s_new = server_find_session(s, server_newer_detached_session); 437 else if (detach_on_destroy == 3) 438 s_new = session_previous_session(s); 439 else if (detach_on_destroy == 4) 440 s_new = session_next_session(s); 441 if (s_new == s) 442 s_new = NULL; 443 TAILQ_FOREACH(c, &clients, entry) { 444 if (c->session != s) 445 continue; 446 c->session = NULL; 447 c->last_session = NULL; 448 server_client_set_session(c, s_new); 449 if (s_new == NULL) 450 c->flags |= CLIENT_EXIT; 451 } 452 recalculate_sizes(); 453 } 454 455 void 456 server_check_unattached(void) 457 { 458 struct session *s; 459 struct session_group *sg; 460 461 /* 462 * If any sessions are no longer attached and have destroy-unattached 463 * set, collect them. 464 */ 465 RB_FOREACH(s, sessions, &sessions) { 466 if (s->attached != 0) 467 continue; 468 switch (options_get_number(s->options, "destroy-unattached")) { 469 case 0: /* off */ 470 continue; 471 case 1: /* on */ 472 break; 473 case 2: /* keep-last */ 474 sg = session_group_contains(s); 475 if (sg == NULL || session_group_count(sg) <= 1) 476 continue; 477 break; 478 case 3: /* keep-group */ 479 sg = session_group_contains(s); 480 if (sg != NULL && session_group_count(sg) == 1) 481 continue; 482 break; 483 } 484 session_destroy(s, 1, __func__); 485 } 486 } 487 488 void 489 server_unzoom_window(struct window *w) 490 { 491 if (window_unzoom(w, 1) == 0) 492 server_redraw_window(w); 493 } 494