xref: /openbsd-src/usr.bin/tmux/window.c (revision 43003dfe3ad45d1698bed8a37f2b0f5b14f20d4f)
1 /* $OpenBSD: window.c,v 1.32 2009/10/11 10:04:27 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 RB_GENERATE(winlinks, winlink, entry, winlink_cmp);
60 
61 int
62 winlink_cmp(struct winlink *wl1, struct winlink *wl2)
63 {
64 	return (wl1->idx - wl2->idx);
65 }
66 
67 struct winlink *
68 winlink_find_by_window(struct winlinks *wwl, struct window *w)
69 {
70 	struct winlink	*wl;
71 
72 	RB_FOREACH(wl, winlinks, wwl) {
73 		if (wl->window == w)
74 			return (wl);
75 	}
76 
77 	return (NULL);
78 }
79 
80 struct winlink *
81 winlink_find_by_index(struct winlinks *wwl, int idx)
82 {
83 	struct winlink	wl;
84 
85 	if (idx < 0)
86 		fatalx("bad index");
87 
88 	wl.idx = idx;
89 	return (RB_FIND(winlinks, wwl, &wl));
90 }
91 
92 int
93 winlink_next_index(struct winlinks *wwl, int idx)
94 {
95 	int	i;
96 
97 	i = idx;
98 	do {
99 		if (winlink_find_by_index(wwl, i) == NULL)
100 			return (i);
101 		if (i == INT_MAX)
102 			i = 0;
103 		else
104 			i++;
105 	} while (i != idx);
106 	return (-1);
107 }
108 
109 u_int
110 winlink_count(struct winlinks *wwl)
111 {
112 	struct winlink	*wl;
113 	u_int		 n;
114 
115 	n = 0;
116 	RB_FOREACH(wl, winlinks, wwl)
117 		n++;
118 
119 	return (n);
120 }
121 
122 struct winlink *
123 winlink_add(struct winlinks *wwl, struct window *w, int idx)
124 {
125 	struct winlink	*wl;
126 
127 	if (idx < 0) {
128 		if ((idx = winlink_next_index(wwl, -idx - 1)) == -1)
129 			return (NULL);
130 	} else if (winlink_find_by_index(wwl, idx) != NULL)
131 		return (NULL);
132 
133 	wl = xcalloc(1, sizeof *wl);
134 	wl->idx = idx;
135 	wl->window = w;
136 	RB_INSERT(winlinks, wwl, wl);
137 
138 	w->references++;
139 
140 	return (wl);
141 }
142 
143 void
144 winlink_remove(struct winlinks *wwl, struct winlink *wl)
145 {
146 	struct window	*w = wl->window;
147 
148 	RB_REMOVE(winlinks, wwl, wl);
149 	xfree(wl);
150 
151 	if (w->references == 0)
152 		fatal("bad reference count");
153 	w->references--;
154 	if (w->references == 0)
155 		window_destroy(w);
156 }
157 
158 struct winlink *
159 winlink_next(unused struct winlinks *wwl, struct winlink *wl)
160 {
161 	return (RB_NEXT(winlinks, wwl, wl));
162 }
163 
164 struct winlink *
165 winlink_previous(unused struct winlinks *wwl, struct winlink *wl)
166 {
167 	return (RB_PREV(winlinks, wwl, wl));
168 }
169 
170 void
171 winlink_stack_push(struct winlink_stack *stack, struct winlink *wl)
172 {
173 	if (wl == NULL)
174 		return;
175 
176 	winlink_stack_remove(stack, wl);
177 	TAILQ_INSERT_HEAD(stack, wl, sentry);
178 }
179 
180 void
181 winlink_stack_remove(struct winlink_stack *stack, struct winlink *wl)
182 {
183 	struct winlink	*wl2;
184 
185 	if (wl == NULL)
186 		return;
187 
188 	TAILQ_FOREACH(wl2, stack, sentry) {
189 		if (wl2 == wl) {
190 			TAILQ_REMOVE(stack, wl, sentry);
191 			return;
192 		}
193 	}
194 }
195 
196 int
197 window_index(struct window *s, u_int *i)
198 {
199 	for (*i = 0; *i < ARRAY_LENGTH(&windows); (*i)++) {
200 		if (s == ARRAY_ITEM(&windows, *i))
201 			return (0);
202 	}
203 	return (-1);
204 }
205 
206 struct window *
207 window_create1(u_int sx, u_int sy)
208 {
209 	struct window	*w;
210 	u_int		 i;
211 
212 	w = xmalloc(sizeof *w);
213 	w->name = NULL;
214 	w->flags = 0;
215 
216 	TAILQ_INIT(&w->panes);
217 	w->active = NULL;
218 
219 	w->lastlayout = -1;
220 	w->layout_root = NULL;
221 
222 	w->sx = sx;
223 	w->sy = sy;
224 
225 	options_init(&w->options, &global_w_options);
226 
227 	for (i = 0; i < ARRAY_LENGTH(&windows); i++) {
228 		if (ARRAY_ITEM(&windows, i) == NULL) {
229 			ARRAY_SET(&windows, i, w);
230 			break;
231 		}
232 	}
233 	if (i == ARRAY_LENGTH(&windows))
234 		ARRAY_ADD(&windows, w);
235 	w->references = 0;
236 
237 	return (w);
238 }
239 
240 struct window *
241 window_create(const char *name, const char *cmd, const char *shell,
242     const char *cwd, struct environ *env, struct termios *tio,
243     u_int sx, u_int sy, u_int hlimit,char **cause)
244 {
245 	struct window		*w;
246 	struct window_pane	*wp;
247 
248 	w = window_create1(sx, sy);
249 	wp = window_add_pane(w, hlimit);
250 	layout_init(w);
251 	if (window_pane_spawn(wp, cmd, shell, cwd, env, tio, cause) != 0) {
252 		window_destroy(w);
253 		return (NULL);
254 	}
255 	w->active = TAILQ_FIRST(&w->panes);
256 	if (name != NULL) {
257 		w->name = xstrdup(name);
258 		options_set_number(&w->options, "automatic-rename", 0);
259 	} else
260 		w->name = default_window_name(w);
261 	return (w);
262 }
263 
264 void
265 window_destroy(struct window *w)
266 {
267 	u_int	i;
268 
269 	if (window_index(w, &i) != 0)
270 		fatalx("index not found");
271 	ARRAY_SET(&windows, i, NULL);
272 	while (!ARRAY_EMPTY(&windows) && ARRAY_LAST(&windows) == NULL)
273 		ARRAY_TRUNC(&windows, 1);
274 
275 	if (w->layout_root != NULL)
276 		layout_free(w);
277 
278 	options_free(&w->options);
279 
280 	window_destroy_panes(w);
281 
282 	if (w->name != NULL)
283 		xfree(w->name);
284 	xfree(w);
285 }
286 
287 void
288 window_resize(struct window *w, u_int sx, u_int sy)
289 {
290 	w->sx = sx;
291 	w->sy = sy;
292 }
293 
294 void
295 window_set_active_pane(struct window *w, struct window_pane *wp)
296 {
297 	w->active = wp;
298 	while (!window_pane_visible(w->active)) {
299 		w->active = TAILQ_PREV(w->active, window_panes, entry);
300 		if (w->active == NULL)
301 			w->active = TAILQ_LAST(&w->panes, window_panes);
302 		if (w->active == wp)
303 			return;
304 	}
305 }
306 
307 void
308 window_set_active_at(struct window *w, u_int x, u_int y)
309 {
310 	struct window_pane	*wp;
311 
312 	TAILQ_FOREACH(wp, &w->panes, entry) {
313 		if (!window_pane_visible(wp))
314 			continue;
315 		if (x < wp->xoff || x >= wp->xoff + wp->sx)
316 			continue;
317 		if (y < wp->yoff || y >= wp->yoff + wp->sy)
318 			continue;
319 		window_set_active_pane(w, wp);
320 		break;
321 	}
322 }
323 
324 struct window_pane *
325 window_add_pane(struct window *w, u_int hlimit)
326 {
327 	struct window_pane	*wp;
328 
329 	wp = window_pane_create(w, w->sx, w->sy, hlimit);
330 	if (TAILQ_EMPTY(&w->panes))
331 		TAILQ_INSERT_HEAD(&w->panes, wp, entry);
332 	else
333 		TAILQ_INSERT_AFTER(&w->panes, w->active, wp, entry);
334 	return (wp);
335 }
336 
337 void
338 window_remove_pane(struct window *w, struct window_pane *wp)
339 {
340 	w->active = TAILQ_PREV(wp, window_panes, entry);
341 	if (w->active == NULL)
342 		w->active = TAILQ_NEXT(wp, entry);
343 
344 	TAILQ_REMOVE(&w->panes, wp, entry);
345 	window_pane_destroy(wp);
346 }
347 
348 struct window_pane *
349 window_pane_at_index(struct window *w, u_int idx)
350 {
351 	struct window_pane	*wp;
352 	u_int			 n;
353 
354 	n = 0;
355 	TAILQ_FOREACH(wp, &w->panes, entry) {
356 		if (n == idx)
357 			return (wp);
358 		n++;
359 	}
360 	return (NULL);
361 }
362 
363 u_int
364 window_pane_index(struct window *w, struct window_pane *wp)
365 {
366 	struct window_pane	*wq;
367 	u_int			 n;
368 
369 	n = 0;
370 	TAILQ_FOREACH(wq, &w->panes, entry) {
371 		if (wp == wq)
372 			break;
373 		n++;
374 	}
375 	return (n);
376 }
377 
378 u_int
379 window_count_panes(struct window *w)
380 {
381 	struct window_pane	*wp;
382 	u_int			 n;
383 
384 	n = 0;
385 	TAILQ_FOREACH(wp, &w->panes, entry)
386 		n++;
387 	return (n);
388 }
389 
390 void
391 window_destroy_panes(struct window *w)
392 {
393 	struct window_pane	*wp;
394 
395 	while (!TAILQ_EMPTY(&w->panes)) {
396 		wp = TAILQ_FIRST(&w->panes);
397 		TAILQ_REMOVE(&w->panes, wp, entry);
398 		window_pane_destroy(wp);
399 	}
400 }
401 
402 struct window_pane *
403 window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit)
404 {
405 	struct window_pane	*wp;
406 
407 	wp = xcalloc(1, sizeof *wp);
408 	wp->window = w;
409 
410 	wp->cmd = NULL;
411 	wp->shell = NULL;
412 	wp->cwd = NULL;
413 
414 	wp->fd = -1;
415 	wp->in = buffer_create(BUFSIZ);
416 	wp->out = buffer_create(BUFSIZ);
417 
418 	wp->mode = NULL;
419 
420 	wp->layout_cell = NULL;
421 
422 	wp->xoff = 0;
423  	wp->yoff = 0;
424 
425 	wp->sx = sx;
426 	wp->sy = sy;
427 
428 	wp->pipe_fd = -1;
429 	wp->pipe_buf = NULL;
430 	wp->pipe_off = 0;
431 
432 	wp->saved_grid = NULL;
433 
434 	screen_init(&wp->base, sx, sy, hlimit);
435 	wp->screen = &wp->base;
436 
437 	input_init(wp);
438 
439 	return (wp);
440 }
441 
442 void
443 window_pane_destroy(struct window_pane *wp)
444 {
445 	if (wp->fd != -1)
446 		close(wp->fd);
447 
448 	input_free(wp);
449 
450 	window_pane_reset_mode(wp);
451 	screen_free(&wp->base);
452 	if (wp->saved_grid != NULL)
453 		grid_destroy(wp->saved_grid);
454 
455 	if (wp->pipe_fd != -1) {
456 		buffer_destroy(wp->pipe_buf);
457 		close(wp->pipe_fd);
458 	}
459 
460 	buffer_destroy(wp->in);
461 	buffer_destroy(wp->out);
462 
463 	if (wp->cwd != NULL)
464 		xfree(wp->cwd);
465 	if (wp->shell != NULL)
466 		xfree(wp->shell);
467 	if (wp->cmd != NULL)
468 		xfree(wp->cmd);
469 	xfree(wp);
470 }
471 
472 int
473 window_pane_spawn(struct window_pane *wp, const char *cmd, const char *shell,
474     const char *cwd, struct environ *env, struct termios *tio, char **cause)
475 {
476 	struct winsize	 	 ws;
477 	int		 	 mode;
478 	char			*argv0, **varp, *var;
479 	ARRAY_DECL(, char *)	 varlist;
480 	struct environ_entry	*envent;
481 	const char		*ptr;
482 	struct timeval	 	 tv;
483 	struct termios		 tio2;
484 	u_int		 	 i;
485 
486 	if (wp->fd != -1)
487 		close(wp->fd);
488 	if (cmd != NULL) {
489 		if (wp->cmd != NULL)
490 			xfree(wp->cmd);
491 		wp->cmd = xstrdup(cmd);
492 	}
493 	if (shell != NULL) {
494 		if (wp->shell != NULL)
495 			xfree(wp->shell);
496 		wp->shell = xstrdup(shell);
497 	}
498 	if (cwd != NULL) {
499 		if (wp->cwd != NULL)
500 			xfree(wp->cwd);
501 		wp->cwd = xstrdup(cwd);
502 	}
503 
504 	memset(&ws, 0, sizeof ws);
505 	ws.ws_col = screen_size_x(&wp->base);
506 	ws.ws_row = screen_size_y(&wp->base);
507 
508 	if (gettimeofday(&wp->window->name_timer, NULL) != 0)
509 		fatal("gettimeofday failed");
510 	tv.tv_sec = 0;
511 	tv.tv_usec = NAME_INTERVAL * 1000L;
512 	timeradd(&wp->window->name_timer, &tv, &wp->window->name_timer);
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 		sigreset();
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 
577 	return (0);
578 }
579 
580 void
581 window_pane_resize(struct window_pane *wp, u_int sx, u_int sy)
582 {
583 	struct winsize	ws;
584 
585 	if (sx == wp->sx && sy == wp->sy)
586 		return;
587 	wp->sx = sx;
588 	wp->sy = sy;
589 
590 	memset(&ws, 0, sizeof ws);
591 	ws.ws_col = sx;
592 	ws.ws_row = sy;
593 
594 	screen_resize(&wp->base, sx, sy);
595 	if (wp->mode != NULL)
596 		wp->mode->resize(wp, sx, sy);
597 
598 	if (wp->fd != -1 && ioctl(wp->fd, TIOCSWINSZ, &ws) == -1)
599 		fatal("ioctl failed");
600 }
601 
602 int
603 window_pane_set_mode(struct window_pane *wp, const struct window_mode *mode)
604 {
605 	struct screen	*s;
606 
607 	if (wp->mode != NULL)
608 		return (1);
609 	wp->mode = mode;
610 
611 	if ((s = wp->mode->init(wp)) != NULL)
612 		wp->screen = s;
613 	server_redraw_window(wp->window);
614 	return (0);
615 }
616 
617 void
618 window_pane_reset_mode(struct window_pane *wp)
619 {
620 	if (wp->mode == NULL)
621 		return;
622 
623 	wp->mode->free(wp);
624 	wp->mode = NULL;
625 
626 	wp->screen = &wp->base;
627 	server_redraw_window(wp->window);
628 }
629 
630 void
631 window_pane_parse(struct window_pane *wp)
632 {
633 	size_t	new_size;
634 
635 	new_size = BUFFER_USED(wp->in) - wp->pipe_off;
636 	if (wp->pipe_fd != -1 && new_size > 0)
637 		buffer_write(wp->pipe_buf, BUFFER_OUT(wp->in), new_size);
638 
639 	input_parse(wp);
640 
641 	wp->pipe_off = BUFFER_USED(wp->in);
642 }
643 
644 void
645 window_pane_key(struct window_pane *wp, struct client *c, int key)
646 {
647 	struct window_pane	*wp2;
648 
649 	if (!window_pane_visible(wp))
650 		return;
651 
652 	if (wp->mode != NULL) {
653 		if (wp->mode->key != NULL)
654 			wp->mode->key(wp, c, key);
655 		return;
656 	}
657 
658 	if (wp->fd == -1)
659 		return;
660 	input_key(wp, key);
661 	if (options_get_number(&wp->window->options, "synchronize-panes")) {
662 		TAILQ_FOREACH(wp2, &wp->window->panes, entry) {
663 			if (wp2 == wp || wp2->mode != NULL)
664 				continue;
665 			if (wp2->fd != -1 && window_pane_visible(wp2))
666 				input_key(wp2, key);
667 		}
668 	}
669 }
670 
671 void
672 window_pane_mouse(
673     struct window_pane *wp, struct client *c, struct mouse_event *m)
674 {
675 	if (!window_pane_visible(wp))
676 		return;
677 
678 	if (m->x < wp->xoff || m->x >= wp->xoff + wp->sx)
679 		return;
680 	if (m->y < wp->yoff || m->y >= wp->yoff + wp->sy)
681 		return;
682 	m->x -= wp->xoff;
683 	m->y -= wp->yoff;
684 
685 	if (wp->mode != NULL) {
686 		if (wp->mode->mouse != NULL)
687 			wp->mode->mouse(wp, c, m);
688 	} else if (wp->fd != -1)
689 		input_mouse(wp, m);
690 }
691 
692 int
693 window_pane_visible(struct window_pane *wp)
694 {
695 	struct window	*w = wp->window;
696 
697 	if (wp->xoff >= w->sx || wp->yoff >= w->sy)
698 		return (0);
699 	if (wp->xoff + wp->sx > w->sx || wp->yoff + wp->sy > w->sy)
700 		return (0);
701 	return (1);
702 }
703 
704 char *
705 window_pane_search(struct window_pane *wp, const char *searchstr, u_int *lineno)
706 {
707 	struct screen	*s = &wp->base;
708 	char		*newsearchstr, *line, *msg;
709 	u_int	 	 i;
710 
711 	msg = NULL;
712 	xasprintf(&newsearchstr, "*%s*", searchstr);
713 
714 	for (i = 0; i < screen_size_y(s); i++) {
715 		line = grid_view_string_cells(s->grid, 0, i, screen_size_x(s));
716 		if (fnmatch(newsearchstr, line, 0) == 0) {
717 			msg = line;
718 			if (lineno != NULL)
719 				*lineno = i;
720 			break;
721 		}
722 		xfree(line);
723 	}
724 
725 	xfree(newsearchstr);
726 	return (msg);
727 }
728