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