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/ioctl.h> 21 22 #include <errno.h> 23 #include <fcntl.h> 24 #include <fnmatch.h> 25 #include <signal.h> 26 #include <stdint.h> 27 #include <stdlib.h> 28 #include <string.h> 29 #include <termios.h> 30 #include <time.h> 31 #include <unistd.h> 32 33 #include "tmux.h" 34 35 /* 36 * Each window is attached to a number of panes, each of which is a pty. This 37 * file contains code to handle them. 38 * 39 * A pane has two buffers attached, these are filled and emptied by the main 40 * server poll loop. Output data is received from pty's in screen format, 41 * translated and returned as a series of escape sequences and strings via 42 * input_parse (in input.c). Input data is received as key codes and written 43 * directly via input_key. 44 * 45 * Each pane also has a "virtual" screen (screen.c) which contains the current 46 * state and is redisplayed when the window is reattached to a client. 47 * 48 * Windows are stored directly on a global array and wrapped in any number of 49 * winlink structs to be linked onto local session RB trees. A reference count 50 * is maintained and a window removed from the global list and destroyed when 51 * it reaches zero. 52 */ 53 54 /* Global window list. */ 55 struct windows windows; 56 57 /* Global panes tree. */ 58 struct window_pane_tree all_window_panes; 59 static u_int next_window_pane_id; 60 static u_int next_window_id; 61 static u_int next_active_point; 62 63 static void window_destroy(struct window *); 64 65 static struct window_pane *window_pane_create(struct window *, u_int, u_int, 66 u_int); 67 static void window_pane_destroy(struct window_pane *); 68 69 static void window_pane_read_callback(struct bufferevent *, void *); 70 static void window_pane_error_callback(struct bufferevent *, short, void *); 71 72 static int winlink_next_index(struct winlinks *, int); 73 74 static struct window_pane *window_pane_choose_best(struct window_pane **, 75 u_int); 76 77 RB_GENERATE(windows, window, entry, window_cmp); 78 RB_GENERATE(winlinks, winlink, entry, winlink_cmp); 79 RB_GENERATE(window_pane_tree, window_pane, tree_entry, window_pane_cmp); 80 81 int 82 window_cmp(struct window *w1, struct window *w2) 83 { 84 return (w1->id - w2->id); 85 } 86 87 int 88 winlink_cmp(struct winlink *wl1, struct winlink *wl2) 89 { 90 return (wl1->idx - wl2->idx); 91 } 92 93 int 94 window_pane_cmp(struct window_pane *wp1, struct window_pane *wp2) 95 { 96 return (wp1->id - wp2->id); 97 } 98 99 struct winlink * 100 winlink_find_by_window(struct winlinks *wwl, struct window *w) 101 { 102 struct winlink *wl; 103 104 RB_FOREACH(wl, winlinks, wwl) { 105 if (wl->window == w) 106 return (wl); 107 } 108 109 return (NULL); 110 } 111 112 struct winlink * 113 winlink_find_by_index(struct winlinks *wwl, int idx) 114 { 115 struct winlink wl; 116 117 if (idx < 0) 118 fatalx("bad index"); 119 120 wl.idx = idx; 121 return (RB_FIND(winlinks, wwl, &wl)); 122 } 123 124 struct winlink * 125 winlink_find_by_window_id(struct winlinks *wwl, u_int id) 126 { 127 struct winlink *wl; 128 129 RB_FOREACH(wl, winlinks, wwl) { 130 if (wl->window->id == id) 131 return (wl); 132 } 133 return (NULL); 134 } 135 136 static int 137 winlink_next_index(struct winlinks *wwl, int idx) 138 { 139 int i; 140 141 i = idx; 142 do { 143 if (winlink_find_by_index(wwl, i) == NULL) 144 return (i); 145 if (i == INT_MAX) 146 i = 0; 147 else 148 i++; 149 } while (i != idx); 150 return (-1); 151 } 152 153 u_int 154 winlink_count(struct winlinks *wwl) 155 { 156 struct winlink *wl; 157 u_int n; 158 159 n = 0; 160 RB_FOREACH(wl, winlinks, wwl) 161 n++; 162 163 return (n); 164 } 165 166 struct winlink * 167 winlink_add(struct winlinks *wwl, int idx) 168 { 169 struct winlink *wl; 170 171 if (idx < 0) { 172 if ((idx = winlink_next_index(wwl, -idx - 1)) == -1) 173 return (NULL); 174 } else if (winlink_find_by_index(wwl, idx) != NULL) 175 return (NULL); 176 177 wl = xcalloc(1, sizeof *wl); 178 wl->idx = idx; 179 RB_INSERT(winlinks, wwl, wl); 180 181 return (wl); 182 } 183 184 void 185 winlink_set_window(struct winlink *wl, struct window *w) 186 { 187 if (wl->window != NULL) { 188 TAILQ_REMOVE(&wl->window->winlinks, wl, wentry); 189 window_remove_ref(wl->window, __func__); 190 } 191 TAILQ_INSERT_TAIL(&w->winlinks, wl, wentry); 192 wl->window = w; 193 window_add_ref(w, __func__); 194 } 195 196 void 197 winlink_remove(struct winlinks *wwl, struct winlink *wl) 198 { 199 struct window *w = wl->window; 200 201 if (w != NULL) { 202 TAILQ_REMOVE(&w->winlinks, wl, wentry); 203 window_remove_ref(w, __func__); 204 } 205 206 RB_REMOVE(winlinks, wwl, wl); 207 free(wl->status_text); 208 free(wl); 209 } 210 211 struct winlink * 212 winlink_next(struct winlink *wl) 213 { 214 return (RB_NEXT(winlinks, wwl, wl)); 215 } 216 217 struct winlink * 218 winlink_previous(struct winlink *wl) 219 { 220 return (RB_PREV(winlinks, wwl, wl)); 221 } 222 223 struct winlink * 224 winlink_next_by_number(struct winlink *wl, struct session *s, int n) 225 { 226 for (; n > 0; n--) { 227 if ((wl = RB_NEXT(winlinks, wwl, wl)) == NULL) 228 wl = RB_MIN(winlinks, &s->windows); 229 } 230 231 return (wl); 232 } 233 234 struct winlink * 235 winlink_previous_by_number(struct winlink *wl, struct session *s, int n) 236 { 237 for (; n > 0; n--) { 238 if ((wl = RB_PREV(winlinks, wwl, wl)) == NULL) 239 wl = RB_MAX(winlinks, &s->windows); 240 } 241 242 return (wl); 243 } 244 245 void 246 winlink_stack_push(struct winlink_stack *stack, struct winlink *wl) 247 { 248 if (wl == NULL) 249 return; 250 251 winlink_stack_remove(stack, wl); 252 TAILQ_INSERT_HEAD(stack, wl, sentry); 253 } 254 255 void 256 winlink_stack_remove(struct winlink_stack *stack, struct winlink *wl) 257 { 258 struct winlink *wl2; 259 260 if (wl == NULL) 261 return; 262 263 TAILQ_FOREACH(wl2, stack, sentry) { 264 if (wl2 == wl) { 265 TAILQ_REMOVE(stack, wl, sentry); 266 return; 267 } 268 } 269 } 270 271 struct window * 272 window_find_by_id_str(const char *s) 273 { 274 const char *errstr; 275 u_int id; 276 277 if (*s != '@') 278 return (NULL); 279 280 id = strtonum(s + 1, 0, UINT_MAX, &errstr); 281 if (errstr != NULL) 282 return (NULL); 283 return (window_find_by_id(id)); 284 } 285 286 struct window * 287 window_find_by_id(u_int id) 288 { 289 struct window w; 290 291 w.id = id; 292 return (RB_FIND(windows, &windows, &w)); 293 } 294 295 void 296 window_update_activity(struct window *w) 297 { 298 gettimeofday(&w->activity_time, NULL); 299 alerts_queue(w, WINDOW_ACTIVITY); 300 } 301 302 struct window * 303 window_create(u_int sx, u_int sy) 304 { 305 struct window *w; 306 307 w = xcalloc(1, sizeof *w); 308 w->name = NULL; 309 w->flags = WINDOW_STYLECHANGED; 310 311 TAILQ_INIT(&w->panes); 312 w->active = NULL; 313 314 w->lastlayout = -1; 315 w->layout_root = NULL; 316 317 w->sx = sx; 318 w->sy = sy; 319 320 w->options = options_create(global_w_options); 321 322 w->references = 0; 323 TAILQ_INIT(&w->winlinks); 324 325 w->id = next_window_id++; 326 RB_INSERT(windows, &windows, w); 327 328 window_update_activity(w); 329 330 return (w); 331 } 332 333 struct window * 334 window_create_spawn(const char *name, int argc, char **argv, const char *path, 335 const char *shell, const char *cwd, struct environ *env, 336 struct termios *tio, u_int sx, u_int sy, u_int hlimit, char **cause) 337 { 338 struct window *w; 339 struct window_pane *wp; 340 341 w = window_create(sx, sy); 342 wp = window_add_pane(w, NULL, 0, hlimit); 343 layout_init(w, wp); 344 345 if (window_pane_spawn(wp, argc, argv, path, shell, cwd, 346 env, tio, cause) != 0) { 347 window_destroy(w); 348 return (NULL); 349 } 350 351 w->active = TAILQ_FIRST(&w->panes); 352 if (name != NULL) { 353 w->name = xstrdup(name); 354 options_set_number(w->options, "automatic-rename", 0); 355 } else 356 w->name = default_window_name(w); 357 358 notify_window("window-pane-changed", w); 359 360 return (w); 361 } 362 363 static void 364 window_destroy(struct window *w) 365 { 366 log_debug("window @%u destroyed (%d references)", w->id, w->references); 367 368 RB_REMOVE(windows, &windows, w); 369 370 if (w->layout_root != NULL) 371 layout_free_cell(w->layout_root); 372 if (w->saved_layout_root != NULL) 373 layout_free_cell(w->saved_layout_root); 374 free(w->old_layout); 375 376 if (event_initialized(&w->name_event)) 377 evtimer_del(&w->name_event); 378 379 if (event_initialized(&w->alerts_timer)) 380 evtimer_del(&w->alerts_timer); 381 382 options_free(w->options); 383 384 window_destroy_panes(w); 385 386 free(w->name); 387 free(w); 388 } 389 390 int 391 window_pane_destroy_ready(struct window_pane *wp) 392 { 393 int n; 394 395 if (wp->pipe_fd != -1) { 396 if (EVBUFFER_LENGTH(wp->pipe_event->output) != 0) 397 return (0); 398 if (ioctl(wp->fd, FIONREAD, &n) != -1 && n > 0) 399 return (0); 400 } 401 402 if (~wp->flags & PANE_EXITED) 403 return (0); 404 return (1); 405 } 406 407 void 408 window_add_ref(struct window *w, const char *from) 409 { 410 w->references++; 411 log_debug("%s: @%u %s, now %d", __func__, w->id, from, w->references); 412 } 413 414 void 415 window_remove_ref(struct window *w, const char *from) 416 { 417 w->references--; 418 log_debug("%s: @%u %s, now %d", __func__, w->id, from, w->references); 419 420 if (w->references == 0) 421 window_destroy(w); 422 } 423 424 void 425 window_set_name(struct window *w, const char *new_name) 426 { 427 free(w->name); 428 utf8_stravis(&w->name, new_name, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL); 429 notify_window("window-renamed", w); 430 } 431 432 void 433 window_resize(struct window *w, u_int sx, u_int sy) 434 { 435 w->sx = sx; 436 w->sy = sy; 437 } 438 439 int 440 window_has_pane(struct window *w, struct window_pane *wp) 441 { 442 struct window_pane *wp1; 443 444 TAILQ_FOREACH(wp1, &w->panes, entry) { 445 if (wp1 == wp) 446 return (1); 447 } 448 return (0); 449 } 450 451 int 452 window_set_active_pane(struct window *w, struct window_pane *wp) 453 { 454 log_debug("%s: pane %%%u (was %%%u)", __func__, wp->id, w->active->id); 455 if (wp == w->active) 456 return (0); 457 w->last = w->active; 458 w->active = wp; 459 while (!window_pane_visible(w->active)) { 460 w->active = TAILQ_PREV(w->active, window_panes, entry); 461 if (w->active == NULL) 462 w->active = TAILQ_LAST(&w->panes, window_panes); 463 if (w->active == wp) { 464 notify_window("window-pane-changed", w); 465 return (1); 466 } 467 } 468 w->active->active_point = next_active_point++; 469 w->active->flags |= PANE_CHANGED; 470 notify_window("window-pane-changed", w); 471 return (1); 472 } 473 474 void 475 window_redraw_active_switch(struct window *w, struct window_pane *wp) 476 { 477 const struct grid_cell *gc; 478 479 if (wp == w->active) 480 return; 481 482 /* 483 * If window-style and window-active-style are the same, we don't need 484 * to redraw panes when switching active panes. 485 */ 486 gc = options_get_style(w->options, "window-active-style"); 487 if (style_equal(gc, options_get_style(w->options, "window-style"))) 488 return; 489 490 /* 491 * If the now active or inactive pane do not have a custom style or if 492 * the palette is different, they need to be redrawn. 493 */ 494 if (window_pane_get_palette(w->active, w->active->colgc.fg) != -1 || 495 window_pane_get_palette(w->active, w->active->colgc.bg) != -1 || 496 style_equal(&grid_default_cell, &w->active->colgc)) 497 w->active->flags |= PANE_REDRAW; 498 if (window_pane_get_palette(wp, wp->colgc.fg) != -1 || 499 window_pane_get_palette(wp, wp->colgc.bg) != -1 || 500 style_equal(&grid_default_cell, &wp->colgc)) 501 wp->flags |= PANE_REDRAW; 502 } 503 504 struct window_pane * 505 window_get_active_at(struct window *w, u_int x, u_int y) 506 { 507 struct window_pane *wp; 508 509 TAILQ_FOREACH(wp, &w->panes, entry) { 510 if (!window_pane_visible(wp)) 511 continue; 512 if (x < wp->xoff || x > wp->xoff + wp->sx) 513 continue; 514 if (y < wp->yoff || y > wp->yoff + wp->sy) 515 continue; 516 return (wp); 517 } 518 return (NULL); 519 } 520 521 struct window_pane * 522 window_find_string(struct window *w, const char *s) 523 { 524 u_int x, y; 525 526 x = w->sx / 2; 527 y = w->sy / 2; 528 529 if (strcasecmp(s, "top") == 0) 530 y = 0; 531 else if (strcasecmp(s, "bottom") == 0) 532 y = w->sy - 1; 533 else if (strcasecmp(s, "left") == 0) 534 x = 0; 535 else if (strcasecmp(s, "right") == 0) 536 x = w->sx - 1; 537 else if (strcasecmp(s, "top-left") == 0) { 538 x = 0; 539 y = 0; 540 } else if (strcasecmp(s, "top-right") == 0) { 541 x = w->sx - 1; 542 y = 0; 543 } else if (strcasecmp(s, "bottom-left") == 0) { 544 x = 0; 545 y = w->sy - 1; 546 } else if (strcasecmp(s, "bottom-right") == 0) { 547 x = w->sx - 1; 548 y = w->sy - 1; 549 } else 550 return (NULL); 551 552 return (window_get_active_at(w, x, y)); 553 } 554 555 int 556 window_zoom(struct window_pane *wp) 557 { 558 struct window *w = wp->window; 559 struct window_pane *wp1; 560 561 if (w->flags & WINDOW_ZOOMED) 562 return (-1); 563 564 if (!window_pane_visible(wp)) 565 return (-1); 566 567 if (window_count_panes(w) == 1) 568 return (-1); 569 570 if (w->active != wp) 571 window_set_active_pane(w, wp); 572 573 TAILQ_FOREACH(wp1, &w->panes, entry) { 574 wp1->saved_layout_cell = wp1->layout_cell; 575 wp1->layout_cell = NULL; 576 } 577 578 w->saved_layout_root = w->layout_root; 579 layout_init(w, wp); 580 w->flags |= WINDOW_ZOOMED; 581 notify_window("window-layout-changed", w); 582 583 return (0); 584 } 585 586 int 587 window_unzoom(struct window *w) 588 { 589 struct window_pane *wp; 590 591 if (!(w->flags & WINDOW_ZOOMED)) 592 return (-1); 593 594 w->flags &= ~WINDOW_ZOOMED; 595 layout_free(w); 596 w->layout_root = w->saved_layout_root; 597 w->saved_layout_root = NULL; 598 599 TAILQ_FOREACH(wp, &w->panes, entry) { 600 wp->layout_cell = wp->saved_layout_cell; 601 wp->saved_layout_cell = NULL; 602 } 603 layout_fix_panes(w, w->sx, w->sy); 604 notify_window("window-layout-changed", w); 605 606 return (0); 607 } 608 609 struct window_pane * 610 window_add_pane(struct window *w, struct window_pane *other, int before, 611 u_int hlimit) 612 { 613 struct window_pane *wp; 614 615 if (other == NULL) 616 other = w->active; 617 618 wp = window_pane_create(w, w->sx, w->sy, hlimit); 619 if (TAILQ_EMPTY(&w->panes)) { 620 log_debug("%s: @%u at start", __func__, w->id); 621 TAILQ_INSERT_HEAD(&w->panes, wp, entry); 622 } else if (before) { 623 log_debug("%s: @%u before %%%u", __func__, w->id, wp->id); 624 TAILQ_INSERT_BEFORE(other, wp, entry); 625 } else { 626 log_debug("%s: @%u after %%%u", __func__, w->id, wp->id); 627 TAILQ_INSERT_AFTER(&w->panes, other, wp, entry); 628 } 629 return (wp); 630 } 631 632 void 633 window_lost_pane(struct window *w, struct window_pane *wp) 634 { 635 log_debug("%s: @%u pane %%%u", __func__, w->id, wp->id); 636 637 if (wp == marked_pane.wp) 638 server_clear_marked(); 639 640 if (wp == w->active) { 641 w->active = w->last; 642 w->last = NULL; 643 if (w->active == NULL) { 644 w->active = TAILQ_PREV(wp, window_panes, entry); 645 if (w->active == NULL) 646 w->active = TAILQ_NEXT(wp, entry); 647 } 648 if (w->active != NULL) { 649 w->active->flags |= PANE_CHANGED; 650 notify_window("window-pane-changed", w); 651 } 652 } else if (wp == w->last) 653 w->last = NULL; 654 } 655 656 void 657 window_remove_pane(struct window *w, struct window_pane *wp) 658 { 659 window_lost_pane(w, wp); 660 661 TAILQ_REMOVE(&w->panes, wp, entry); 662 window_pane_destroy(wp); 663 } 664 665 struct window_pane * 666 window_pane_at_index(struct window *w, u_int idx) 667 { 668 struct window_pane *wp; 669 u_int n; 670 671 n = options_get_number(w->options, "pane-base-index"); 672 TAILQ_FOREACH(wp, &w->panes, entry) { 673 if (n == idx) 674 return (wp); 675 n++; 676 } 677 return (NULL); 678 } 679 680 struct window_pane * 681 window_pane_next_by_number(struct window *w, struct window_pane *wp, u_int n) 682 { 683 for (; n > 0; n--) { 684 if ((wp = TAILQ_NEXT(wp, entry)) == NULL) 685 wp = TAILQ_FIRST(&w->panes); 686 } 687 688 return (wp); 689 } 690 691 struct window_pane * 692 window_pane_previous_by_number(struct window *w, struct window_pane *wp, 693 u_int n) 694 { 695 for (; n > 0; n--) { 696 if ((wp = TAILQ_PREV(wp, window_panes, entry)) == NULL) 697 wp = TAILQ_LAST(&w->panes, window_panes); 698 } 699 700 return (wp); 701 } 702 703 int 704 window_pane_index(struct window_pane *wp, u_int *i) 705 { 706 struct window_pane *wq; 707 struct window *w = wp->window; 708 709 *i = options_get_number(w->options, "pane-base-index"); 710 TAILQ_FOREACH(wq, &w->panes, entry) { 711 if (wp == wq) { 712 return (0); 713 } 714 (*i)++; 715 } 716 717 return (-1); 718 } 719 720 u_int 721 window_count_panes(struct window *w) 722 { 723 struct window_pane *wp; 724 u_int n; 725 726 n = 0; 727 TAILQ_FOREACH(wp, &w->panes, entry) 728 n++; 729 return (n); 730 } 731 732 void 733 window_destroy_panes(struct window *w) 734 { 735 struct window_pane *wp; 736 737 while (!TAILQ_EMPTY(&w->panes)) { 738 wp = TAILQ_FIRST(&w->panes); 739 TAILQ_REMOVE(&w->panes, wp, entry); 740 window_pane_destroy(wp); 741 } 742 } 743 744 const char * 745 window_printable_flags(struct winlink *wl) 746 { 747 struct session *s = wl->session; 748 static char flags[32]; 749 int pos; 750 751 pos = 0; 752 if (wl->flags & WINLINK_ACTIVITY) 753 flags[pos++] = '#'; 754 if (wl->flags & WINLINK_BELL) 755 flags[pos++] = '!'; 756 if (wl->flags & WINLINK_SILENCE) 757 flags[pos++] = '~'; 758 if (wl == s->curw) 759 flags[pos++] = '*'; 760 if (wl == TAILQ_FIRST(&s->lastw)) 761 flags[pos++] = '-'; 762 if (server_check_marked() && wl == marked_pane.wl) 763 flags[pos++] = 'M'; 764 if (wl->window->flags & WINDOW_ZOOMED) 765 flags[pos++] = 'Z'; 766 flags[pos] = '\0'; 767 return (flags); 768 } 769 770 struct window_pane * 771 window_pane_find_by_id_str(const char *s) 772 { 773 const char *errstr; 774 u_int id; 775 776 if (*s != '%') 777 return (NULL); 778 779 id = strtonum(s + 1, 0, UINT_MAX, &errstr); 780 if (errstr != NULL) 781 return (NULL); 782 return (window_pane_find_by_id(id)); 783 } 784 785 struct window_pane * 786 window_pane_find_by_id(u_int id) 787 { 788 struct window_pane wp; 789 790 wp.id = id; 791 return (RB_FIND(window_pane_tree, &all_window_panes, &wp)); 792 } 793 794 static struct window_pane * 795 window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit) 796 { 797 struct window_pane *wp; 798 char host[HOST_NAME_MAX + 1]; 799 800 wp = xcalloc(1, sizeof *wp); 801 wp->window = w; 802 803 wp->id = next_window_pane_id++; 804 RB_INSERT(window_pane_tree, &all_window_panes, wp); 805 806 wp->argc = 0; 807 wp->argv = NULL; 808 wp->shell = NULL; 809 wp->cwd = NULL; 810 811 wp->fd = -1; 812 wp->event = NULL; 813 814 wp->mode = NULL; 815 wp->modeprefix = 1; 816 817 wp->layout_cell = NULL; 818 819 wp->xoff = 0; 820 wp->yoff = 0; 821 822 wp->sx = wp->osx = sx; 823 wp->sy = wp->osx = sy; 824 825 wp->pipe_fd = -1; 826 wp->pipe_off = 0; 827 wp->pipe_event = NULL; 828 829 wp->saved_grid = NULL; 830 831 memcpy(&wp->colgc, &grid_default_cell, sizeof wp->colgc); 832 833 screen_init(&wp->base, sx, sy, hlimit); 834 wp->screen = &wp->base; 835 836 screen_init(&wp->status_screen, 1, 1, 0); 837 838 if (gethostname(host, sizeof host) == 0) 839 screen_set_title(&wp->base, host); 840 841 input_init(wp); 842 843 return (wp); 844 } 845 846 static void 847 window_pane_destroy(struct window_pane *wp) 848 { 849 window_pane_reset_mode(wp); 850 free(wp->searchstr); 851 852 if (wp->fd != -1) { 853 #ifdef HAVE_UTEMPTER 854 utempter_remove_record(wp->fd); 855 #endif 856 bufferevent_free(wp->event); 857 close(wp->fd); 858 } 859 860 input_free(wp); 861 862 screen_free(&wp->base); 863 if (wp->saved_grid != NULL) 864 grid_destroy(wp->saved_grid); 865 866 if (wp->pipe_fd != -1) { 867 bufferevent_free(wp->pipe_event); 868 close(wp->pipe_fd); 869 } 870 871 if (event_initialized(&wp->resize_timer)) 872 event_del(&wp->resize_timer); 873 874 RB_REMOVE(window_pane_tree, &all_window_panes, wp); 875 876 free(__UNCONST(wp->cwd)); 877 free(wp->shell); 878 cmd_free_argv(wp->argc, wp->argv); 879 free(wp->palette); 880 free(wp); 881 } 882 883 int 884 window_pane_spawn(struct window_pane *wp, int argc, char **argv, 885 const char *path, const char *shell, const char *cwd, struct environ *env, 886 struct termios *tio, char **cause) 887 { 888 struct winsize ws; 889 char *argv0, *cmd, **argvp; 890 const char *ptr, *first, *home; 891 struct termios tio2; 892 #ifdef HAVE_UTEMPTER 893 char s[32]; 894 #endif 895 int i; 896 sigset_t set, oldset; 897 898 if (wp->fd != -1) { 899 bufferevent_free(wp->event); 900 close(wp->fd); 901 } 902 if (argc > 0) { 903 cmd_free_argv(wp->argc, wp->argv); 904 wp->argc = argc; 905 wp->argv = cmd_copy_argv(argc, argv); 906 } 907 if (shell != NULL) { 908 free(wp->shell); 909 wp->shell = xstrdup(shell); 910 } 911 if (cwd != NULL) { 912 free(__UNCONST(wp->cwd)); 913 wp->cwd = xstrdup(cwd); 914 } 915 916 cmd = cmd_stringify_argv(wp->argc, wp->argv); 917 log_debug("spawn: %s -- %s", wp->shell, cmd); 918 for (i = 0; i < wp->argc; i++) 919 log_debug("spawn: argv[%d] = %s", i, wp->argv[i]); 920 environ_log(env, "spawn: "); 921 922 memset(&ws, 0, sizeof ws); 923 ws.ws_col = screen_size_x(&wp->base); 924 ws.ws_row = screen_size_y(&wp->base); 925 926 sigfillset(&set); 927 sigprocmask(SIG_BLOCK, &set, &oldset); 928 switch (wp->pid = fdforkpty(ptm_fd, &wp->fd, wp->tty, NULL, &ws)) { 929 case -1: 930 wp->fd = -1; 931 932 xasprintf(cause, "%s: %s", cmd, strerror(errno)); 933 free(cmd); 934 935 sigprocmask(SIG_SETMASK, &oldset, NULL); 936 return (-1); 937 case 0: 938 proc_clear_signals(server_proc, 1); 939 sigprocmask(SIG_SETMASK, &oldset, NULL); 940 941 if (chdir(wp->cwd) != 0) { 942 if ((home = find_home()) == NULL || chdir(home) != 0) 943 chdir("/"); 944 } 945 946 if (tcgetattr(STDIN_FILENO, &tio2) != 0) 947 fatal("tcgetattr failed"); 948 if (tio != NULL) 949 memcpy(tio2.c_cc, tio->c_cc, sizeof tio2.c_cc); 950 tio2.c_cc[VERASE] = '\177'; 951 #ifdef IUTF8 952 tio2.c_iflag |= IUTF8; 953 #endif 954 if (tcsetattr(STDIN_FILENO, TCSANOW, &tio2) != 0) 955 fatal("tcgetattr failed"); 956 957 log_close(); 958 closefrom(STDERR_FILENO + 1); 959 960 if (path != NULL) 961 environ_set(env, "PATH", "%s", path); 962 environ_set(env, "TMUX_PANE", "%%%u", wp->id); 963 environ_push(env); 964 965 setenv("SHELL", wp->shell, 1); 966 ptr = strrchr(wp->shell, '/'); 967 968 /* 969 * If given one argument, assume it should be passed to sh -c; 970 * with more than one argument, use execvp(). If there is no 971 * arguments, create a login shell. 972 */ 973 if (wp->argc > 0) { 974 if (wp->argc != 1) { 975 /* Copy to ensure argv ends in NULL. */ 976 argvp = cmd_copy_argv(wp->argc, wp->argv); 977 execvp(argvp[0], argvp); 978 fatal("execvp failed"); 979 } 980 first = wp->argv[0]; 981 982 if (ptr != NULL && *(ptr + 1) != '\0') 983 xasprintf(&argv0, "%s", ptr + 1); 984 else 985 xasprintf(&argv0, "%s", wp->shell); 986 execl(wp->shell, argv0, "-c", first, (char *)NULL); 987 fatal("execl failed"); 988 } 989 if (ptr != NULL && *(ptr + 1) != '\0') 990 xasprintf(&argv0, "-%s", ptr + 1); 991 else 992 xasprintf(&argv0, "-%s", wp->shell); 993 execl(wp->shell, argv0, (char *)NULL); 994 fatal("execl failed"); 995 } 996 997 #ifdef HAVE_UTEMPTER 998 xsnprintf(s, sizeof s, "tmux(%lu).%%%u", (long) getpid(), wp->id); 999 utempter_add_record(wp->fd, s); 1000 kill(getpid(), SIGCHLD); 1001 #endif 1002 1003 sigprocmask(SIG_SETMASK, &oldset, NULL); 1004 setblocking(wp->fd, 0); 1005 1006 wp->event = bufferevent_new(wp->fd, window_pane_read_callback, NULL, 1007 window_pane_error_callback, wp); 1008 1009 bufferevent_setwatermark(wp->event, EV_READ, 0, READ_SIZE); 1010 bufferevent_enable(wp->event, EV_READ|EV_WRITE); 1011 1012 free(cmd); 1013 return (0); 1014 } 1015 1016 static void 1017 window_pane_read_callback(__unused struct bufferevent *bufev, void *data) 1018 { 1019 struct window_pane *wp = data; 1020 struct evbuffer *evb = wp->event->input; 1021 size_t size = EVBUFFER_LENGTH(evb); 1022 char *new_data; 1023 size_t new_size; 1024 1025 1026 new_size = size - wp->pipe_off; 1027 if (wp->pipe_fd != -1 && new_size > 0) { 1028 new_data = (char *)EVBUFFER_DATA(evb) + wp->pipe_off; 1029 bufferevent_write(wp->pipe_event, new_data, new_size); 1030 } 1031 1032 log_debug("%%%u has %zu bytes", wp->id, size); 1033 input_parse(wp); 1034 1035 wp->pipe_off = EVBUFFER_LENGTH(evb); 1036 } 1037 1038 static void 1039 window_pane_error_callback(__unused struct bufferevent *bufev, 1040 __unused short what, void *data) 1041 { 1042 struct window_pane *wp = data; 1043 1044 log_debug("%%%u error", wp->id); 1045 wp->flags |= PANE_EXITED; 1046 1047 if (window_pane_destroy_ready(wp)) 1048 server_destroy_pane(wp, 1); 1049 } 1050 1051 void 1052 window_pane_resize(struct window_pane *wp, u_int sx, u_int sy) 1053 { 1054 if (sx == wp->sx && sy == wp->sy) 1055 return; 1056 wp->sx = sx; 1057 wp->sy = sy; 1058 1059 screen_resize(&wp->base, sx, sy, wp->saved_grid == NULL); 1060 if (wp->mode != NULL) 1061 wp->mode->resize(wp, sx, sy); 1062 1063 wp->flags |= PANE_RESIZE; 1064 } 1065 1066 /* 1067 * Enter alternative screen mode. A copy of the visible screen is saved and the 1068 * history is not updated 1069 */ 1070 void 1071 window_pane_alternate_on(struct window_pane *wp, struct grid_cell *gc, 1072 int cursor) 1073 { 1074 struct screen *s = &wp->base; 1075 u_int sx, sy; 1076 1077 if (wp->saved_grid != NULL) 1078 return; 1079 if (!options_get_number(wp->window->options, "alternate-screen")) 1080 return; 1081 sx = screen_size_x(s); 1082 sy = screen_size_y(s); 1083 1084 wp->saved_grid = grid_create(sx, sy, 0); 1085 grid_duplicate_lines(wp->saved_grid, 0, s->grid, screen_hsize(s), sy); 1086 if (cursor) { 1087 wp->saved_cx = s->cx; 1088 wp->saved_cy = s->cy; 1089 } 1090 memcpy(&wp->saved_cell, gc, sizeof wp->saved_cell); 1091 1092 grid_view_clear(s->grid, 0, 0, sx, sy, 8); 1093 1094 wp->base.grid->flags &= ~GRID_HISTORY; 1095 1096 wp->flags |= PANE_REDRAW; 1097 } 1098 1099 /* Exit alternate screen mode and restore the copied grid. */ 1100 void 1101 window_pane_alternate_off(struct window_pane *wp, struct grid_cell *gc, 1102 int cursor) 1103 { 1104 struct screen *s = &wp->base; 1105 u_int sx, sy; 1106 1107 if (wp->saved_grid == NULL) 1108 return; 1109 if (!options_get_number(wp->window->options, "alternate-screen")) 1110 return; 1111 sx = screen_size_x(s); 1112 sy = screen_size_y(s); 1113 1114 /* 1115 * If the current size is bigger, temporarily resize to the old size 1116 * before copying back. 1117 */ 1118 if (sy > wp->saved_grid->sy) 1119 screen_resize(s, sx, wp->saved_grid->sy, 1); 1120 1121 /* Restore the grid, cursor position and cell. */ 1122 grid_duplicate_lines(s->grid, screen_hsize(s), wp->saved_grid, 0, sy); 1123 if (cursor) 1124 s->cx = wp->saved_cx; 1125 if (s->cx > screen_size_x(s) - 1) 1126 s->cx = screen_size_x(s) - 1; 1127 if (cursor) 1128 s->cy = wp->saved_cy; 1129 if (s->cy > screen_size_y(s) - 1) 1130 s->cy = screen_size_y(s) - 1; 1131 memcpy(gc, &wp->saved_cell, sizeof *gc); 1132 1133 /* 1134 * Turn history back on (so resize can use it) and then resize back to 1135 * the current size. 1136 */ 1137 wp->base.grid->flags |= GRID_HISTORY; 1138 if (sy > wp->saved_grid->sy || sx != wp->saved_grid->sx) 1139 screen_resize(s, sx, sy, 1); 1140 1141 grid_destroy(wp->saved_grid); 1142 wp->saved_grid = NULL; 1143 1144 wp->flags |= PANE_REDRAW; 1145 } 1146 1147 void 1148 window_pane_set_palette(struct window_pane *wp, u_int n, int colour) 1149 { 1150 if (n > 0xff) 1151 return; 1152 1153 if (wp->palette == NULL) 1154 wp->palette = xcalloc(0x100, sizeof *wp->palette); 1155 1156 wp->palette[n] = colour; 1157 wp->flags |= PANE_REDRAW; 1158 } 1159 1160 void 1161 window_pane_unset_palette(struct window_pane *wp, u_int n) 1162 { 1163 if (n > 0xff || wp->palette == NULL) 1164 return; 1165 1166 wp->palette[n] = 0; 1167 wp->flags |= PANE_REDRAW; 1168 } 1169 1170 void 1171 window_pane_reset_palette(struct window_pane *wp) 1172 { 1173 if (wp->palette == NULL) 1174 return; 1175 1176 free(wp->palette); 1177 wp->palette = NULL; 1178 wp->flags |= PANE_REDRAW; 1179 } 1180 1181 int 1182 window_pane_get_palette(const struct window_pane *wp, int c) 1183 { 1184 int new; 1185 1186 if (wp == NULL || wp->palette == NULL) 1187 return (-1); 1188 1189 new = -1; 1190 if (c < 8) 1191 new = wp->palette[c]; 1192 else if (c >= 90 && c <= 97) 1193 new = wp->palette[8 + c - 90]; 1194 else if (c & COLOUR_FLAG_256) 1195 new = wp->palette[c & ~COLOUR_FLAG_256]; 1196 if (new == 0) 1197 return (-1); 1198 return (new); 1199 } 1200 1201 static void 1202 window_pane_mode_timer(__unused int fd, __unused short events, void *arg) 1203 { 1204 struct window_pane *wp = arg; 1205 struct timeval tv = { .tv_sec = 10 }; 1206 int n = 0; 1207 1208 evtimer_del(&wp->modetimer); 1209 evtimer_add(&wp->modetimer, &tv); 1210 1211 log_debug("%%%u in mode: last=%ld", wp->id, (long)wp->modelast); 1212 1213 if (wp->modelast < time(NULL) - WINDOW_MODE_TIMEOUT) { 1214 if (ioctl(wp->fd, FIONREAD, &n) == -1 || n > 0) 1215 window_pane_reset_mode(wp); 1216 } 1217 } 1218 1219 int 1220 window_pane_set_mode(struct window_pane *wp, const struct window_mode *mode, 1221 struct cmd_find_state *fs, struct args *args) 1222 { 1223 struct screen *s; 1224 struct timeval tv = { .tv_sec = 10 }; 1225 1226 if (wp->mode != NULL) 1227 return (1); 1228 wp->mode = mode; 1229 1230 wp->modelast = time(NULL); 1231 evtimer_set(&wp->modetimer, window_pane_mode_timer, wp); 1232 evtimer_add(&wp->modetimer, &tv); 1233 1234 if ((s = wp->mode->init(wp, fs, args)) != NULL) 1235 wp->screen = s; 1236 wp->flags |= (PANE_REDRAW|PANE_CHANGED); 1237 1238 server_status_window(wp->window); 1239 notify_pane("pane-mode-changed", wp); 1240 return (0); 1241 } 1242 1243 void 1244 window_pane_reset_mode(struct window_pane *wp) 1245 { 1246 if (wp->mode == NULL) 1247 return; 1248 1249 evtimer_del(&wp->modetimer); 1250 1251 wp->mode->free(wp); 1252 wp->mode = NULL; 1253 wp->modeprefix = 1; 1254 1255 wp->screen = &wp->base; 1256 wp->flags |= (PANE_REDRAW|PANE_CHANGED); 1257 1258 server_status_window(wp->window); 1259 notify_pane("pane-mode-changed", wp); 1260 } 1261 1262 void 1263 window_pane_key(struct window_pane *wp, struct client *c, struct session *s, 1264 key_code key, struct mouse_event *m) 1265 { 1266 struct window_pane *wp2; 1267 1268 if (KEYC_IS_MOUSE(key) && m == NULL) 1269 return; 1270 1271 if (wp->mode != NULL) { 1272 wp->modelast = time(NULL); 1273 if (wp->mode->key != NULL) 1274 wp->mode->key(wp, c, s, (key & ~KEYC_XTERM), m); 1275 return; 1276 } 1277 1278 if (wp->fd == -1 || wp->flags & PANE_INPUTOFF) 1279 return; 1280 1281 input_key(wp, key, m); 1282 1283 if (KEYC_IS_MOUSE(key)) 1284 return; 1285 if (options_get_number(wp->window->options, "synchronize-panes")) { 1286 TAILQ_FOREACH(wp2, &wp->window->panes, entry) { 1287 if (wp2 == wp || wp2->mode != NULL) 1288 continue; 1289 if (wp2->fd == -1 || wp2->flags & PANE_INPUTOFF) 1290 continue; 1291 if (window_pane_visible(wp2)) 1292 input_key(wp2, key, NULL); 1293 } 1294 } 1295 } 1296 1297 int 1298 window_pane_visible(struct window_pane *wp) 1299 { 1300 struct window *w = wp->window; 1301 1302 if (wp->layout_cell == NULL) 1303 return (0); 1304 1305 if (wp->xoff >= w->sx || wp->yoff >= w->sy) 1306 return (0); 1307 if (wp->xoff + wp->sx > w->sx || wp->yoff + wp->sy > w->sy) 1308 return (0); 1309 return (1); 1310 } 1311 1312 u_int 1313 window_pane_search(struct window_pane *wp, const char *searchstr) 1314 { 1315 struct screen *s = &wp->base; 1316 char *newsearchstr, *line; 1317 u_int i; 1318 1319 xasprintf(&newsearchstr, "*%s*", searchstr); 1320 1321 for (i = 0; i < screen_size_y(s); i++) { 1322 line = grid_view_string_cells(s->grid, 0, i, screen_size_x(s)); 1323 if (fnmatch(newsearchstr, line, 0) == 0) { 1324 free(line); 1325 break; 1326 } 1327 free(line); 1328 } 1329 1330 free(newsearchstr); 1331 if (i == screen_size_y(s)) 1332 return (0); 1333 return (i + 1); 1334 } 1335 1336 /* Get MRU pane from a list. */ 1337 static struct window_pane * 1338 window_pane_choose_best(struct window_pane **list, u_int size) 1339 { 1340 struct window_pane *next, *best; 1341 u_int i; 1342 1343 if (size == 0) 1344 return (NULL); 1345 1346 best = list[0]; 1347 for (i = 1; i < size; i++) { 1348 next = list[i]; 1349 if (next->active_point > best->active_point) 1350 best = next; 1351 } 1352 return (best); 1353 } 1354 1355 /* 1356 * Find the pane directly above another. We build a list of those adjacent to 1357 * top edge and then choose the best. 1358 */ 1359 struct window_pane * 1360 window_pane_find_up(struct window_pane *wp) 1361 { 1362 struct window_pane *next, *best, **list; 1363 u_int edge, left, right, end, size; 1364 int status, found; 1365 1366 if (wp == NULL || !window_pane_visible(wp)) 1367 return (NULL); 1368 status = options_get_number(wp->window->options, "pane-border-status"); 1369 1370 list = NULL; 1371 size = 0; 1372 1373 edge = wp->yoff; 1374 if (edge == (status == 1 ? 1 : 0)) 1375 edge = wp->window->sy + 1 - (status == 2 ? 1 : 0); 1376 1377 left = wp->xoff; 1378 right = wp->xoff + wp->sx; 1379 1380 TAILQ_FOREACH(next, &wp->window->panes, entry) { 1381 if (next == wp || !window_pane_visible(next)) 1382 continue; 1383 if (next->yoff + next->sy + 1 != edge) 1384 continue; 1385 end = next->xoff + next->sx - 1; 1386 1387 found = 0; 1388 if (next->xoff < left && end > right) 1389 found = 1; 1390 else if (next->xoff >= left && next->xoff <= right) 1391 found = 1; 1392 else if (end >= left && end <= right) 1393 found = 1; 1394 if (!found) 1395 continue; 1396 list = xreallocarray(list, size + 1, sizeof *list); 1397 list[size++] = next; 1398 } 1399 1400 best = window_pane_choose_best(list, size); 1401 free(list); 1402 return (best); 1403 } 1404 1405 /* Find the pane directly below another. */ 1406 struct window_pane * 1407 window_pane_find_down(struct window_pane *wp) 1408 { 1409 struct window_pane *next, *best, **list; 1410 u_int edge, left, right, end, size; 1411 int status, found; 1412 1413 if (wp == NULL || !window_pane_visible(wp)) 1414 return (NULL); 1415 status = options_get_number(wp->window->options, "pane-border-status"); 1416 1417 list = NULL; 1418 size = 0; 1419 1420 edge = wp->yoff + wp->sy + 1; 1421 if (edge >= wp->window->sy - (status == 2 ? 1 : 0)) 1422 edge = (status == 1 ? 1 : 0); 1423 1424 left = wp->xoff; 1425 right = wp->xoff + wp->sx; 1426 1427 TAILQ_FOREACH(next, &wp->window->panes, entry) { 1428 if (next == wp || !window_pane_visible(next)) 1429 continue; 1430 if (next->yoff != edge) 1431 continue; 1432 end = next->xoff + next->sx - 1; 1433 1434 found = 0; 1435 if (next->xoff < left && end > right) 1436 found = 1; 1437 else if (next->xoff >= left && next->xoff <= right) 1438 found = 1; 1439 else if (end >= left && end <= right) 1440 found = 1; 1441 if (!found) 1442 continue; 1443 list = xreallocarray(list, size + 1, sizeof *list); 1444 list[size++] = next; 1445 } 1446 1447 best = window_pane_choose_best(list, size); 1448 free(list); 1449 return (best); 1450 } 1451 1452 /* Find the pane directly to the left of another. */ 1453 struct window_pane * 1454 window_pane_find_left(struct window_pane *wp) 1455 { 1456 struct window_pane *next, *best, **list; 1457 u_int edge, top, bottom, end, size; 1458 int found; 1459 1460 if (wp == NULL || !window_pane_visible(wp)) 1461 return (NULL); 1462 1463 list = NULL; 1464 size = 0; 1465 1466 edge = wp->xoff; 1467 if (edge == 0) 1468 edge = wp->window->sx + 1; 1469 1470 top = wp->yoff; 1471 bottom = wp->yoff + wp->sy; 1472 1473 TAILQ_FOREACH(next, &wp->window->panes, entry) { 1474 if (next == wp || !window_pane_visible(next)) 1475 continue; 1476 if (next->xoff + next->sx + 1 != edge) 1477 continue; 1478 end = next->yoff + next->sy - 1; 1479 1480 found = 0; 1481 if (next->yoff < top && end > bottom) 1482 found = 1; 1483 else if (next->yoff >= top && next->yoff <= bottom) 1484 found = 1; 1485 else if (end >= top && end <= bottom) 1486 found = 1; 1487 if (!found) 1488 continue; 1489 list = xreallocarray(list, size + 1, sizeof *list); 1490 list[size++] = next; 1491 } 1492 1493 best = window_pane_choose_best(list, size); 1494 free(list); 1495 return (best); 1496 } 1497 1498 /* Find the pane directly to the right of another. */ 1499 struct window_pane * 1500 window_pane_find_right(struct window_pane *wp) 1501 { 1502 struct window_pane *next, *best, **list; 1503 u_int edge, top, bottom, end, size; 1504 int found; 1505 1506 if (wp == NULL || !window_pane_visible(wp)) 1507 return (NULL); 1508 1509 list = NULL; 1510 size = 0; 1511 1512 edge = wp->xoff + wp->sx + 1; 1513 if (edge >= wp->window->sx) 1514 edge = 0; 1515 1516 top = wp->yoff; 1517 bottom = wp->yoff + wp->sy; 1518 1519 TAILQ_FOREACH(next, &wp->window->panes, entry) { 1520 if (next == wp || !window_pane_visible(next)) 1521 continue; 1522 if (next->xoff != edge) 1523 continue; 1524 end = next->yoff + next->sy - 1; 1525 1526 found = 0; 1527 if (next->yoff < top && end > bottom) 1528 found = 1; 1529 else if (next->yoff >= top && next->yoff <= bottom) 1530 found = 1; 1531 else if (end >= top && end <= bottom) 1532 found = 1; 1533 if (!found) 1534 continue; 1535 list = xreallocarray(list, size + 1, sizeof *list); 1536 list[size++] = next; 1537 } 1538 1539 best = window_pane_choose_best(list, size); 1540 free(list); 1541 return (best); 1542 } 1543 1544 /* Clear alert flags for a winlink */ 1545 void 1546 winlink_clear_flags(struct winlink *wl) 1547 { 1548 struct winlink *loop; 1549 1550 wl->window->flags &= ~WINDOW_ALERTFLAGS; 1551 TAILQ_FOREACH(loop, &wl->window->winlinks, wentry) { 1552 if ((loop->flags & WINLINK_ALERTFLAGS) != 0) { 1553 loop->flags &= ~WINLINK_ALERTFLAGS; 1554 server_status_session(loop->session); 1555 } 1556 } 1557 } 1558 1559 /* Shuffle window indexes up. */ 1560 int 1561 winlink_shuffle_up(struct session *s, struct winlink *wl) 1562 { 1563 int idx, last; 1564 1565 idx = wl->idx + 1; 1566 1567 /* Find the next free index. */ 1568 for (last = idx; last < INT_MAX; last++) { 1569 if (winlink_find_by_index(&s->windows, last) == NULL) 1570 break; 1571 } 1572 if (last == INT_MAX) 1573 return (-1); 1574 1575 /* Move everything from last - 1 to idx up a bit. */ 1576 for (; last > idx; last--) { 1577 wl = winlink_find_by_index(&s->windows, last - 1); 1578 server_link_window(s, wl, s, last, 0, 0, NULL); 1579 server_unlink_window(s, wl); 1580 } 1581 1582 return (idx); 1583 } 1584