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