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