This commit was manufactured by cvs2svn to create tag 'TMUX_1_2'.
[tmux.git] / window.c
blobc48dc7987271b57389456752d40c1cbbf4c59739
1 /* $Id: window.c,v 1.126 2010-02-08 18:10:07 tcunha Exp $ */
3 /*
4 * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net>
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.
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.
19 #include <sys/types.h>
20 #include <sys/ioctl.h>
22 #include <errno.h>
23 #include <fcntl.h>
24 #include <fnmatch.h>
25 #include <pwd.h>
26 #include <signal.h>
27 #include <stdint.h>
28 #include <stdlib.h>
29 #include <string.h>
30 #include <termios.h>
31 #include <unistd.h>
33 #include "tmux.h"
36 * Each window is attached to a number of panes, each of which is a pty. This
37 * file contains code to handle them.
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.
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.
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.
54 /* Global window list. */
55 struct windows windows;
57 void window_pane_read_callback(struct bufferevent *, void *);
58 void window_pane_error_callback(struct bufferevent *, short, void *);
60 RB_GENERATE(winlinks, winlink, entry, winlink_cmp);
62 int
63 winlink_cmp(struct winlink *wl1, struct winlink *wl2)
65 return (wl1->idx - wl2->idx);
68 struct winlink *
69 winlink_find_by_window(struct winlinks *wwl, struct window *w)
71 struct winlink *wl;
73 RB_FOREACH(wl, winlinks, wwl) {
74 if (wl->window == w)
75 return (wl);
78 return (NULL);
81 struct winlink *
82 winlink_find_by_index(struct winlinks *wwl, int idx)
84 struct winlink wl;
86 if (idx < 0)
87 fatalx("bad index");
89 wl.idx = idx;
90 return (RB_FIND(winlinks, wwl, &wl));
93 int
94 winlink_next_index(struct winlinks *wwl, int idx)
96 int i;
98 i = idx;
99 do {
100 if (winlink_find_by_index(wwl, i) == NULL)
101 return (i);
102 if (i == INT_MAX)
103 i = 0;
104 else
105 i++;
106 } while (i != idx);
107 return (-1);
110 u_int
111 winlink_count(struct winlinks *wwl)
113 struct winlink *wl;
114 u_int n;
116 n = 0;
117 RB_FOREACH(wl, winlinks, wwl)
118 n++;
120 return (n);
123 struct winlink *
124 winlink_add(struct winlinks *wwl, struct window *w, int idx)
126 struct winlink *wl;
128 if (idx < 0) {
129 if ((idx = winlink_next_index(wwl, -idx - 1)) == -1)
130 return (NULL);
131 } else if (winlink_find_by_index(wwl, idx) != NULL)
132 return (NULL);
134 wl = xcalloc(1, sizeof *wl);
135 wl->idx = idx;
136 wl->window = w;
137 RB_INSERT(winlinks, wwl, wl);
139 w->references++;
141 return (wl);
144 void
145 winlink_remove(struct winlinks *wwl, struct winlink *wl)
147 struct window *w = wl->window;
149 RB_REMOVE(winlinks, wwl, wl);
150 if (wl->status_text != NULL)
151 xfree(wl->status_text);
152 xfree(wl);
154 if (w->references == 0)
155 fatal("bad reference count");
156 w->references--;
157 if (w->references == 0)
158 window_destroy(w);
161 struct winlink *
162 winlink_next(struct winlink *wl)
164 return (RB_NEXT(winlinks, wwl, wl));
167 struct winlink *
168 winlink_previous(struct winlink *wl)
170 return (RB_PREV(winlinks, wwl, wl));
173 void
174 winlink_stack_push(struct winlink_stack *stack, struct winlink *wl)
176 if (wl == NULL)
177 return;
179 winlink_stack_remove(stack, wl);
180 TAILQ_INSERT_HEAD(stack, wl, sentry);
183 void
184 winlink_stack_remove(struct winlink_stack *stack, struct winlink *wl)
186 struct winlink *wl2;
188 if (wl == NULL)
189 return;
191 TAILQ_FOREACH(wl2, stack, sentry) {
192 if (wl2 == wl) {
193 TAILQ_REMOVE(stack, wl, sentry);
194 return;
200 window_index(struct window *s, u_int *i)
202 for (*i = 0; *i < ARRAY_LENGTH(&windows); (*i)++) {
203 if (s == ARRAY_ITEM(&windows, *i))
204 return (0);
206 return (-1);
209 struct window *
210 window_create1(u_int sx, u_int sy)
212 struct window *w;
213 u_int i;
215 w = xcalloc(1, sizeof *w);
216 w->name = NULL;
217 w->flags = 0;
219 TAILQ_INIT(&w->panes);
220 w->active = NULL;
222 w->lastlayout = -1;
223 w->layout_root = NULL;
225 w->sx = sx;
226 w->sy = sy;
228 queue_window_name(w);
230 options_init(&w->options, &global_w_options);
232 for (i = 0; i < ARRAY_LENGTH(&windows); i++) {
233 if (ARRAY_ITEM(&windows, i) == NULL) {
234 ARRAY_SET(&windows, i, w);
235 break;
238 if (i == ARRAY_LENGTH(&windows))
239 ARRAY_ADD(&windows, w);
240 w->references = 0;
242 return (w);
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)
250 struct window *w;
251 struct window_pane *wp;
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);
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);
269 void
270 window_destroy(struct window *w)
272 u_int i;
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);
280 if (w->layout_root != NULL)
281 layout_free(w);
283 evtimer_del(&w->name_timer);
285 options_free(&w->options);
287 window_destroy_panes(w);
289 if (w->name != NULL)
290 xfree(w->name);
291 xfree(w);
294 void
295 window_resize(struct window *w, u_int sx, u_int sy)
297 w->sx = sx;
298 w->sy = sy;
301 void
302 window_set_active_pane(struct window *w, struct window_pane *wp)
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;
314 void
315 window_set_active_at(struct window *w, u_int x, u_int y)
317 struct window_pane *wp;
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;
331 struct window_pane *
332 window_add_pane(struct window *w, u_int hlimit)
334 struct window_pane *wp;
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);
344 void
345 window_remove_pane(struct window *w, struct window_pane *wp)
347 w->active = TAILQ_PREV(wp, window_panes, entry);
348 if (w->active == NULL)
349 w->active = TAILQ_NEXT(wp, entry);
351 TAILQ_REMOVE(&w->panes, wp, entry);
352 window_pane_destroy(wp);
355 struct window_pane *
356 window_pane_at_index(struct window *w, u_int idx)
358 struct window_pane *wp;
359 u_int n;
361 n = 0;
362 TAILQ_FOREACH(wp, &w->panes, entry) {
363 if (n == idx)
364 return (wp);
365 n++;
367 return (NULL);
370 u_int
371 window_pane_index(struct window *w, struct window_pane *wp)
373 struct window_pane *wq;
374 u_int n;
376 n = 0;
377 TAILQ_FOREACH(wq, &w->panes, entry) {
378 if (wp == wq)
379 break;
380 n++;
382 return (n);
385 u_int
386 window_count_panes(struct window *w)
388 struct window_pane *wp;
389 u_int n;
391 n = 0;
392 TAILQ_FOREACH(wp, &w->panes, entry)
393 n++;
394 return (n);
397 void
398 window_destroy_panes(struct window *w)
400 struct window_pane *wp;
402 while (!TAILQ_EMPTY(&w->panes)) {
403 wp = TAILQ_FIRST(&w->panes);
404 TAILQ_REMOVE(&w->panes, wp, entry);
405 window_pane_destroy(wp);
409 struct window_pane *
410 window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit)
412 struct window_pane *wp;
414 wp = xcalloc(1, sizeof *wp);
415 wp->window = w;
417 wp->cmd = NULL;
418 wp->shell = NULL;
419 wp->cwd = NULL;
421 wp->fd = -1;
422 wp->event = NULL;
424 wp->mode = NULL;
426 wp->layout_cell = NULL;
428 wp->xoff = 0;
429 wp->yoff = 0;
431 wp->sx = sx;
432 wp->sy = sy;
434 wp->pipe_fd = -1;
435 wp->pipe_off = 0;
436 wp->pipe_event = NULL;
438 wp->saved_grid = NULL;
440 screen_init(&wp->base, sx, sy, hlimit);
441 wp->screen = &wp->base;
443 input_init(wp);
445 return (wp);
448 void
449 window_pane_destroy(struct window_pane *wp)
451 if (wp->fd != -1) {
452 close(wp->fd);
453 bufferevent_free(wp->event);
456 input_free(wp);
458 window_pane_reset_mode(wp);
459 screen_free(&wp->base);
460 if (wp->saved_grid != NULL)
461 grid_destroy(wp->saved_grid);
463 if (wp->pipe_fd != -1) {
464 close(wp->pipe_fd);
465 bufferevent_free(wp->pipe_event);
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);
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)
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;
490 if (wp->fd != -1) {
491 close(wp->fd);
492 bufferevent_free(wp->event);
494 if (cmd != NULL) {
495 if (wp->cmd != NULL)
496 xfree(wp->cmd);
497 wp->cmd = xstrdup(cmd);
499 if (shell != NULL) {
500 if (wp->shell != NULL)
501 xfree(wp->shell);
502 wp->shell = xstrdup(shell);
504 if (cwd != NULL) {
505 if (wp->cwd != NULL)
506 xfree(wp->cwd);
507 wp->cwd = xstrdup(cwd);
510 memset(&ws, 0, sizeof ws);
511 ws.ws_col = screen_size_x(&wp->base);
512 ws.ws_row = screen_size_y(&wp->base);
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("/");
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");
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);
537 for (i = 0; i < ARRAY_LENGTH(&varlist); i++) {
538 var = ARRAY_ITEM(&varlist, i);
539 unsetenv(var);
541 RB_FOREACH(envent, environ, env) {
542 if (envent->value != NULL)
543 setenv(envent->name, envent->value, 1);
546 server_signal_clear();
547 log_close();
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);
555 execl(_PATH_BSHELL, "sh", "-c", wp->cmd, (char *) NULL);
556 fatal("execl failed");
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");
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);
580 return (0);
583 /* ARGSUSED */
584 void
585 window_pane_read_callback(unused struct bufferevent *bufev, void *data)
587 struct window_pane *wp = data;
589 window_pane_parse(wp);
592 /* ARGSUSED */
593 void
594 window_pane_error_callback(
595 unused struct bufferevent *bufev, unused short what, void *data)
597 struct window_pane *wp = data;
599 server_destroy_pane(wp);
602 void
603 window_pane_resize(struct window_pane *wp, u_int sx, u_int sy)
605 struct winsize ws;
607 if (sx == wp->sx && sy == wp->sy)
608 return;
609 wp->sx = sx;
610 wp->sy = sy;
612 memset(&ws, 0, sizeof ws);
613 ws.ws_col = sx;
614 ws.ws_row = sy;
616 screen_resize(&wp->base, sx, sy);
617 if (wp->mode != NULL)
618 wp->mode->resize(wp, sx, sy);
620 if (wp->fd != -1 && ioctl(wp->fd, TIOCSWINSZ, &ws) == -1)
621 #ifdef __sun
623 * Some versions of Solaris apparently can return an error when
624 * resizing; don't know why this happens, can't reproduce on
625 * other platforms and ignoring it doesn't seem to cause any
626 * issues.
628 if (errno != EINVAL)
629 #endif
630 fatal("ioctl failed");
634 window_pane_set_mode(struct window_pane *wp, const struct window_mode *mode)
636 struct screen *s;
638 if (wp->mode != NULL)
639 return (1);
640 wp->mode = mode;
642 if ((s = wp->mode->init(wp)) != NULL)
643 wp->screen = s;
644 wp->flags |= PANE_REDRAW;
645 return (0);
648 void
649 window_pane_reset_mode(struct window_pane *wp)
651 if (wp->mode == NULL)
652 return;
654 wp->mode->free(wp);
655 wp->mode = NULL;
657 wp->screen = &wp->base;
658 wp->flags |= PANE_REDRAW;
661 void
662 window_pane_parse(struct window_pane *wp)
664 char *data;
665 size_t new_size;
667 new_size = EVBUFFER_LENGTH(wp->event->input) - wp->pipe_off;
668 if (wp->pipe_fd != -1 && new_size > 0) {
669 data = EVBUFFER_DATA(wp->event->input);
670 bufferevent_write(wp->pipe_event, data, new_size);
673 input_parse(wp);
675 wp->pipe_off = EVBUFFER_LENGTH(wp->event->input);
678 void
679 window_pane_key(struct window_pane *wp, struct client *c, int key)
681 struct window_pane *wp2;
683 if (!window_pane_visible(wp))
684 return;
686 if (wp->mode != NULL) {
687 if (wp->mode->key != NULL)
688 wp->mode->key(wp, c, key);
689 return;
692 if (wp->fd == -1)
693 return;
694 input_key(wp, key);
695 if (options_get_number(&wp->window->options, "synchronize-panes")) {
696 TAILQ_FOREACH(wp2, &wp->window->panes, entry) {
697 if (wp2 == wp || wp2->mode != NULL)
698 continue;
699 if (wp2->fd != -1 && window_pane_visible(wp2))
700 input_key(wp2, key);
705 void
706 window_pane_mouse(
707 struct window_pane *wp, struct client *c, struct mouse_event *m)
709 if (!window_pane_visible(wp))
710 return;
712 if (m->x < wp->xoff || m->x >= wp->xoff + wp->sx)
713 return;
714 if (m->y < wp->yoff || m->y >= wp->yoff + wp->sy)
715 return;
716 m->x -= wp->xoff;
717 m->y -= wp->yoff;
719 if (wp->mode != NULL) {
720 if (wp->mode->mouse != NULL)
721 wp->mode->mouse(wp, c, m);
722 } else if (wp->fd != -1)
723 input_mouse(wp, m);
727 window_pane_visible(struct window_pane *wp)
729 struct window *w = wp->window;
731 if (wp->xoff >= w->sx || wp->yoff >= w->sy)
732 return (0);
733 if (wp->xoff + wp->sx > w->sx || wp->yoff + wp->sy > w->sy)
734 return (0);
735 return (1);
738 char *
739 window_pane_search(struct window_pane *wp, const char *searchstr, u_int *lineno)
741 struct screen *s = &wp->base;
742 char *newsearchstr, *line, *msg;
743 u_int i;
745 msg = NULL;
746 xasprintf(&newsearchstr, "*%s*", searchstr);
748 for (i = 0; i < screen_size_y(s); i++) {
749 line = grid_view_string_cells(s->grid, 0, i, screen_size_x(s));
750 if (fnmatch(newsearchstr, line, 0) == 0) {
751 msg = line;
752 if (lineno != NULL)
753 *lineno = i;
754 break;
756 xfree(line);
759 xfree(newsearchstr);
760 return (msg);