Fix a memory leak, from Japin Li in GitHub issue 3358.
[tmux.git] / window.c
blob1b0066c281a495290604376a0bd610eb88e1a440
1 /* $OpenBSD$ */
3 /*
4 * Copyright (c) 2007 Nicholas Marriott <nicholas.marriott@gmail.com>
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 <ctype.h>
23 #include <errno.h>
24 #include <fcntl.h>
25 #include <fnmatch.h>
26 #include <regex.h>
27 #include <signal.h>
28 #include <stdint.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #include <time.h>
32 #include <unistd.h>
33 #include <util.h>
34 #include <vis.h>
36 #include "tmux.h"
39 * Each window is attached to a number of panes, each of which is a pty. This
40 * file contains code to handle them.
42 * A pane has two buffers attached, these are filled and emptied by the main
43 * server poll loop. Output data is received from pty's in screen format,
44 * translated and returned as a series of escape sequences and strings via
45 * input_parse (in input.c). Input data is received as key codes and written
46 * directly via input_key.
48 * Each pane also has a "virtual" screen (screen.c) which contains the current
49 * state and is redisplayed when the window is reattached to a client.
51 * Windows are stored directly on a global array and wrapped in any number of
52 * winlink structs to be linked onto local session RB trees. A reference count
53 * is maintained and a window removed from the global list and destroyed when
54 * it reaches zero.
57 /* Global window list. */
58 struct windows windows;
60 /* Global panes tree. */
61 struct window_pane_tree all_window_panes;
62 static u_int next_window_pane_id;
63 static u_int next_window_id;
64 static u_int next_active_point;
66 struct window_pane_input_data {
67 struct cmdq_item *item;
68 u_int wp;
71 static struct window_pane *window_pane_create(struct window *, u_int, u_int,
72 u_int);
73 static void window_pane_destroy(struct window_pane *);
75 RB_GENERATE(windows, window, entry, window_cmp);
76 RB_GENERATE(winlinks, winlink, entry, winlink_cmp);
77 RB_GENERATE(window_pane_tree, window_pane, tree_entry, window_pane_cmp);
79 int
80 window_cmp(struct window *w1, struct window *w2)
82 return (w1->id - w2->id);
85 int
86 winlink_cmp(struct winlink *wl1, struct winlink *wl2)
88 return (wl1->idx - wl2->idx);
91 int
92 window_pane_cmp(struct window_pane *wp1, struct window_pane *wp2)
94 return (wp1->id - wp2->id);
97 struct winlink *
98 winlink_find_by_window(struct winlinks *wwl, struct window *w)
100 struct winlink *wl;
102 RB_FOREACH(wl, winlinks, wwl) {
103 if (wl->window == w)
104 return (wl);
107 return (NULL);
110 struct winlink *
111 winlink_find_by_index(struct winlinks *wwl, int idx)
113 struct winlink wl;
115 if (idx < 0)
116 fatalx("bad index");
118 wl.idx = idx;
119 return (RB_FIND(winlinks, wwl, &wl));
122 struct winlink *
123 winlink_find_by_window_id(struct winlinks *wwl, u_int id)
125 struct winlink *wl;
127 RB_FOREACH(wl, winlinks, wwl) {
128 if (wl->window->id == id)
129 return (wl);
131 return (NULL);
134 static int
135 winlink_next_index(struct winlinks *wwl, int idx)
137 int i;
139 i = idx;
140 do {
141 if (winlink_find_by_index(wwl, i) == NULL)
142 return (i);
143 if (i == INT_MAX)
144 i = 0;
145 else
146 i++;
147 } while (i != idx);
148 return (-1);
151 u_int
152 winlink_count(struct winlinks *wwl)
154 struct winlink *wl;
155 u_int n;
157 n = 0;
158 RB_FOREACH(wl, winlinks, wwl)
159 n++;
161 return (n);
164 struct winlink *
165 winlink_add(struct winlinks *wwl, int idx)
167 struct winlink *wl;
169 if (idx < 0) {
170 if ((idx = winlink_next_index(wwl, -idx - 1)) == -1)
171 return (NULL);
172 } else if (winlink_find_by_index(wwl, idx) != NULL)
173 return (NULL);
175 wl = xcalloc(1, sizeof *wl);
176 wl->idx = idx;
177 RB_INSERT(winlinks, wwl, wl);
179 return (wl);
182 void
183 winlink_set_window(struct winlink *wl, struct window *w)
185 if (wl->window != NULL) {
186 TAILQ_REMOVE(&wl->window->winlinks, wl, wentry);
187 window_remove_ref(wl->window, __func__);
189 TAILQ_INSERT_TAIL(&w->winlinks, wl, wentry);
190 wl->window = w;
191 window_add_ref(w, __func__);
194 void
195 winlink_remove(struct winlinks *wwl, struct winlink *wl)
197 struct window *w = wl->window;
199 if (w != NULL) {
200 TAILQ_REMOVE(&w->winlinks, wl, wentry);
201 window_remove_ref(w, __func__);
204 RB_REMOVE(winlinks, wwl, wl);
205 free(wl);
208 struct winlink *
209 winlink_next(struct winlink *wl)
211 return (RB_NEXT(winlinks, wwl, wl));
214 struct winlink *
215 winlink_previous(struct winlink *wl)
217 return (RB_PREV(winlinks, wwl, wl));
220 struct winlink *
221 winlink_next_by_number(struct winlink *wl, struct session *s, int n)
223 for (; n > 0; n--) {
224 if ((wl = RB_NEXT(winlinks, wwl, wl)) == NULL)
225 wl = RB_MIN(winlinks, &s->windows);
228 return (wl);
231 struct winlink *
232 winlink_previous_by_number(struct winlink *wl, struct session *s, int n)
234 for (; n > 0; n--) {
235 if ((wl = RB_PREV(winlinks, wwl, wl)) == NULL)
236 wl = RB_MAX(winlinks, &s->windows);
239 return (wl);
242 void
243 winlink_stack_push(struct winlink_stack *stack, struct winlink *wl)
245 if (wl == NULL)
246 return;
248 winlink_stack_remove(stack, wl);
249 TAILQ_INSERT_HEAD(stack, wl, sentry);
252 void
253 winlink_stack_remove(struct winlink_stack *stack, struct winlink *wl)
255 struct winlink *wl2;
257 if (wl == NULL)
258 return;
260 TAILQ_FOREACH(wl2, stack, sentry) {
261 if (wl2 == wl) {
262 TAILQ_REMOVE(stack, wl, sentry);
263 return;
268 struct window *
269 window_find_by_id_str(const char *s)
271 const char *errstr;
272 u_int id;
274 if (*s != '@')
275 return (NULL);
277 id = strtonum(s + 1, 0, UINT_MAX, &errstr);
278 if (errstr != NULL)
279 return (NULL);
280 return (window_find_by_id(id));
283 struct window *
284 window_find_by_id(u_int id)
286 struct window w;
288 w.id = id;
289 return (RB_FIND(windows, &windows, &w));
292 void
293 window_update_activity(struct window *w)
295 gettimeofday(&w->activity_time, NULL);
296 alerts_queue(w, WINDOW_ACTIVITY);
299 struct window *
300 window_create(u_int sx, u_int sy, u_int xpixel, u_int ypixel)
302 struct window *w;
304 if (xpixel == 0)
305 xpixel = DEFAULT_XPIXEL;
306 if (ypixel == 0)
307 ypixel = DEFAULT_YPIXEL;
309 w = xcalloc(1, sizeof *w);
310 w->name = xstrdup("");
311 w->flags = 0;
313 TAILQ_INIT(&w->panes);
314 w->active = NULL;
316 w->lastlayout = -1;
317 w->layout_root = NULL;
319 w->sx = sx;
320 w->sy = sy;
321 w->manual_sx = sx;
322 w->manual_sy = sy;
323 w->xpixel = xpixel;
324 w->ypixel = ypixel;
326 w->options = options_create(global_w_options);
328 w->references = 0;
329 TAILQ_INIT(&w->winlinks);
331 w->id = next_window_id++;
332 RB_INSERT(windows, &windows, w);
334 window_set_fill_character(w);
335 window_update_activity(w);
337 log_debug("%s: @%u create %ux%u (%ux%u)", __func__, w->id, sx, sy,
338 w->xpixel, w->ypixel);
339 return (w);
342 static void
343 window_destroy(struct window *w)
345 log_debug("window @%u destroyed (%d references)", w->id, w->references);
347 RB_REMOVE(windows, &windows, w);
349 if (w->layout_root != NULL)
350 layout_free_cell(w->layout_root);
351 if (w->saved_layout_root != NULL)
352 layout_free_cell(w->saved_layout_root);
353 free(w->old_layout);
355 window_destroy_panes(w);
357 if (event_initialized(&w->name_event))
358 evtimer_del(&w->name_event);
360 if (event_initialized(&w->alerts_timer))
361 evtimer_del(&w->alerts_timer);
362 if (event_initialized(&w->offset_timer))
363 event_del(&w->offset_timer);
365 options_free(w->options);
366 free(w->fill_character);
368 free(w->name);
369 free(w);
373 window_pane_destroy_ready(struct window_pane *wp)
375 int n;
377 if (wp->pipe_fd != -1) {
378 if (EVBUFFER_LENGTH(wp->pipe_event->output) != 0)
379 return (0);
380 if (ioctl(wp->fd, FIONREAD, &n) != -1 && n > 0)
381 return (0);
384 if (~wp->flags & PANE_EXITED)
385 return (0);
386 return (1);
389 void
390 window_add_ref(struct window *w, const char *from)
392 w->references++;
393 log_debug("%s: @%u %s, now %d", __func__, w->id, from, w->references);
396 void
397 window_remove_ref(struct window *w, const char *from)
399 w->references--;
400 log_debug("%s: @%u %s, now %d", __func__, w->id, from, w->references);
402 if (w->references == 0)
403 window_destroy(w);
406 void
407 window_set_name(struct window *w, const char *new_name)
409 free(w->name);
410 utf8_stravis(&w->name, new_name, VIS_OCTAL|VIS_CSTYLE|VIS_TAB|VIS_NL);
411 notify_window("window-renamed", w);
414 void
415 window_resize(struct window *w, u_int sx, u_int sy, int xpixel, int ypixel)
417 if (xpixel == 0)
418 xpixel = DEFAULT_XPIXEL;
419 if (ypixel == 0)
420 ypixel = DEFAULT_YPIXEL;
422 log_debug("%s: @%u resize %ux%u (%ux%u)", __func__, w->id, sx, sy,
423 xpixel == -1 ? w->xpixel : (u_int)xpixel,
424 ypixel == -1 ? w->ypixel : (u_int)ypixel);
425 w->sx = sx;
426 w->sy = sy;
427 if (xpixel != -1)
428 w->xpixel = xpixel;
429 if (ypixel != -1)
430 w->ypixel = ypixel;
433 void
434 window_pane_send_resize(struct window_pane *wp, u_int sx, u_int sy)
436 struct window *w = wp->window;
437 struct winsize ws;
439 if (wp->fd == -1)
440 return;
442 log_debug("%s: %%%u resize to %u,%u", __func__, wp->id, sx, sy);
444 memset(&ws, 0, sizeof ws);
445 ws.ws_col = sx;
446 ws.ws_row = sy;
447 ws.ws_xpixel = w->xpixel * ws.ws_col;
448 ws.ws_ypixel = w->ypixel * ws.ws_row;
449 if (ioctl(wp->fd, TIOCSWINSZ, &ws) == -1)
450 fatal("ioctl failed");
454 window_has_pane(struct window *w, struct window_pane *wp)
456 struct window_pane *wp1;
458 TAILQ_FOREACH(wp1, &w->panes, entry) {
459 if (wp1 == wp)
460 return (1);
462 return (0);
465 void
466 window_update_focus(struct window *w)
468 if (w != NULL) {
469 log_debug("%s: @%u", __func__, w->id);
470 window_pane_update_focus(w->active);
474 void
475 window_pane_update_focus(struct window_pane *wp)
477 struct client *c;
478 int focused = 0;
480 if (wp != NULL) {
481 if (wp != wp->window->active)
482 focused = 0;
483 else {
484 TAILQ_FOREACH(c, &clients, entry) {
485 if (c->session != NULL &&
486 c->session->attached != 0 &&
487 (c->flags & CLIENT_FOCUSED) &&
488 c->session->curw->window == wp->window) {
489 focused = 1;
490 break;
494 if (!focused && (wp->flags & PANE_FOCUSED)) {
495 log_debug("%s: %%%u focus out", __func__, wp->id);
496 if (wp->base.mode & MODE_FOCUSON)
497 bufferevent_write(wp->event, "\033[O", 3);
498 notify_pane("pane-focus-out", wp);
499 wp->flags &= ~PANE_FOCUSED;
500 } else if (focused && (~wp->flags & PANE_FOCUSED)) {
501 log_debug("%s: %%%u focus in", __func__, wp->id);
502 if (wp->base.mode & MODE_FOCUSON)
503 bufferevent_write(wp->event, "\033[I", 3);
504 notify_pane("pane-focus-in", wp);
505 wp->flags |= PANE_FOCUSED;
506 } else
507 log_debug("%s: %%%u focus unchanged", __func__, wp->id);
512 window_set_active_pane(struct window *w, struct window_pane *wp, int notify)
514 log_debug("%s: pane %%%u", __func__, wp->id);
516 if (wp == w->active)
517 return (0);
518 w->last = w->active;
520 w->active = wp;
521 w->active->active_point = next_active_point++;
522 w->active->flags |= PANE_CHANGED;
524 if (options_get_number(global_options, "focus-events")) {
525 window_pane_update_focus(w->last);
526 window_pane_update_focus(w->active);
529 tty_update_window_offset(w);
531 if (notify)
532 notify_window("window-pane-changed", w);
533 return (1);
536 static int
537 window_pane_get_palette(struct window_pane *wp, int c)
539 if (wp == NULL)
540 return (-1);
541 return (colour_palette_get(&wp->palette, c));
544 void
545 window_redraw_active_switch(struct window *w, struct window_pane *wp)
547 struct grid_cell *gc1, *gc2;
548 int c1, c2;
550 if (wp == w->active)
551 return;
553 for (;;) {
555 * If the active and inactive styles or palettes are different,
556 * need to redraw the panes.
558 gc1 = &wp->cached_gc;
559 gc2 = &wp->cached_active_gc;
560 if (!grid_cells_look_equal(gc1, gc2))
561 wp->flags |= PANE_REDRAW;
562 else {
563 c1 = window_pane_get_palette(wp, gc1->fg);
564 c2 = window_pane_get_palette(wp, gc2->fg);
565 if (c1 != c2)
566 wp->flags |= PANE_REDRAW;
567 else {
568 c1 = window_pane_get_palette(wp, gc1->bg);
569 c2 = window_pane_get_palette(wp, gc2->bg);
570 if (c1 != c2)
571 wp->flags |= PANE_REDRAW;
574 if (wp == w->active)
575 break;
576 wp = w->active;
580 struct window_pane *
581 window_get_active_at(struct window *w, u_int x, u_int y)
583 struct window_pane *wp;
585 TAILQ_FOREACH(wp, &w->panes, entry) {
586 if (!window_pane_visible(wp))
587 continue;
588 if (x < wp->xoff || x > wp->xoff + wp->sx)
589 continue;
590 if (y < wp->yoff || y > wp->yoff + wp->sy)
591 continue;
592 return (wp);
594 return (NULL);
597 struct window_pane *
598 window_find_string(struct window *w, const char *s)
600 u_int x, y, top = 0, bottom = w->sy - 1;
601 int status;
603 x = w->sx / 2;
604 y = w->sy / 2;
606 status = options_get_number(w->options, "pane-border-status");
607 if (status == PANE_STATUS_TOP)
608 top++;
609 else if (status == PANE_STATUS_BOTTOM)
610 bottom--;
612 if (strcasecmp(s, "top") == 0)
613 y = top;
614 else if (strcasecmp(s, "bottom") == 0)
615 y = bottom;
616 else if (strcasecmp(s, "left") == 0)
617 x = 0;
618 else if (strcasecmp(s, "right") == 0)
619 x = w->sx - 1;
620 else if (strcasecmp(s, "top-left") == 0) {
621 x = 0;
622 y = top;
623 } else if (strcasecmp(s, "top-right") == 0) {
624 x = w->sx - 1;
625 y = top;
626 } else if (strcasecmp(s, "bottom-left") == 0) {
627 x = 0;
628 y = bottom;
629 } else if (strcasecmp(s, "bottom-right") == 0) {
630 x = w->sx - 1;
631 y = bottom;
632 } else
633 return (NULL);
635 return (window_get_active_at(w, x, y));
639 window_zoom(struct window_pane *wp)
641 struct window *w = wp->window;
642 struct window_pane *wp1;
644 if (w->flags & WINDOW_ZOOMED)
645 return (-1);
647 if (window_count_panes(w) == 1)
648 return (-1);
650 if (w->active != wp)
651 window_set_active_pane(w, wp, 1);
653 TAILQ_FOREACH(wp1, &w->panes, entry) {
654 wp1->saved_layout_cell = wp1->layout_cell;
655 wp1->layout_cell = NULL;
658 w->saved_layout_root = w->layout_root;
659 layout_init(w, wp);
660 w->flags |= WINDOW_ZOOMED;
661 notify_window("window-layout-changed", w);
663 return (0);
667 window_unzoom(struct window *w)
669 struct window_pane *wp;
671 if (!(w->flags & WINDOW_ZOOMED))
672 return (-1);
674 w->flags &= ~WINDOW_ZOOMED;
675 layout_free(w);
676 w->layout_root = w->saved_layout_root;
677 w->saved_layout_root = NULL;
679 TAILQ_FOREACH(wp, &w->panes, entry) {
680 wp->layout_cell = wp->saved_layout_cell;
681 wp->saved_layout_cell = NULL;
683 layout_fix_panes(w, NULL);
684 notify_window("window-layout-changed", w);
686 return (0);
690 window_push_zoom(struct window *w, int always, int flag)
692 log_debug("%s: @%u %d", __func__, w->id,
693 flag && (w->flags & WINDOW_ZOOMED));
694 if (flag && (always || (w->flags & WINDOW_ZOOMED)))
695 w->flags |= WINDOW_WASZOOMED;
696 else
697 w->flags &= ~WINDOW_WASZOOMED;
698 return (window_unzoom(w) == 0);
702 window_pop_zoom(struct window *w)
704 log_debug("%s: @%u %d", __func__, w->id,
705 !!(w->flags & WINDOW_WASZOOMED));
706 if (w->flags & WINDOW_WASZOOMED)
707 return (window_zoom(w->active) == 0);
708 return (0);
711 struct window_pane *
712 window_add_pane(struct window *w, struct window_pane *other, u_int hlimit,
713 int flags)
715 struct window_pane *wp;
717 if (other == NULL)
718 other = w->active;
720 wp = window_pane_create(w, w->sx, w->sy, hlimit);
721 if (TAILQ_EMPTY(&w->panes)) {
722 log_debug("%s: @%u at start", __func__, w->id);
723 TAILQ_INSERT_HEAD(&w->panes, wp, entry);
724 } else if (flags & SPAWN_BEFORE) {
725 log_debug("%s: @%u before %%%u", __func__, w->id, wp->id);
726 if (flags & SPAWN_FULLSIZE)
727 TAILQ_INSERT_HEAD(&w->panes, wp, entry);
728 else
729 TAILQ_INSERT_BEFORE(other, wp, entry);
730 } else {
731 log_debug("%s: @%u after %%%u", __func__, w->id, wp->id);
732 if (flags & SPAWN_FULLSIZE)
733 TAILQ_INSERT_TAIL(&w->panes, wp, entry);
734 else
735 TAILQ_INSERT_AFTER(&w->panes, other, wp, entry);
737 return (wp);
740 void
741 window_lost_pane(struct window *w, struct window_pane *wp)
743 log_debug("%s: @%u pane %%%u", __func__, w->id, wp->id);
745 if (wp == marked_pane.wp)
746 server_clear_marked();
748 if (wp == w->active) {
749 w->active = w->last;
750 w->last = NULL;
751 if (w->active == NULL) {
752 w->active = TAILQ_PREV(wp, window_panes, entry);
753 if (w->active == NULL)
754 w->active = TAILQ_NEXT(wp, entry);
756 if (w->active != NULL) {
757 w->active->flags |= PANE_CHANGED;
758 notify_window("window-pane-changed", w);
759 window_update_focus(w);
761 } else if (wp == w->last)
762 w->last = NULL;
765 void
766 window_remove_pane(struct window *w, struct window_pane *wp)
768 window_lost_pane(w, wp);
770 TAILQ_REMOVE(&w->panes, wp, entry);
771 window_pane_destroy(wp);
774 struct window_pane *
775 window_pane_at_index(struct window *w, u_int idx)
777 struct window_pane *wp;
778 u_int n;
780 n = options_get_number(w->options, "pane-base-index");
781 TAILQ_FOREACH(wp, &w->panes, entry) {
782 if (n == idx)
783 return (wp);
784 n++;
786 return (NULL);
789 struct window_pane *
790 window_pane_next_by_number(struct window *w, struct window_pane *wp, u_int n)
792 for (; n > 0; n--) {
793 if ((wp = TAILQ_NEXT(wp, entry)) == NULL)
794 wp = TAILQ_FIRST(&w->panes);
797 return (wp);
800 struct window_pane *
801 window_pane_previous_by_number(struct window *w, struct window_pane *wp,
802 u_int n)
804 for (; n > 0; n--) {
805 if ((wp = TAILQ_PREV(wp, window_panes, entry)) == NULL)
806 wp = TAILQ_LAST(&w->panes, window_panes);
809 return (wp);
813 window_pane_index(struct window_pane *wp, u_int *i)
815 struct window_pane *wq;
816 struct window *w = wp->window;
818 *i = options_get_number(w->options, "pane-base-index");
819 TAILQ_FOREACH(wq, &w->panes, entry) {
820 if (wp == wq) {
821 return (0);
823 (*i)++;
826 return (-1);
829 u_int
830 window_count_panes(struct window *w)
832 struct window_pane *wp;
833 u_int n;
835 n = 0;
836 TAILQ_FOREACH(wp, &w->panes, entry)
837 n++;
838 return (n);
841 void
842 window_destroy_panes(struct window *w)
844 struct window_pane *wp;
846 while (!TAILQ_EMPTY(&w->panes)) {
847 wp = TAILQ_FIRST(&w->panes);
848 TAILQ_REMOVE(&w->panes, wp, entry);
849 window_pane_destroy(wp);
853 const char *
854 window_printable_flags(struct winlink *wl, int escape)
856 struct session *s = wl->session;
857 static char flags[32];
858 int pos;
860 pos = 0;
861 if (wl->flags & WINLINK_ACTIVITY) {
862 flags[pos++] = '#';
863 if (escape)
864 flags[pos++] = '#';
866 if (wl->flags & WINLINK_BELL)
867 flags[pos++] = '!';
868 if (wl->flags & WINLINK_SILENCE)
869 flags[pos++] = '~';
870 if (wl == s->curw)
871 flags[pos++] = '*';
872 if (wl == TAILQ_FIRST(&s->lastw))
873 flags[pos++] = '-';
874 if (server_check_marked() && wl == marked_pane.wl)
875 flags[pos++] = 'M';
876 if (wl->window->flags & WINDOW_ZOOMED)
877 flags[pos++] = 'Z';
878 flags[pos] = '\0';
879 return (flags);
882 struct window_pane *
883 window_pane_find_by_id_str(const char *s)
885 const char *errstr;
886 u_int id;
888 if (*s != '%')
889 return (NULL);
891 id = strtonum(s + 1, 0, UINT_MAX, &errstr);
892 if (errstr != NULL)
893 return (NULL);
894 return (window_pane_find_by_id(id));
897 struct window_pane *
898 window_pane_find_by_id(u_int id)
900 struct window_pane wp;
902 wp.id = id;
903 return (RB_FIND(window_pane_tree, &all_window_panes, &wp));
906 static struct window_pane *
907 window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit)
909 struct window_pane *wp;
910 char host[HOST_NAME_MAX + 1];
912 wp = xcalloc(1, sizeof *wp);
913 wp->window = w;
914 wp->options = options_create(w->options);
915 wp->flags = PANE_STYLECHANGED;
917 wp->id = next_window_pane_id++;
918 RB_INSERT(window_pane_tree, &all_window_panes, wp);
920 wp->fd = -1;
922 TAILQ_INIT(&wp->modes);
924 TAILQ_INIT (&wp->resize_queue);
926 wp->sx = sx;
927 wp->sy = sy;
929 wp->pipe_fd = -1;
931 colour_palette_init(&wp->palette);
932 colour_palette_from_option(&wp->palette, wp->options);
934 screen_init(&wp->base, sx, sy, hlimit);
935 wp->screen = &wp->base;
936 window_pane_default_cursor(wp);
938 screen_init(&wp->status_screen, 1, 1, 0);
940 if (gethostname(host, sizeof host) == 0)
941 screen_set_title(&wp->base, host);
943 return (wp);
946 static void
947 window_pane_destroy(struct window_pane *wp)
949 struct window_pane_resize *r;
950 struct window_pane_resize *r1;
952 window_pane_reset_mode_all(wp);
953 free(wp->searchstr);
955 if (wp->fd != -1) {
956 bufferevent_free(wp->event);
957 close(wp->fd);
959 if (wp->ictx != NULL)
960 input_free(wp->ictx);
962 screen_free(&wp->status_screen);
964 screen_free(&wp->base);
966 if (wp->pipe_fd != -1) {
967 bufferevent_free(wp->pipe_event);
968 close(wp->pipe_fd);
971 if (event_initialized(&wp->resize_timer))
972 event_del(&wp->resize_timer);
973 TAILQ_FOREACH_SAFE(r, &wp->resize_queue, entry, r1) {
974 TAILQ_REMOVE(&wp->resize_queue, r, entry);
975 free(r);
978 RB_REMOVE(window_pane_tree, &all_window_panes, wp);
980 options_free(wp->options);
981 free((void *)wp->cwd);
982 free(wp->shell);
983 cmd_free_argv(wp->argc, wp->argv);
984 colour_palette_free(&wp->palette);
985 free(wp);
988 static void
989 window_pane_read_callback(__unused struct bufferevent *bufev, void *data)
991 struct window_pane *wp = data;
992 struct evbuffer *evb = wp->event->input;
993 struct window_pane_offset *wpo = &wp->pipe_offset;
994 size_t size = EVBUFFER_LENGTH(evb);
995 char *new_data;
996 size_t new_size;
997 struct client *c;
999 if (wp->pipe_fd != -1) {
1000 new_data = window_pane_get_new_data(wp, wpo, &new_size);
1001 if (new_size > 0) {
1002 bufferevent_write(wp->pipe_event, new_data, new_size);
1003 window_pane_update_used_data(wp, wpo, new_size);
1007 log_debug("%%%u has %zu bytes", wp->id, size);
1008 TAILQ_FOREACH(c, &clients, entry) {
1009 if (c->session != NULL && (c->flags & CLIENT_CONTROL))
1010 control_write_output(c, wp);
1012 input_parse_pane(wp);
1013 bufferevent_disable(wp->event, EV_READ);
1016 static void
1017 window_pane_error_callback(__unused struct bufferevent *bufev,
1018 __unused short what, void *data)
1020 struct window_pane *wp = data;
1022 log_debug("%%%u error", wp->id);
1023 wp->flags |= PANE_EXITED;
1025 if (window_pane_destroy_ready(wp))
1026 server_destroy_pane(wp, 1);
1029 void
1030 window_pane_set_event(struct window_pane *wp)
1032 setblocking(wp->fd, 0);
1034 wp->event = bufferevent_new(wp->fd, window_pane_read_callback,
1035 NULL, window_pane_error_callback, wp);
1036 if (wp->event == NULL)
1037 fatalx("out of memory");
1038 wp->ictx = input_init(wp, wp->event, &wp->palette);
1040 bufferevent_enable(wp->event, EV_READ|EV_WRITE);
1043 void
1044 window_pane_resize(struct window_pane *wp, u_int sx, u_int sy)
1046 struct window_mode_entry *wme;
1047 struct window_pane_resize *r;
1049 if (sx == wp->sx && sy == wp->sy)
1050 return;
1052 r = xmalloc(sizeof *r);
1053 r->sx = sx;
1054 r->sy = sy;
1055 r->osx = wp->sx;
1056 r->osy = wp->sy;
1057 TAILQ_INSERT_TAIL (&wp->resize_queue, r, entry);
1059 wp->sx = sx;
1060 wp->sy = sy;
1062 log_debug("%s: %%%u resize %ux%u", __func__, wp->id, sx, sy);
1063 screen_resize(&wp->base, sx, sy, wp->base.saved_grid == NULL);
1065 wme = TAILQ_FIRST(&wp->modes);
1066 if (wme != NULL && wme->mode->resize != NULL)
1067 wme->mode->resize(wme, sx, sy);
1071 window_pane_set_mode(struct window_pane *wp, struct window_pane *swp,
1072 const struct window_mode *mode, struct cmd_find_state *fs,
1073 struct args *args)
1075 struct window_mode_entry *wme;
1077 if (!TAILQ_EMPTY(&wp->modes) && TAILQ_FIRST(&wp->modes)->mode == mode)
1078 return (1);
1080 TAILQ_FOREACH(wme, &wp->modes, entry) {
1081 if (wme->mode == mode)
1082 break;
1084 if (wme != NULL) {
1085 TAILQ_REMOVE(&wp->modes, wme, entry);
1086 TAILQ_INSERT_HEAD(&wp->modes, wme, entry);
1087 } else {
1088 wme = xcalloc(1, sizeof *wme);
1089 wme->wp = wp;
1090 wme->swp = swp;
1091 wme->mode = mode;
1092 wme->prefix = 1;
1093 TAILQ_INSERT_HEAD(&wp->modes, wme, entry);
1094 wme->screen = wme->mode->init(wme, fs, args);
1097 wp->screen = wme->screen;
1098 wp->flags |= (PANE_REDRAW|PANE_CHANGED);
1100 server_redraw_window_borders(wp->window);
1101 server_status_window(wp->window);
1102 notify_pane("pane-mode-changed", wp);
1104 return (0);
1107 void
1108 window_pane_reset_mode(struct window_pane *wp)
1110 struct window_mode_entry *wme, *next;
1112 if (TAILQ_EMPTY(&wp->modes))
1113 return;
1115 wme = TAILQ_FIRST(&wp->modes);
1116 TAILQ_REMOVE(&wp->modes, wme, entry);
1117 wme->mode->free(wme);
1118 free(wme);
1120 next = TAILQ_FIRST(&wp->modes);
1121 if (next == NULL) {
1122 log_debug("%s: no next mode", __func__);
1123 wp->screen = &wp->base;
1124 } else {
1125 log_debug("%s: next mode is %s", __func__, next->mode->name);
1126 wp->screen = next->screen;
1127 if (next->mode->resize != NULL)
1128 next->mode->resize(next, wp->sx, wp->sy);
1130 wp->flags |= (PANE_REDRAW|PANE_CHANGED);
1132 server_redraw_window_borders(wp->window);
1133 server_status_window(wp->window);
1134 notify_pane("pane-mode-changed", wp);
1137 void
1138 window_pane_reset_mode_all(struct window_pane *wp)
1140 while (!TAILQ_EMPTY(&wp->modes))
1141 window_pane_reset_mode(wp);
1144 static void
1145 window_pane_copy_key(struct window_pane *wp, key_code key)
1147 struct window_pane *loop;
1149 TAILQ_FOREACH(loop, &wp->window->panes, entry) {
1150 if (loop != wp &&
1151 TAILQ_EMPTY(&loop->modes) &&
1152 loop->fd != -1 &&
1153 (~loop->flags & PANE_INPUTOFF) &&
1154 window_pane_visible(loop) &&
1155 options_get_number(loop->options, "synchronize-panes"))
1156 input_key_pane(loop, key, NULL);
1161 window_pane_key(struct window_pane *wp, struct client *c, struct session *s,
1162 struct winlink *wl, key_code key, struct mouse_event *m)
1164 struct window_mode_entry *wme;
1166 if (KEYC_IS_MOUSE(key) && m == NULL)
1167 return (-1);
1169 wme = TAILQ_FIRST(&wp->modes);
1170 if (wme != NULL) {
1171 if (wme->mode->key != NULL && c != NULL) {
1172 key &= ~KEYC_MASK_FLAGS;
1173 wme->mode->key(wme, c, s, wl, key, m);
1175 return (0);
1178 if (wp->fd == -1 || wp->flags & PANE_INPUTOFF)
1179 return (0);
1181 if (input_key_pane(wp, key, m) != 0)
1182 return (-1);
1184 if (KEYC_IS_MOUSE(key))
1185 return (0);
1186 if (options_get_number(wp->options, "synchronize-panes"))
1187 window_pane_copy_key(wp, key);
1188 return (0);
1192 window_pane_visible(struct window_pane *wp)
1194 if (~wp->window->flags & WINDOW_ZOOMED)
1195 return (1);
1196 return (wp == wp->window->active);
1199 u_int
1200 window_pane_search(struct window_pane *wp, const char *term, int regex,
1201 int ignore)
1203 struct screen *s = &wp->base;
1204 regex_t r;
1205 char *new = NULL, *line;
1206 u_int i;
1207 int flags = 0, found;
1208 size_t n;
1210 if (!regex) {
1211 if (ignore)
1212 flags |= FNM_CASEFOLD;
1213 xasprintf(&new, "*%s*", term);
1214 } else {
1215 if (ignore)
1216 flags |= REG_ICASE;
1217 if (regcomp(&r, term, flags|REG_EXTENDED) != 0)
1218 return (0);
1221 for (i = 0; i < screen_size_y(s); i++) {
1222 line = grid_view_string_cells(s->grid, 0, i, screen_size_x(s));
1223 for (n = strlen(line); n > 0; n--) {
1224 if (!isspace((u_char)line[n - 1]))
1225 break;
1226 line[n - 1] = '\0';
1228 log_debug("%s: %s", __func__, line);
1229 if (!regex)
1230 found = (fnmatch(new, line, flags) == 0);
1231 else
1232 found = (regexec(&r, line, 0, NULL, 0) == 0);
1233 free(line);
1234 if (found)
1235 break;
1237 if (!regex)
1238 free(new);
1239 else
1240 regfree(&r);
1242 if (i == screen_size_y(s))
1243 return (0);
1244 return (i + 1);
1247 /* Get MRU pane from a list. */
1248 static struct window_pane *
1249 window_pane_choose_best(struct window_pane **list, u_int size)
1251 struct window_pane *next, *best;
1252 u_int i;
1254 if (size == 0)
1255 return (NULL);
1257 best = list[0];
1258 for (i = 1; i < size; i++) {
1259 next = list[i];
1260 if (next->active_point > best->active_point)
1261 best = next;
1263 return (best);
1267 * Find the pane directly above another. We build a list of those adjacent to
1268 * top edge and then choose the best.
1270 struct window_pane *
1271 window_pane_find_up(struct window_pane *wp)
1273 struct window *w;
1274 struct window_pane *next, *best, **list;
1275 u_int edge, left, right, end, size;
1276 int status, found;
1278 if (wp == NULL)
1279 return (NULL);
1280 w = wp->window;
1281 status = options_get_number(w->options, "pane-border-status");
1283 list = NULL;
1284 size = 0;
1286 edge = wp->yoff;
1287 if (status == PANE_STATUS_TOP) {
1288 if (edge == 1)
1289 edge = w->sy + 1;
1290 } else if (status == PANE_STATUS_BOTTOM) {
1291 if (edge == 0)
1292 edge = w->sy;
1293 } else {
1294 if (edge == 0)
1295 edge = w->sy + 1;
1298 left = wp->xoff;
1299 right = wp->xoff + wp->sx;
1301 TAILQ_FOREACH(next, &w->panes, entry) {
1302 if (next == wp)
1303 continue;
1304 if (next->yoff + next->sy + 1 != edge)
1305 continue;
1306 end = next->xoff + next->sx - 1;
1308 found = 0;
1309 if (next->xoff < left && end > right)
1310 found = 1;
1311 else if (next->xoff >= left && next->xoff <= right)
1312 found = 1;
1313 else if (end >= left && end <= right)
1314 found = 1;
1315 if (!found)
1316 continue;
1317 list = xreallocarray(list, size + 1, sizeof *list);
1318 list[size++] = next;
1321 best = window_pane_choose_best(list, size);
1322 free(list);
1323 return (best);
1326 /* Find the pane directly below another. */
1327 struct window_pane *
1328 window_pane_find_down(struct window_pane *wp)
1330 struct window *w;
1331 struct window_pane *next, *best, **list;
1332 u_int edge, left, right, end, size;
1333 int status, found;
1335 if (wp == NULL)
1336 return (NULL);
1337 w = wp->window;
1338 status = options_get_number(w->options, "pane-border-status");
1340 list = NULL;
1341 size = 0;
1343 edge = wp->yoff + wp->sy + 1;
1344 if (status == PANE_STATUS_TOP) {
1345 if (edge >= w->sy)
1346 edge = 1;
1347 } else if (status == PANE_STATUS_BOTTOM) {
1348 if (edge >= w->sy - 1)
1349 edge = 0;
1350 } else {
1351 if (edge >= w->sy)
1352 edge = 0;
1355 left = wp->xoff;
1356 right = wp->xoff + wp->sx;
1358 TAILQ_FOREACH(next, &w->panes, entry) {
1359 if (next == wp)
1360 continue;
1361 if (next->yoff != edge)
1362 continue;
1363 end = next->xoff + next->sx - 1;
1365 found = 0;
1366 if (next->xoff < left && end > right)
1367 found = 1;
1368 else if (next->xoff >= left && next->xoff <= right)
1369 found = 1;
1370 else if (end >= left && end <= right)
1371 found = 1;
1372 if (!found)
1373 continue;
1374 list = xreallocarray(list, size + 1, sizeof *list);
1375 list[size++] = next;
1378 best = window_pane_choose_best(list, size);
1379 free(list);
1380 return (best);
1383 /* Find the pane directly to the left of another. */
1384 struct window_pane *
1385 window_pane_find_left(struct window_pane *wp)
1387 struct window *w;
1388 struct window_pane *next, *best, **list;
1389 u_int edge, top, bottom, end, size;
1390 int found;
1392 if (wp == NULL)
1393 return (NULL);
1394 w = wp->window;
1396 list = NULL;
1397 size = 0;
1399 edge = wp->xoff;
1400 if (edge == 0)
1401 edge = w->sx + 1;
1403 top = wp->yoff;
1404 bottom = wp->yoff + wp->sy;
1406 TAILQ_FOREACH(next, &w->panes, entry) {
1407 if (next == wp)
1408 continue;
1409 if (next->xoff + next->sx + 1 != edge)
1410 continue;
1411 end = next->yoff + next->sy - 1;
1413 found = 0;
1414 if (next->yoff < top && end > bottom)
1415 found = 1;
1416 else if (next->yoff >= top && next->yoff <= bottom)
1417 found = 1;
1418 else if (end >= top && end <= bottom)
1419 found = 1;
1420 if (!found)
1421 continue;
1422 list = xreallocarray(list, size + 1, sizeof *list);
1423 list[size++] = next;
1426 best = window_pane_choose_best(list, size);
1427 free(list);
1428 return (best);
1431 /* Find the pane directly to the right of another. */
1432 struct window_pane *
1433 window_pane_find_right(struct window_pane *wp)
1435 struct window *w;
1436 struct window_pane *next, *best, **list;
1437 u_int edge, top, bottom, end, size;
1438 int found;
1440 if (wp == NULL)
1441 return (NULL);
1442 w = wp->window;
1444 list = NULL;
1445 size = 0;
1447 edge = wp->xoff + wp->sx + 1;
1448 if (edge >= w->sx)
1449 edge = 0;
1451 top = wp->yoff;
1452 bottom = wp->yoff + wp->sy;
1454 TAILQ_FOREACH(next, &w->panes, entry) {
1455 if (next == wp)
1456 continue;
1457 if (next->xoff != edge)
1458 continue;
1459 end = next->yoff + next->sy - 1;
1461 found = 0;
1462 if (next->yoff < top && end > bottom)
1463 found = 1;
1464 else if (next->yoff >= top && next->yoff <= bottom)
1465 found = 1;
1466 else if (end >= top && end <= bottom)
1467 found = 1;
1468 if (!found)
1469 continue;
1470 list = xreallocarray(list, size + 1, sizeof *list);
1471 list[size++] = next;
1474 best = window_pane_choose_best(list, size);
1475 free(list);
1476 return (best);
1479 /* Clear alert flags for a winlink */
1480 void
1481 winlink_clear_flags(struct winlink *wl)
1483 struct winlink *loop;
1485 wl->window->flags &= ~WINDOW_ALERTFLAGS;
1486 TAILQ_FOREACH(loop, &wl->window->winlinks, wentry) {
1487 if ((loop->flags & WINLINK_ALERTFLAGS) != 0) {
1488 loop->flags &= ~WINLINK_ALERTFLAGS;
1489 server_status_session(loop->session);
1494 /* Shuffle window indexes up. */
1496 winlink_shuffle_up(struct session *s, struct winlink *wl, int before)
1498 int idx, last;
1500 if (wl == NULL)
1501 return (-1);
1502 if (before)
1503 idx = wl->idx;
1504 else
1505 idx = wl->idx + 1;
1507 /* Find the next free index. */
1508 for (last = idx; last < INT_MAX; last++) {
1509 if (winlink_find_by_index(&s->windows, last) == NULL)
1510 break;
1512 if (last == INT_MAX)
1513 return (-1);
1515 /* Move everything from last - 1 to idx up a bit. */
1516 for (; last > idx; last--) {
1517 wl = winlink_find_by_index(&s->windows, last - 1);
1518 RB_REMOVE(winlinks, &s->windows, wl);
1519 wl->idx++;
1520 RB_INSERT(winlinks, &s->windows, wl);
1523 return (idx);
1526 static void
1527 window_pane_input_callback(struct client *c, __unused const char *path,
1528 int error, int closed, struct evbuffer *buffer, void *data)
1530 struct window_pane_input_data *cdata = data;
1531 struct window_pane *wp;
1532 u_char *buf = EVBUFFER_DATA(buffer);
1533 size_t len = EVBUFFER_LENGTH(buffer);
1535 wp = window_pane_find_by_id(cdata->wp);
1536 if (wp == NULL || closed || error != 0 || (c->flags & CLIENT_DEAD)) {
1537 if (wp == NULL)
1538 c->flags |= CLIENT_EXIT;
1540 evbuffer_drain(buffer, len);
1541 cmdq_continue(cdata->item);
1543 server_client_unref(c);
1544 free(cdata);
1545 return;
1547 input_parse_buffer(wp, buf, len);
1548 evbuffer_drain(buffer, len);
1552 window_pane_start_input(struct window_pane *wp, struct cmdq_item *item,
1553 char **cause)
1555 struct client *c = cmdq_get_client(item);
1556 struct window_pane_input_data *cdata;
1558 if (~wp->flags & PANE_EMPTY) {
1559 *cause = xstrdup("pane is not empty");
1560 return (-1);
1562 if (c->flags & (CLIENT_DEAD|CLIENT_EXITED))
1563 return (1);
1564 if (c->session != NULL)
1565 return (1);
1567 cdata = xmalloc(sizeof *cdata);
1568 cdata->item = item;
1569 cdata->wp = wp->id;
1571 c->references++;
1572 file_read(c, "-", window_pane_input_callback, cdata);
1574 return (0);
1577 void *
1578 window_pane_get_new_data(struct window_pane *wp,
1579 struct window_pane_offset *wpo, size_t *size)
1581 size_t used = wpo->used - wp->base_offset;
1583 *size = EVBUFFER_LENGTH(wp->event->input) - used;
1584 return (EVBUFFER_DATA(wp->event->input) + used);
1587 void
1588 window_pane_update_used_data(struct window_pane *wp,
1589 struct window_pane_offset *wpo, size_t size)
1591 size_t used = wpo->used - wp->base_offset;
1593 if (size > EVBUFFER_LENGTH(wp->event->input) - used)
1594 size = EVBUFFER_LENGTH(wp->event->input) - used;
1595 wpo->used += size;
1598 void
1599 window_set_fill_character(struct window *w)
1601 const char *value;
1602 struct utf8_data *ud;
1604 free(w->fill_character);
1605 w->fill_character = NULL;
1607 value = options_get_string(w->options, "fill-character");
1608 if (*value != '\0' && utf8_isvalid(value)) {
1609 ud = utf8_fromcstr(value);
1610 if (ud != NULL && ud[0].width == 1)
1611 w->fill_character = ud;
1615 void
1616 window_pane_default_cursor(struct window_pane *wp)
1618 struct screen *s = wp->screen;
1619 int c;
1621 c = options_get_number(wp->options, "cursor-colour");
1622 s->default_ccolour = c;
1624 c = options_get_number(wp->options, "cursor-style");
1625 s->default_mode = 0;
1626 screen_set_cursor_style(c, &s->default_cstyle, &s->default_mode);