1 /* $OpenBSD: window.c,v 1.39 2009/11/13 17:33:07 nicm Exp $ */ 2 3 /* 4 * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net> 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 <paths.h> 26 #include <pwd.h> 27 #include <signal.h> 28 #include <stdint.h> 29 #include <stdlib.h> 30 #include <string.h> 31 #include <termios.h> 32 #include <unistd.h> 33 #include <util.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 void window_pane_read_callback(struct bufferevent *, void *); 60 void window_pane_error_callback(struct bufferevent *, short, void *); 61 62 RB_GENERATE(winlinks, winlink, entry, winlink_cmp); 63 64 int 65 winlink_cmp(struct winlink *wl1, struct winlink *wl2) 66 { 67 return (wl1->idx - wl2->idx); 68 } 69 70 struct winlink * 71 winlink_find_by_window(struct winlinks *wwl, struct window *w) 72 { 73 struct winlink *wl; 74 75 RB_FOREACH(wl, winlinks, wwl) { 76 if (wl->window == w) 77 return (wl); 78 } 79 80 return (NULL); 81 } 82 83 struct winlink * 84 winlink_find_by_index(struct winlinks *wwl, int idx) 85 { 86 struct winlink wl; 87 88 if (idx < 0) 89 fatalx("bad index"); 90 91 wl.idx = idx; 92 return (RB_FIND(winlinks, wwl, &wl)); 93 } 94 95 int 96 winlink_next_index(struct winlinks *wwl, int idx) 97 { 98 int i; 99 100 i = idx; 101 do { 102 if (winlink_find_by_index(wwl, i) == NULL) 103 return (i); 104 if (i == INT_MAX) 105 i = 0; 106 else 107 i++; 108 } while (i != idx); 109 return (-1); 110 } 111 112 u_int 113 winlink_count(struct winlinks *wwl) 114 { 115 struct winlink *wl; 116 u_int n; 117 118 n = 0; 119 RB_FOREACH(wl, winlinks, wwl) 120 n++; 121 122 return (n); 123 } 124 125 struct winlink * 126 winlink_add(struct winlinks *wwl, struct window *w, int idx) 127 { 128 struct winlink *wl; 129 130 if (idx < 0) { 131 if ((idx = winlink_next_index(wwl, -idx - 1)) == -1) 132 return (NULL); 133 } else if (winlink_find_by_index(wwl, idx) != NULL) 134 return (NULL); 135 136 wl = xcalloc(1, sizeof *wl); 137 wl->idx = idx; 138 wl->window = w; 139 RB_INSERT(winlinks, wwl, wl); 140 141 w->references++; 142 143 return (wl); 144 } 145 146 void 147 winlink_remove(struct winlinks *wwl, struct winlink *wl) 148 { 149 struct window *w = wl->window; 150 151 RB_REMOVE(winlinks, wwl, wl); 152 xfree(wl); 153 154 if (w->references == 0) 155 fatal("bad reference count"); 156 w->references--; 157 if (w->references == 0) 158 window_destroy(w); 159 } 160 161 struct winlink * 162 winlink_next(unused struct winlinks *wwl, struct winlink *wl) 163 { 164 return (RB_NEXT(winlinks, wwl, wl)); 165 } 166 167 struct winlink * 168 winlink_previous(unused struct winlinks *wwl, struct winlink *wl) 169 { 170 return (RB_PREV(winlinks, wwl, wl)); 171 } 172 173 void 174 winlink_stack_push(struct winlink_stack *stack, struct winlink *wl) 175 { 176 if (wl == NULL) 177 return; 178 179 winlink_stack_remove(stack, wl); 180 TAILQ_INSERT_HEAD(stack, wl, sentry); 181 } 182 183 void 184 winlink_stack_remove(struct winlink_stack *stack, struct winlink *wl) 185 { 186 struct winlink *wl2; 187 188 if (wl == NULL) 189 return; 190 191 TAILQ_FOREACH(wl2, stack, sentry) { 192 if (wl2 == wl) { 193 TAILQ_REMOVE(stack, wl, sentry); 194 return; 195 } 196 } 197 } 198 199 int 200 window_index(struct window *s, u_int *i) 201 { 202 for (*i = 0; *i < ARRAY_LENGTH(&windows); (*i)++) { 203 if (s == ARRAY_ITEM(&windows, *i)) 204 return (0); 205 } 206 return (-1); 207 } 208 209 struct window * 210 window_create1(u_int sx, u_int sy) 211 { 212 struct window *w; 213 u_int i; 214 215 w = xcalloc(1, sizeof *w); 216 w->name = NULL; 217 w->flags = 0; 218 219 TAILQ_INIT(&w->panes); 220 w->active = NULL; 221 222 w->lastlayout = -1; 223 w->layout_root = NULL; 224 225 w->sx = sx; 226 w->sy = sy; 227 228 queue_window_name(w); 229 230 options_init(&w->options, &global_w_options); 231 232 for (i = 0; i < ARRAY_LENGTH(&windows); i++) { 233 if (ARRAY_ITEM(&windows, i) == NULL) { 234 ARRAY_SET(&windows, i, w); 235 break; 236 } 237 } 238 if (i == ARRAY_LENGTH(&windows)) 239 ARRAY_ADD(&windows, w); 240 w->references = 0; 241 242 return (w); 243 } 244 245 struct window * 246 window_create(const char *name, const char *cmd, const char *shell, 247 const char *cwd, struct environ *env, struct termios *tio, 248 u_int sx, u_int sy, u_int hlimit,char **cause) 249 { 250 struct window *w; 251 struct window_pane *wp; 252 253 w = window_create1(sx, sy); 254 wp = window_add_pane(w, hlimit); 255 layout_init(w); 256 if (window_pane_spawn(wp, cmd, shell, cwd, env, tio, cause) != 0) { 257 window_destroy(w); 258 return (NULL); 259 } 260 w->active = TAILQ_FIRST(&w->panes); 261 if (name != NULL) { 262 w->name = xstrdup(name); 263 options_set_number(&w->options, "automatic-rename", 0); 264 } else 265 w->name = default_window_name(w); 266 return (w); 267 } 268 269 void 270 window_destroy(struct window *w) 271 { 272 u_int i; 273 274 if (window_index(w, &i) != 0) 275 fatalx("index not found"); 276 ARRAY_SET(&windows, i, NULL); 277 while (!ARRAY_EMPTY(&windows) && ARRAY_LAST(&windows) == NULL) 278 ARRAY_TRUNC(&windows, 1); 279 280 if (w->layout_root != NULL) 281 layout_free(w); 282 283 evtimer_del(&w->name_timer); 284 285 options_free(&w->options); 286 287 window_destroy_panes(w); 288 289 if (w->name != NULL) 290 xfree(w->name); 291 xfree(w); 292 } 293 294 void 295 window_resize(struct window *w, u_int sx, u_int sy) 296 { 297 w->sx = sx; 298 w->sy = sy; 299 } 300 301 void 302 window_set_active_pane(struct window *w, struct window_pane *wp) 303 { 304 w->active = wp; 305 while (!window_pane_visible(w->active)) { 306 w->active = TAILQ_PREV(w->active, window_panes, entry); 307 if (w->active == NULL) 308 w->active = TAILQ_LAST(&w->panes, window_panes); 309 if (w->active == wp) 310 return; 311 } 312 } 313 314 void 315 window_set_active_at(struct window *w, u_int x, u_int y) 316 { 317 struct window_pane *wp; 318 319 TAILQ_FOREACH(wp, &w->panes, entry) { 320 if (!window_pane_visible(wp)) 321 continue; 322 if (x < wp->xoff || x >= wp->xoff + wp->sx) 323 continue; 324 if (y < wp->yoff || y >= wp->yoff + wp->sy) 325 continue; 326 window_set_active_pane(w, wp); 327 break; 328 } 329 } 330 331 struct window_pane * 332 window_add_pane(struct window *w, u_int hlimit) 333 { 334 struct window_pane *wp; 335 336 wp = window_pane_create(w, w->sx, w->sy, hlimit); 337 if (TAILQ_EMPTY(&w->panes)) 338 TAILQ_INSERT_HEAD(&w->panes, wp, entry); 339 else 340 TAILQ_INSERT_AFTER(&w->panes, w->active, wp, entry); 341 return (wp); 342 } 343 344 void 345 window_remove_pane(struct window *w, struct window_pane *wp) 346 { 347 w->active = TAILQ_PREV(wp, window_panes, entry); 348 if (w->active == NULL) 349 w->active = TAILQ_NEXT(wp, entry); 350 351 TAILQ_REMOVE(&w->panes, wp, entry); 352 window_pane_destroy(wp); 353 } 354 355 struct window_pane * 356 window_pane_at_index(struct window *w, u_int idx) 357 { 358 struct window_pane *wp; 359 u_int n; 360 361 n = 0; 362 TAILQ_FOREACH(wp, &w->panes, entry) { 363 if (n == idx) 364 return (wp); 365 n++; 366 } 367 return (NULL); 368 } 369 370 u_int 371 window_pane_index(struct window *w, struct window_pane *wp) 372 { 373 struct window_pane *wq; 374 u_int n; 375 376 n = 0; 377 TAILQ_FOREACH(wq, &w->panes, entry) { 378 if (wp == wq) 379 break; 380 n++; 381 } 382 return (n); 383 } 384 385 u_int 386 window_count_panes(struct window *w) 387 { 388 struct window_pane *wp; 389 u_int n; 390 391 n = 0; 392 TAILQ_FOREACH(wp, &w->panes, entry) 393 n++; 394 return (n); 395 } 396 397 void 398 window_destroy_panes(struct window *w) 399 { 400 struct window_pane *wp; 401 402 while (!TAILQ_EMPTY(&w->panes)) { 403 wp = TAILQ_FIRST(&w->panes); 404 TAILQ_REMOVE(&w->panes, wp, entry); 405 window_pane_destroy(wp); 406 } 407 } 408 409 struct window_pane * 410 window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit) 411 { 412 struct window_pane *wp; 413 414 wp = xcalloc(1, sizeof *wp); 415 wp->window = w; 416 417 wp->cmd = NULL; 418 wp->shell = NULL; 419 wp->cwd = NULL; 420 421 wp->fd = -1; 422 wp->event = NULL; 423 424 wp->mode = NULL; 425 426 wp->layout_cell = NULL; 427 428 wp->xoff = 0; 429 wp->yoff = 0; 430 431 wp->sx = sx; 432 wp->sy = sy; 433 434 wp->pipe_fd = -1; 435 wp->pipe_off = 0; 436 wp->pipe_event = NULL; 437 438 wp->saved_grid = NULL; 439 440 screen_init(&wp->base, sx, sy, hlimit); 441 wp->screen = &wp->base; 442 443 input_init(wp); 444 445 return (wp); 446 } 447 448 void 449 window_pane_destroy(struct window_pane *wp) 450 { 451 if (wp->fd != -1) { 452 close(wp->fd); 453 bufferevent_free(wp->event); 454 } 455 456 input_free(wp); 457 458 window_pane_reset_mode(wp); 459 screen_free(&wp->base); 460 if (wp->saved_grid != NULL) 461 grid_destroy(wp->saved_grid); 462 463 if (wp->pipe_fd != -1) { 464 close(wp->pipe_fd); 465 bufferevent_free(wp->pipe_event); 466 } 467 468 if (wp->cwd != NULL) 469 xfree(wp->cwd); 470 if (wp->shell != NULL) 471 xfree(wp->shell); 472 if (wp->cmd != NULL) 473 xfree(wp->cmd); 474 xfree(wp); 475 } 476 477 int 478 window_pane_spawn(struct window_pane *wp, const char *cmd, const char *shell, 479 const char *cwd, struct environ *env, struct termios *tio, char **cause) 480 { 481 struct winsize ws; 482 int mode; 483 char *argv0, **varp, *var; 484 ARRAY_DECL(, char *) varlist; 485 struct environ_entry *envent; 486 const char *ptr; 487 struct termios tio2; 488 u_int i; 489 490 if (wp->fd != -1) { 491 close(wp->fd); 492 bufferevent_free(wp->event); 493 } 494 if (cmd != NULL) { 495 if (wp->cmd != NULL) 496 xfree(wp->cmd); 497 wp->cmd = xstrdup(cmd); 498 } 499 if (shell != NULL) { 500 if (wp->shell != NULL) 501 xfree(wp->shell); 502 wp->shell = xstrdup(shell); 503 } 504 if (cwd != NULL) { 505 if (wp->cwd != NULL) 506 xfree(wp->cwd); 507 wp->cwd = xstrdup(cwd); 508 } 509 510 memset(&ws, 0, sizeof ws); 511 ws.ws_col = screen_size_x(&wp->base); 512 ws.ws_row = screen_size_y(&wp->base); 513 514 switch (wp->pid = forkpty(&wp->fd, wp->tty, NULL, &ws)) { 515 case -1: 516 wp->fd = -1; 517 xasprintf(cause, "%s: %s", cmd, strerror(errno)); 518 return (-1); 519 case 0: 520 if (chdir(wp->cwd) != 0) 521 chdir("/"); 522 523 if (tcgetattr(STDIN_FILENO, &tio2) != 0) 524 fatal("tcgetattr failed"); 525 if (tio != NULL) 526 memcpy(tio2.c_cc, tio->c_cc, sizeof tio2.c_cc); 527 tio2.c_cc[VERASE] = '\177'; 528 if (tcsetattr(STDIN_FILENO, TCSANOW, &tio2) != 0) 529 fatal("tcgetattr failed"); 530 531 ARRAY_INIT(&varlist); 532 for (varp = environ; *varp != NULL; varp++) { 533 var = xstrdup(*varp); 534 var[strcspn(var, "=")] = '\0'; 535 ARRAY_ADD(&varlist, var); 536 } 537 for (i = 0; i < ARRAY_LENGTH(&varlist); i++) { 538 var = ARRAY_ITEM(&varlist, i); 539 unsetenv(var); 540 } 541 RB_FOREACH(envent, environ, env) { 542 if (envent->value != NULL) 543 setenv(envent->name, envent->value, 1); 544 } 545 546 server_signal_clear(); 547 log_close(); 548 549 if (*wp->cmd != '\0') { 550 /* Set SHELL but only if it is currently not useful. */ 551 shell = getenv("SHELL"); 552 if (shell == NULL || *shell == '\0' || areshell(shell)) 553 setenv("SHELL", wp->shell, 1); 554 555 execl(_PATH_BSHELL, "sh", "-c", wp->cmd, (char *) NULL); 556 fatal("execl failed"); 557 } 558 559 /* No command; fork a login shell. */ 560 ptr = strrchr(wp->shell, '/'); 561 if (ptr != NULL && *(ptr + 1) != '\0') 562 xasprintf(&argv0, "-%s", ptr + 1); 563 else 564 xasprintf(&argv0, "-%s", wp->shell); 565 setenv("SHELL", wp->shell, 1); 566 execl(wp->shell, argv0, (char *) NULL); 567 fatal("execl failed"); 568 } 569 570 if ((mode = fcntl(wp->fd, F_GETFL)) == -1) 571 fatal("fcntl failed"); 572 if (fcntl(wp->fd, F_SETFL, mode|O_NONBLOCK) == -1) 573 fatal("fcntl failed"); 574 if (fcntl(wp->fd, F_SETFD, FD_CLOEXEC) == -1) 575 fatal("fcntl failed"); 576 wp->event = bufferevent_new(wp->fd, 577 window_pane_read_callback, NULL, window_pane_error_callback, wp); 578 bufferevent_enable(wp->event, EV_READ|EV_WRITE); 579 580 return (0); 581 } 582 583 void 584 window_pane_read_callback(unused struct bufferevent *bufev, void *data) 585 { 586 struct window_pane *wp = data; 587 588 window_pane_parse(wp); 589 } 590 591 void 592 window_pane_error_callback( 593 unused struct bufferevent *bufev, unused short what, void *data) 594 { 595 struct window_pane *wp = data; 596 597 server_destroy_pane(wp); 598 } 599 600 void 601 window_pane_resize(struct window_pane *wp, u_int sx, u_int sy) 602 { 603 struct winsize ws; 604 605 if (sx == wp->sx && sy == wp->sy) 606 return; 607 wp->sx = sx; 608 wp->sy = sy; 609 610 memset(&ws, 0, sizeof ws); 611 ws.ws_col = sx; 612 ws.ws_row = sy; 613 614 screen_resize(&wp->base, sx, sy); 615 if (wp->mode != NULL) 616 wp->mode->resize(wp, sx, sy); 617 618 if (wp->fd != -1 && ioctl(wp->fd, TIOCSWINSZ, &ws) == -1) 619 fatal("ioctl failed"); 620 } 621 622 int 623 window_pane_set_mode(struct window_pane *wp, const struct window_mode *mode) 624 { 625 struct screen *s; 626 627 if (wp->mode != NULL) 628 return (1); 629 wp->mode = mode; 630 631 if ((s = wp->mode->init(wp)) != NULL) 632 wp->screen = s; 633 wp->flags |= PANE_REDRAW; 634 return (0); 635 } 636 637 void 638 window_pane_reset_mode(struct window_pane *wp) 639 { 640 if (wp->mode == NULL) 641 return; 642 643 wp->mode->free(wp); 644 wp->mode = NULL; 645 646 wp->screen = &wp->base; 647 wp->flags |= PANE_REDRAW; 648 } 649 650 void 651 window_pane_parse(struct window_pane *wp) 652 { 653 char *data; 654 size_t new_size; 655 656 if (wp->mode != NULL) 657 return; 658 659 new_size = EVBUFFER_LENGTH(wp->event->input) - wp->pipe_off; 660 if (wp->pipe_fd != -1 && new_size > 0) { 661 data = EVBUFFER_DATA(wp->event->input); 662 bufferevent_write(wp->pipe_event, data, new_size); 663 } 664 665 input_parse(wp); 666 667 wp->pipe_off = EVBUFFER_LENGTH(wp->event->input); 668 } 669 670 void 671 window_pane_key(struct window_pane *wp, struct client *c, int key) 672 { 673 struct window_pane *wp2; 674 675 if (!window_pane_visible(wp)) 676 return; 677 678 if (wp->mode != NULL) { 679 if (wp->mode->key != NULL) 680 wp->mode->key(wp, c, key); 681 return; 682 } 683 684 if (wp->fd == -1) 685 return; 686 input_key(wp, key); 687 if (options_get_number(&wp->window->options, "synchronize-panes")) { 688 TAILQ_FOREACH(wp2, &wp->window->panes, entry) { 689 if (wp2 == wp || wp2->mode != NULL) 690 continue; 691 if (wp2->fd != -1 && window_pane_visible(wp2)) 692 input_key(wp2, key); 693 } 694 } 695 } 696 697 void 698 window_pane_mouse( 699 struct window_pane *wp, struct client *c, struct mouse_event *m) 700 { 701 if (!window_pane_visible(wp)) 702 return; 703 704 if (m->x < wp->xoff || m->x >= wp->xoff + wp->sx) 705 return; 706 if (m->y < wp->yoff || m->y >= wp->yoff + wp->sy) 707 return; 708 m->x -= wp->xoff; 709 m->y -= wp->yoff; 710 711 if (wp->mode != NULL) { 712 if (wp->mode->mouse != NULL) 713 wp->mode->mouse(wp, c, m); 714 } else if (wp->fd != -1) 715 input_mouse(wp, m); 716 } 717 718 int 719 window_pane_visible(struct window_pane *wp) 720 { 721 struct window *w = wp->window; 722 723 if (wp->xoff >= w->sx || wp->yoff >= w->sy) 724 return (0); 725 if (wp->xoff + wp->sx > w->sx || wp->yoff + wp->sy > w->sy) 726 return (0); 727 return (1); 728 } 729 730 char * 731 window_pane_search(struct window_pane *wp, const char *searchstr, u_int *lineno) 732 { 733 struct screen *s = &wp->base; 734 char *newsearchstr, *line, *msg; 735 u_int i; 736 737 msg = NULL; 738 xasprintf(&newsearchstr, "*%s*", searchstr); 739 740 for (i = 0; i < screen_size_y(s); i++) { 741 line = grid_view_string_cells(s->grid, 0, i, screen_size_x(s)); 742 if (fnmatch(newsearchstr, line, 0) == 0) { 743 msg = line; 744 if (lineno != NULL) 745 *lineno = i; 746 break; 747 } 748 xfree(line); 749 } 750 751 xfree(newsearchstr); 752 return (msg); 753 } 754