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