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 struct session *server_next_session(struct session *); 31 static void server_destroy_session_group(struct session *); 32 33 void 34 server_redraw_client(struct client *c) 35 { 36 c->flags |= CLIENT_ALLREDRAWFLAGS; 37 } 38 39 void 40 server_status_client(struct client *c) 41 { 42 c->flags |= CLIENT_REDRAWSTATUS; 43 } 44 45 void 46 server_redraw_session(struct session *s) 47 { 48 struct client *c; 49 50 TAILQ_FOREACH(c, &clients, entry) { 51 if (c->session == s) 52 server_redraw_client(c); 53 } 54 } 55 56 void 57 server_redraw_session_group(struct session *s) 58 { 59 struct session_group *sg; 60 61 if ((sg = session_group_contains(s)) == NULL) 62 server_redraw_session(s); 63 else { 64 TAILQ_FOREACH(s, &sg->sessions, gentry) 65 server_redraw_session(s); 66 } 67 } 68 69 void 70 server_status_session(struct session *s) 71 { 72 struct client *c; 73 74 TAILQ_FOREACH(c, &clients, entry) { 75 if (c->session == s) 76 server_status_client(c); 77 } 78 } 79 80 void 81 server_status_session_group(struct session *s) 82 { 83 struct session_group *sg; 84 85 if ((sg = session_group_contains(s)) == NULL) 86 server_status_session(s); 87 else { 88 TAILQ_FOREACH(s, &sg->sessions, gentry) 89 server_status_session(s); 90 } 91 } 92 93 void 94 server_redraw_window(struct window *w) 95 { 96 struct client *c; 97 98 TAILQ_FOREACH(c, &clients, entry) { 99 if (c->session != NULL && c->session->curw->window == w) 100 server_redraw_client(c); 101 } 102 } 103 104 void 105 server_redraw_window_borders(struct window *w) 106 { 107 struct client *c; 108 109 TAILQ_FOREACH(c, &clients, entry) { 110 if (c->session != NULL && c->session->curw->window == w) 111 c->flags |= CLIENT_REDRAWBORDERS; 112 } 113 } 114 115 void 116 server_status_window(struct window *w) 117 { 118 struct session *s; 119 120 /* 121 * This is slightly different. We want to redraw the status line of any 122 * clients containing this window rather than anywhere it is the 123 * current window. 124 */ 125 126 RB_FOREACH(s, sessions, &sessions) { 127 if (session_has(s, w)) 128 server_status_session(s); 129 } 130 } 131 132 void 133 server_lock(void) 134 { 135 struct client *c; 136 137 TAILQ_FOREACH(c, &clients, entry) { 138 if (c->session != NULL) 139 server_lock_client(c); 140 } 141 } 142 143 void 144 server_lock_session(struct session *s) 145 { 146 struct client *c; 147 148 TAILQ_FOREACH(c, &clients, entry) { 149 if (c->session == s) 150 server_lock_client(c); 151 } 152 } 153 154 void 155 server_lock_client(struct client *c) 156 { 157 const char *cmd; 158 159 if (c->flags & CLIENT_CONTROL) 160 return; 161 162 if (c->flags & CLIENT_SUSPENDED) 163 return; 164 165 cmd = options_get_string(c->session->options, "lock-command"); 166 if (*cmd == '\0' || strlen(cmd) + 1 > MAX_IMSGSIZE - IMSG_HEADER_SIZE) 167 return; 168 169 tty_stop_tty(&c->tty); 170 tty_raw(&c->tty, tty_term_string(c->tty.term, TTYC_SMCUP)); 171 tty_raw(&c->tty, tty_term_string(c->tty.term, TTYC_CLEAR)); 172 tty_raw(&c->tty, tty_term_string(c->tty.term, TTYC_E3)); 173 174 c->flags |= CLIENT_SUSPENDED; 175 proc_send(c->peer, MSG_LOCK, -1, cmd, strlen(cmd) + 1); 176 } 177 178 void 179 server_kill_pane(struct window_pane *wp) 180 { 181 struct window *w = wp->window; 182 183 if (window_count_panes(w) == 1) { 184 server_kill_window(w); 185 recalculate_sizes(); 186 } else { 187 server_unzoom_window(w); 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) 196 { 197 struct session *s, *next_s, *target_s; 198 struct session_group *sg; 199 struct winlink *wl; 200 201 next_s = RB_MIN(sessions, &sessions); 202 while (next_s != NULL) { 203 s = next_s; 204 next_s = RB_NEXT(sessions, &sessions, s); 205 206 if (!session_has(s, w)) 207 continue; 208 server_unzoom_window(w); 209 while ((wl = winlink_find_by_window(&s->windows, w)) != NULL) { 210 if (session_detach(s, wl)) { 211 server_destroy_session_group(s); 212 break; 213 } else 214 server_redraw_session_group(s); 215 } 216 217 if (options_get_number(s->options, "renumber-windows")) { 218 if ((sg = session_group_contains(s)) != NULL) { 219 TAILQ_FOREACH(target_s, &sg->sessions, gentry) 220 session_renumber_windows(target_s); 221 } else 222 session_renumber_windows(s); 223 } 224 } 225 recalculate_sizes(); 226 } 227 228 int 229 server_link_window(struct session *src, struct winlink *srcwl, 230 struct session *dst, int dstidx, int killflag, int selectflag, 231 char **cause) 232 { 233 struct winlink *dstwl; 234 struct session_group *srcsg, *dstsg; 235 236 srcsg = session_group_contains(src); 237 dstsg = session_group_contains(dst); 238 if (src != dst && srcsg != NULL && dstsg != NULL && srcsg == dstsg) { 239 xasprintf(cause, "sessions are grouped"); 240 return (-1); 241 } 242 243 dstwl = NULL; 244 if (dstidx != -1) 245 dstwl = winlink_find_by_index(&dst->windows, dstidx); 246 if (dstwl != NULL) { 247 if (dstwl->window == srcwl->window) { 248 xasprintf(cause, "same index: %d", dstidx); 249 return (-1); 250 } 251 if (killflag) { 252 /* 253 * Can't use session_detach as it will destroy session 254 * if this makes it empty. 255 */ 256 notify_session_window("window-unlinked", dst, 257 dstwl->window); 258 dstwl->flags &= ~WINLINK_ALERTFLAGS; 259 winlink_stack_remove(&dst->lastw, dstwl); 260 winlink_remove(&dst->windows, dstwl); 261 262 /* Force select/redraw if current. */ 263 if (dstwl == dst->curw) { 264 selectflag = 1; 265 dst->curw = NULL; 266 } 267 } 268 } 269 270 if (dstidx == -1) 271 dstidx = -1 - options_get_number(dst->options, "base-index"); 272 dstwl = session_attach(dst, srcwl->window, dstidx, cause); 273 if (dstwl == NULL) 274 return (-1); 275 276 if (selectflag) 277 session_select(dst, dstwl->idx); 278 server_redraw_session_group(dst); 279 280 return (0); 281 } 282 283 void 284 server_unlink_window(struct session *s, struct winlink *wl) 285 { 286 if (session_detach(s, wl)) 287 server_destroy_session_group(s); 288 else 289 server_redraw_session_group(s); 290 } 291 292 void 293 server_destroy_pane(struct window_pane *wp, int notify) 294 { 295 struct window *w = wp->window; 296 struct screen_write_ctx ctx; 297 struct grid_cell gc; 298 time_t t; 299 char tim[26]; 300 301 if (wp->fd != -1) { 302 #ifdef HAVE_UTEMPTER 303 utempter_remove_record(wp->fd); 304 #endif 305 bufferevent_free(wp->event); 306 wp->event = NULL; 307 close(wp->fd); 308 wp->fd = -1; 309 } 310 311 if (options_get_number(wp->options, "remain-on-exit")) { 312 if (~wp->flags & PANE_STATUSREADY) 313 return; 314 315 if (wp->flags & PANE_STATUSDRAWN) 316 return; 317 wp->flags |= PANE_STATUSDRAWN; 318 319 if (notify) 320 notify_pane("pane-died", wp); 321 322 screen_write_start(&ctx, wp, &wp->base); 323 screen_write_scrollregion(&ctx, 0, screen_size_y(ctx.s) - 1); 324 screen_write_cursormove(&ctx, 0, screen_size_y(ctx.s) - 1, 0); 325 screen_write_linefeed(&ctx, 1, 8); 326 memcpy(&gc, &grid_default_cell, sizeof gc); 327 328 time(&t); 329 ctime_r(&t, tim); 330 331 if (WIFEXITED(wp->status)) { 332 screen_write_nputs(&ctx, -1, &gc, 333 "Pane is dead (status %d, %s)", 334 WEXITSTATUS(wp->status), 335 tim); 336 } else if (WIFSIGNALED(wp->status)) { 337 screen_write_nputs(&ctx, -1, &gc, 338 "Pane is dead (signal %d, %s)", 339 WTERMSIG(wp->status), 340 tim); 341 } 342 343 screen_write_stop(&ctx); 344 wp->flags |= PANE_REDRAW; 345 return; 346 } 347 348 if (notify) 349 notify_pane("pane-exited", wp); 350 351 server_unzoom_window(w); 352 layout_close_pane(wp); 353 window_remove_pane(w, wp); 354 355 if (TAILQ_EMPTY(&w->panes)) 356 server_kill_window(w); 357 else 358 server_redraw_window(w); 359 } 360 361 static void 362 server_destroy_session_group(struct session *s) 363 { 364 struct session_group *sg; 365 struct session *s1; 366 367 if ((sg = session_group_contains(s)) == NULL) 368 server_destroy_session(s); 369 else { 370 TAILQ_FOREACH_SAFE(s, &sg->sessions, gentry, s1) { 371 server_destroy_session(s); 372 session_destroy(s, 1, __func__); 373 } 374 } 375 } 376 377 static struct session * 378 server_next_session(struct session *s) 379 { 380 struct session *s_loop, *s_out; 381 382 s_out = NULL; 383 RB_FOREACH(s_loop, sessions, &sessions) { 384 if (s_loop == s) 385 continue; 386 if (s_out == NULL || 387 timercmp(&s_loop->activity_time, &s_out->activity_time, <)) 388 s_out = s_loop; 389 } 390 return (s_out); 391 } 392 393 void 394 server_destroy_session(struct session *s) 395 { 396 struct client *c; 397 struct session *s_new; 398 399 if (!options_get_number(s->options, "detach-on-destroy")) 400 s_new = server_next_session(s); 401 else 402 s_new = NULL; 403 404 TAILQ_FOREACH(c, &clients, entry) { 405 if (c->session != s) 406 continue; 407 if (s_new == NULL) { 408 c->session = NULL; 409 c->flags |= CLIENT_EXIT; 410 } else { 411 c->last_session = NULL; 412 c->session = s_new; 413 server_client_set_key_table(c, NULL); 414 tty_update_client_offset(c); 415 status_timer_start(c); 416 notify_client("client-session-changed", c); 417 session_update_activity(s_new, NULL); 418 gettimeofday(&s_new->last_attached_time, NULL); 419 server_redraw_client(c); 420 alerts_check_session(s_new); 421 } 422 } 423 recalculate_sizes(); 424 } 425 426 void 427 server_check_unattached(void) 428 { 429 struct session *s; 430 431 /* 432 * If any sessions are no longer attached and have destroy-unattached 433 * set, collect them. 434 */ 435 RB_FOREACH(s, sessions, &sessions) { 436 if (s->attached != 0) 437 continue; 438 if (options_get_number (s->options, "destroy-unattached")) 439 session_destroy(s, 1, __func__); 440 } 441 } 442 443 void 444 server_unzoom_window(struct window *w) 445 { 446 if (window_unzoom(w) == 0) 447 server_redraw_window(w); 448 } 449