1 /* $OpenBSD: server-fn.c,v 1.133 2022/03/08 18:31:46 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 struct session *server_next_session(struct session *); 33 static void server_destroy_session_group(struct session *); 34 35 void 36 server_redraw_client(struct client *c) 37 { 38 c->flags |= CLIENT_ALLREDRAWFLAGS; 39 } 40 41 void 42 server_status_client(struct client *c) 43 { 44 c->flags |= CLIENT_REDRAWSTATUS; 45 } 46 47 void 48 server_redraw_session(struct session *s) 49 { 50 struct client *c; 51 52 TAILQ_FOREACH(c, &clients, entry) { 53 if (c->session == s) 54 server_redraw_client(c); 55 } 56 } 57 58 void 59 server_redraw_session_group(struct session *s) 60 { 61 struct session_group *sg; 62 63 if ((sg = session_group_contains(s)) == NULL) 64 server_redraw_session(s); 65 else { 66 TAILQ_FOREACH(s, &sg->sessions, gentry) 67 server_redraw_session(s); 68 } 69 } 70 71 void 72 server_status_session(struct session *s) 73 { 74 struct client *c; 75 76 TAILQ_FOREACH(c, &clients, entry) { 77 if (c->session == s) 78 server_status_client(c); 79 } 80 } 81 82 void 83 server_status_session_group(struct session *s) 84 { 85 struct session_group *sg; 86 87 if ((sg = session_group_contains(s)) == NULL) 88 server_status_session(s); 89 else { 90 TAILQ_FOREACH(s, &sg->sessions, gentry) 91 server_status_session(s); 92 } 93 } 94 95 void 96 server_redraw_window(struct window *w) 97 { 98 struct client *c; 99 100 TAILQ_FOREACH(c, &clients, entry) { 101 if (c->session != NULL && c->session->curw->window == w) 102 server_redraw_client(c); 103 } 104 } 105 106 void 107 server_redraw_window_borders(struct window *w) 108 { 109 struct client *c; 110 111 TAILQ_FOREACH(c, &clients, entry) { 112 if (c->session != NULL && c->session->curw->window == w) 113 c->flags |= CLIENT_REDRAWBORDERS; 114 } 115 } 116 117 void 118 server_status_window(struct window *w) 119 { 120 struct session *s; 121 122 /* 123 * This is slightly different. We want to redraw the status line of any 124 * clients containing this window rather than anywhere it is the 125 * current window. 126 */ 127 128 RB_FOREACH(s, sessions, &sessions) { 129 if (session_has(s, w)) 130 server_status_session(s); 131 } 132 } 133 134 void 135 server_lock(void) 136 { 137 struct client *c; 138 139 TAILQ_FOREACH(c, &clients, entry) { 140 if (c->session != NULL) 141 server_lock_client(c); 142 } 143 } 144 145 void 146 server_lock_session(struct session *s) 147 { 148 struct client *c; 149 150 TAILQ_FOREACH(c, &clients, entry) { 151 if (c->session == s) 152 server_lock_client(c); 153 } 154 } 155 156 void 157 server_lock_client(struct client *c) 158 { 159 const char *cmd; 160 161 if (c->flags & CLIENT_CONTROL) 162 return; 163 164 if (c->flags & CLIENT_SUSPENDED) 165 return; 166 167 cmd = options_get_string(c->session->options, "lock-command"); 168 if (*cmd == '\0' || strlen(cmd) + 1 > MAX_IMSGSIZE - IMSG_HEADER_SIZE) 169 return; 170 171 tty_stop_tty(&c->tty); 172 tty_raw(&c->tty, tty_term_string(c->tty.term, TTYC_SMCUP)); 173 tty_raw(&c->tty, tty_term_string(c->tty.term, TTYC_CLEAR)); 174 tty_raw(&c->tty, tty_term_string(c->tty.term, TTYC_E3)); 175 176 c->flags |= CLIENT_SUSPENDED; 177 proc_send(c->peer, MSG_LOCK, -1, cmd, strlen(cmd) + 1); 178 } 179 180 void 181 server_kill_pane(struct window_pane *wp) 182 { 183 struct window *w = wp->window; 184 185 if (window_count_panes(w) == 1) { 186 server_kill_window(w, 1); 187 recalculate_sizes(); 188 } else { 189 server_unzoom_window(w); 190 server_client_remove_pane(wp); 191 layout_close_pane(wp); 192 window_remove_pane(w, wp); 193 server_redraw_window(w); 194 } 195 } 196 197 void 198 server_kill_window(struct window *w, int renumber) 199 { 200 struct session *s, *s1; 201 struct winlink *wl; 202 203 RB_FOREACH_SAFE(s, sessions, &sessions, s1) { 204 if (!session_has(s, w)) 205 continue; 206 207 server_unzoom_window(w); 208 while ((wl = winlink_find_by_window(&s->windows, w)) != NULL) { 209 if (session_detach(s, wl)) { 210 server_destroy_session_group(s); 211 break; 212 } else 213 server_redraw_session_group(s); 214 } 215 216 if (renumber) 217 server_renumber_session(s); 218 } 219 recalculate_sizes(); 220 } 221 222 void 223 server_renumber_session(struct session *s) 224 { 225 struct session_group *sg; 226 227 if (options_get_number(s->options, "renumber-windows")) { 228 if ((sg = session_group_contains(s)) != NULL) { 229 TAILQ_FOREACH(s, &sg->sessions, gentry) 230 session_renumber_windows(s); 231 } else 232 session_renumber_windows(s); 233 } 234 } 235 236 void 237 server_renumber_all(void) 238 { 239 struct session *s; 240 241 RB_FOREACH(s, sessions, &sessions) 242 server_renumber_session(s); 243 } 244 245 int 246 server_link_window(struct session *src, struct winlink *srcwl, 247 struct session *dst, int dstidx, int killflag, int selectflag, 248 char **cause) 249 { 250 struct winlink *dstwl; 251 struct session_group *srcsg, *dstsg; 252 253 srcsg = session_group_contains(src); 254 dstsg = session_group_contains(dst); 255 if (src != dst && srcsg != NULL && dstsg != NULL && srcsg == dstsg) { 256 xasprintf(cause, "sessions are grouped"); 257 return (-1); 258 } 259 260 dstwl = NULL; 261 if (dstidx != -1) 262 dstwl = winlink_find_by_index(&dst->windows, dstidx); 263 if (dstwl != NULL) { 264 if (dstwl->window == srcwl->window) { 265 xasprintf(cause, "same index: %d", dstidx); 266 return (-1); 267 } 268 if (killflag) { 269 /* 270 * Can't use session_detach as it will destroy session 271 * if this makes it empty. 272 */ 273 notify_session_window("window-unlinked", dst, 274 dstwl->window); 275 dstwl->flags &= ~WINLINK_ALERTFLAGS; 276 winlink_stack_remove(&dst->lastw, dstwl); 277 winlink_remove(&dst->windows, dstwl); 278 279 /* Force select/redraw if current. */ 280 if (dstwl == dst->curw) { 281 selectflag = 1; 282 dst->curw = NULL; 283 } 284 } 285 } 286 287 if (dstidx == -1) 288 dstidx = -1 - options_get_number(dst->options, "base-index"); 289 dstwl = session_attach(dst, srcwl->window, dstidx, cause); 290 if (dstwl == NULL) 291 return (-1); 292 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 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 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_next_session(struct session *s) 399 { 400 struct session *s_loop, *s_out = NULL; 401 402 RB_FOREACH(s_loop, sessions, &sessions) { 403 if (s_loop == s) 404 continue; 405 if (s_out == NULL || 406 timercmp(&s_loop->activity_time, &s_out->activity_time, <)) 407 s_out = s_loop; 408 } 409 return (s_out); 410 } 411 412 static struct session * 413 server_next_detached_session(struct session *s) 414 { 415 struct session *s_loop, *s_out = NULL; 416 417 RB_FOREACH(s_loop, sessions, &sessions) { 418 if (s_loop == s || s_loop->attached) 419 continue; 420 if (s_out == NULL || 421 timercmp(&s_loop->activity_time, &s_out->activity_time, <)) 422 s_out = s_loop; 423 } 424 return (s_out); 425 } 426 427 void 428 server_destroy_session(struct session *s) 429 { 430 struct client *c; 431 struct session *s_new; 432 int detach_on_destroy; 433 434 detach_on_destroy = options_get_number(s->options, "detach-on-destroy"); 435 if (detach_on_destroy == 0) 436 s_new = server_next_session(s); 437 else if (detach_on_destroy == 2) 438 s_new = server_next_detached_session(s); 439 else 440 s_new = NULL; 441 TAILQ_FOREACH(c, &clients, entry) { 442 if (c->session != s) 443 continue; 444 server_client_set_session(c, s_new); 445 if (s_new == NULL) 446 c->flags |= CLIENT_EXIT; 447 } 448 recalculate_sizes(); 449 } 450 451 void 452 server_check_unattached(void) 453 { 454 struct session *s; 455 456 /* 457 * If any sessions are no longer attached and have destroy-unattached 458 * set, collect them. 459 */ 460 RB_FOREACH(s, sessions, &sessions) { 461 if (s->attached != 0) 462 continue; 463 if (options_get_number (s->options, "destroy-unattached")) 464 session_destroy(s, 1, __func__); 465 } 466 } 467 468 void 469 server_unzoom_window(struct window *w) 470 { 471 if (window_unzoom(w) == 0) 472 server_redraw_window(w); 473 } 474