Annotation of src/usr.bin/tmux/window.c, Revision 1.2
1.2 ! nicm 1: /* $OpenBSD: window.c,v 1.1 2009/06/01 22:58:49 nicm Exp $ */
1.1 nicm 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 <paths.h>
25: #include <signal.h>
26: #include <stdint.h>
27: #include <stdlib.h>
28: #include <string.h>
29: #include <termios.h>
30: #include <unistd.h>
31: #include <util.h>
32:
33: #include "tmux.h"
34:
35: /*
36: * Each window is attached to one or two panes, each of which is a pty. This
37: * file contains code to handle them.
38: *
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.
44: *
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.
47: *
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.
52: */
53:
54: /* Global window list. */
55: struct windows windows;
56:
57: RB_GENERATE(winlinks, winlink, entry, winlink_cmp);
58:
59: int
60: winlink_cmp(struct winlink *wl1, struct winlink *wl2)
61: {
62: return (wl1->idx - wl2->idx);
63: }
64:
65: struct winlink *
66: winlink_find_by_index(struct winlinks *wwl, int idx)
67: {
68: struct winlink wl;
69:
70: if (idx < 0)
71: fatalx("bad index");
72:
73: wl.idx = idx;
74: return (RB_FIND(winlinks, wwl, &wl));
75: }
76:
77: int
78: winlink_next_index(struct winlinks *wwl)
79: {
80: u_int i;
81:
82: for (i = 0; i < INT_MAX; i++) {
83: if (winlink_find_by_index(wwl, i) == NULL)
84: return (i);
85: }
86:
87: fatalx("no free indexes");
88: }
89:
90: u_int
91: winlink_count(struct winlinks *wwl)
92: {
93: struct winlink *wl;
94: u_int n;
95:
96: n = 0;
97: RB_FOREACH(wl, winlinks, wwl)
98: n++;
99:
100: return (n);
101: }
102:
103: struct winlink *
104: winlink_add(struct winlinks *wwl, struct window *w, int idx)
105: {
106: struct winlink *wl;
107:
108: if (idx == -1)
109: idx = winlink_next_index(wwl);
110: else if (winlink_find_by_index(wwl, idx) != NULL)
111: return (NULL);
112:
113: if (idx < 0)
114: fatalx("bad index");
115:
116: wl = xcalloc(1, sizeof *wl);
117: wl->idx = idx;
118: wl->window = w;
119: RB_INSERT(winlinks, wwl, wl);
120:
121: w->references++;
122:
123: return (wl);
124: }
125:
126: void
127: winlink_remove(struct winlinks *wwl, struct winlink *wl)
128: {
129: struct window *w = wl->window;
130:
131: RB_REMOVE(winlinks, wwl, wl);
132: xfree(wl);
133:
134: if (w->references == 0)
135: fatal("bad reference count");
136: w->references--;
137: if (w->references == 0)
138: window_destroy(w);
139: }
140:
141: struct winlink *
142: winlink_next(unused struct winlinks *wwl, struct winlink *wl)
143: {
144: return (RB_NEXT(winlinks, wwl, wl));
145: }
146:
147: struct winlink *
148: winlink_previous(unused struct winlinks *wwl, struct winlink *wl)
149: {
150: return (RB_PREV(winlinks, wwl, wl));
151: }
152:
153: void
154: winlink_stack_push(struct winlink_stack *stack, struct winlink *wl)
155: {
156: if (wl == NULL)
157: return;
158:
159: winlink_stack_remove(stack, wl);
160: SLIST_INSERT_HEAD(stack, wl, sentry);
161: }
162:
163: void
164: winlink_stack_remove(struct winlink_stack *stack, struct winlink *wl)
165: {
166: struct winlink *wl2;
167:
168: if (wl == NULL)
169: return;
170:
171: SLIST_FOREACH(wl2, stack, sentry) {
172: if (wl2 == wl) {
173: SLIST_REMOVE(stack, wl, winlink, sentry);
174: return;
175: }
176: }
177: }
178:
179: int
180: window_index(struct window *s, u_int *i)
181: {
182: for (*i = 0; *i < ARRAY_LENGTH(&windows); (*i)++) {
183: if (s == ARRAY_ITEM(&windows, *i))
184: return (0);
185: }
186: return (-1);
187: }
188:
189: struct window *
190: window_create1(u_int sx, u_int sy)
191: {
192: struct window *w;
193: u_int i;
194:
195: w = xmalloc(sizeof *w);
196: w->name = NULL;
197: w->flags = 0;
198:
199: TAILQ_INIT(&w->panes);
200: w->active = NULL;
201: w->layout = 0;
202:
203: w->sx = sx;
204: w->sy = sy;
205:
206: options_init(&w->options, &global_window_options);
207:
208: for (i = 0; i < ARRAY_LENGTH(&windows); i++) {
209: if (ARRAY_ITEM(&windows, i) == NULL) {
210: ARRAY_SET(&windows, i, w);
211: break;
212: }
213: }
214: if (i == ARRAY_LENGTH(&windows))
215: ARRAY_ADD(&windows, w);
216: w->references = 0;
217:
218: return (w);
219: }
220:
221: struct window *
222: window_create(const char *name, const char *cmd, const char *cwd,
223: const char **envp, u_int sx, u_int sy, u_int hlimit, char **cause)
224: {
225: struct window *w;
226:
227: w = window_create1(sx, sy);
228: if (window_add_pane(w, -1, cmd, cwd, envp, hlimit, cause) == NULL) {
229: window_destroy(w);
230: return (NULL);
231: }
232: w->active = TAILQ_FIRST(&w->panes);
233:
234: if (name != NULL) {
235: w->name = xstrdup(name);
236: options_set_number(&w->options, "automatic-rename", 0);
237: } else
238: w->name = default_window_name(w);
239: return (w);
240: }
241:
242: void
243: window_destroy(struct window *w)
244: {
245: u_int i;
246:
247: if (window_index(w, &i) != 0)
248: fatalx("index not found");
249: ARRAY_SET(&windows, i, NULL);
250: while (!ARRAY_EMPTY(&windows) && ARRAY_LAST(&windows) == NULL)
251: ARRAY_TRUNC(&windows, 1);
252:
253: options_free(&w->options);
254:
255: window_destroy_panes(w);
256:
257: if (w->name != NULL)
258: xfree(w->name);
259: xfree(w);
260: }
261:
262: int
263: window_resize(struct window *w, u_int sx, u_int sy)
264: {
265: w->sx = sx;
266: w->sy = sy;
267:
268: return (0);
269: }
270:
271: void
272: window_set_active_pane(struct window *w, struct window_pane *wp)
273: {
274: w->active = wp;
275: while (w->active->flags & PANE_HIDDEN)
276: w->active = TAILQ_PREV(w->active, window_panes, entry);
277: }
278:
279: struct window_pane *
280: window_add_pane(struct window *w, int wanty, const char *cmd,
281: const char *cwd, const char **envp, u_int hlimit, char **cause)
282: {
283: struct window_pane *wp;
284: u_int sizey;
285:
286: if (TAILQ_EMPTY(&w->panes))
287: wanty = w->sy;
288: else {
289: sizey = w->active->sy - 1; /* for separator */
290: if (sizey < PANE_MINIMUM * 2) {
291: *cause = xstrdup("pane too small");
292: return (NULL);
293: }
294:
295: if (wanty == -1)
296: wanty = sizey / 2;
297:
298: if (wanty < PANE_MINIMUM)
299: wanty = PANE_MINIMUM;
300: if ((u_int) wanty > sizey - PANE_MINIMUM)
301: wanty = sizey - PANE_MINIMUM;
302:
303: window_pane_resize(w->active, w->sx, sizey - wanty);
304: }
305:
306: wp = window_pane_create(w, w->sx, wanty, hlimit);
307: if (TAILQ_EMPTY(&w->panes))
308: TAILQ_INSERT_HEAD(&w->panes, wp, entry);
309: else
310: TAILQ_INSERT_AFTER(&w->panes, w->active, wp, entry);
311: if (window_pane_spawn(wp, cmd, cwd, envp, cause) != 0) {
312: window_remove_pane(w, wp);
313: return (NULL);
314: }
315: return (wp);
316: }
317:
318: void
319: window_remove_pane(struct window *w, struct window_pane *wp)
320: {
321: w->active = TAILQ_PREV(wp, window_panes, entry);
322: if (w->active == NULL)
323: w->active = TAILQ_NEXT(wp, entry);
324:
325: TAILQ_REMOVE(&w->panes, wp, entry);
326: window_pane_destroy(wp);
327: }
328:
329: u_int
330: window_index_of_pane(struct window *w, struct window_pane *find)
331: {
332: struct window_pane *wp;
333: u_int n;
334:
335: n = 0;
336: TAILQ_FOREACH(wp, &w->panes, entry) {
337: if (wp == find)
338: return (n);
339: n++;
340: }
341: fatalx("unknown pane");
342: }
343:
344: struct window_pane *
345: window_pane_at_index(struct window *w, u_int idx)
346: {
347: struct window_pane *wp;
348: u_int n;
349:
350: n = 0;
351: TAILQ_FOREACH(wp, &w->panes, entry) {
352: if (n == idx)
353: return (wp);
354: n++;
355: }
356: return (NULL);
357: }
358:
359: u_int
360: window_count_panes(struct window *w)
361: {
362: struct window_pane *wp;
363: u_int n;
364:
365: n = 0;
366: TAILQ_FOREACH(wp, &w->panes, entry)
367: n++;
368: return (n);
369: }
370:
371: void
372: window_destroy_panes(struct window *w)
373: {
374: struct window_pane *wp;
375:
376: while (!TAILQ_EMPTY(&w->panes)) {
377: wp = TAILQ_FIRST(&w->panes);
378: TAILQ_REMOVE(&w->panes, wp, entry);
379: window_pane_destroy(wp);
380: }
381: }
382:
383: struct window_pane *
384: window_pane_create(struct window *w, u_int sx, u_int sy, u_int hlimit)
385: {
386: struct window_pane *wp;
387:
388: wp = xcalloc(1, sizeof *wp);
389: wp->window = w;
390:
391: wp->cmd = NULL;
392: wp->cwd = NULL;
393:
394: wp->fd = -1;
395: wp->in = buffer_create(BUFSIZ);
396: wp->out = buffer_create(BUFSIZ);
397:
398: wp->mode = NULL;
399:
400: wp->xoff = 0;
401: wp->yoff = 0;
402:
403: wp->sx = sx;
404: wp->sy = sy;
405:
406: screen_init(&wp->base, sx, sy, hlimit);
407: wp->screen = &wp->base;
408:
409: input_init(wp);
410:
411: return (wp);
412: }
413:
414: void
415: window_pane_destroy(struct window_pane *wp)
416: {
417: if (wp->fd != -1)
418: close(wp->fd);
419:
420: input_free(wp);
421:
422: window_pane_reset_mode(wp);
423: screen_free(&wp->base);
424:
425: buffer_destroy(wp->in);
426: buffer_destroy(wp->out);
427:
428: if (wp->cwd != NULL)
429: xfree(wp->cwd);
430: if (wp->cmd != NULL)
431: xfree(wp->cmd);
432: xfree(wp);
433: }
434:
435: int
436: window_pane_spawn(struct window_pane *wp,
437: const char *cmd, const char *cwd, const char **envp, char **cause)
438: {
439: struct winsize ws;
440: int mode;
441: const char **envq;
442: struct timeval tv;
443:
444: if (wp->fd != -1)
445: close(wp->fd);
446: if (cmd != NULL) {
447: if (wp->cmd != NULL)
448: xfree(wp->cmd);
449: wp->cmd = xstrdup(cmd);
450: }
451: if (cwd != NULL) {
452: if (wp->cwd != NULL)
453: xfree(wp->cwd);
454: wp->cwd = xstrdup(cwd);
455: }
456:
457: memset(&ws, 0, sizeof ws);
458: ws.ws_col = screen_size_x(&wp->base);
459: ws.ws_row = screen_size_y(&wp->base);
460:
461: if (gettimeofday(&wp->window->name_timer, NULL) != 0)
462: fatal("gettimeofday");
463: tv.tv_sec = 0;
464: tv.tv_usec = NAME_INTERVAL * 1000L;
465: timeradd(&wp->window->name_timer, &tv, &wp->window->name_timer);
466:
467: switch (wp->pid = forkpty(&wp->fd, wp->tty, NULL, &ws)) {
468: case -1:
469: wp->fd = -1;
470: xasprintf(cause, "%s: %s", cmd, strerror(errno));
471: return (-1);
472: case 0:
473: if (chdir(wp->cwd) != 0)
474: chdir("/");
475: for (envq = envp; *envq != NULL; envq++) {
1.2 ! nicm 476: if (putenv(xstrdup(*envq)) != 0)
1.1 nicm 477: fatal("putenv failed");
478: }
479: sigreset();
480: log_close();
481:
482: execl(_PATH_BSHELL, "sh", "-c", wp->cmd, (char *) NULL);
483: fatal("execl failed");
484: }
485:
486: if ((mode = fcntl(wp->fd, F_GETFL)) == -1)
487: fatal("fcntl failed");
488: if (fcntl(wp->fd, F_SETFL, mode|O_NONBLOCK) == -1)
489: fatal("fcntl failed");
490: if (fcntl(wp->fd, F_SETFD, FD_CLOEXEC) == -1)
491: fatal("fcntl failed");
492:
493: return (0);
494: }
495:
496: int
497: window_pane_resize(struct window_pane *wp, u_int sx, u_int sy)
498: {
499: struct winsize ws;
500:
501: if (sx == wp->sx && sy == wp->sy)
502: return (-1);
503: wp->sx = sx;
504: wp->sy = sy;
505:
506: memset(&ws, 0, sizeof ws);
507: ws.ws_col = sx;
508: ws.ws_row = sy;
509:
510: screen_resize(&wp->base, sx, sy);
511: if (wp->mode != NULL)
512: wp->mode->resize(wp, sx, sy);
513:
514: if (wp->fd != -1 && ioctl(wp->fd, TIOCSWINSZ, &ws) == -1)
515: fatal("ioctl failed");
516: return (0);
517: }
518:
519: int
520: window_pane_set_mode(struct window_pane *wp, const struct window_mode *mode)
521: {
522: struct screen *s;
523:
524: if (wp->mode != NULL || wp->mode == mode)
525: return (1);
526:
527: wp->mode = mode;
528:
529: if ((s = wp->mode->init(wp)) != NULL)
530: wp->screen = s;
531: server_redraw_window(wp->window);
532: return (0);
533: }
534:
535: void
536: window_pane_reset_mode(struct window_pane *wp)
537: {
538: if (wp->mode == NULL)
539: return;
540:
541: wp->mode->free(wp);
542: wp->mode = NULL;
543:
544: wp->screen = &wp->base;
545: server_redraw_window(wp->window);
546: }
547:
548: void
549: window_pane_parse(struct window_pane *wp)
550: {
551: input_parse(wp);
552: }
553:
554: void
555: window_pane_key(struct window_pane *wp, struct client *c, int key)
556: {
557: if (wp->mode != NULL) {
558: if (wp->mode->key != NULL)
559: wp->mode->key(wp, c, key);
560: } else
561: input_key(wp, key);
562: }
563:
564: void
565: window_pane_mouse(
566: struct window_pane *wp, struct client *c, u_char b, u_char x, u_char y)
567: {
568: /* XXX convert from 1-based? */
569:
570: if (x < wp->xoff || x >= wp->xoff + wp->sx)
571: return;
572: if (y < wp->yoff || y >= wp->yoff + wp->sy)
573: return;
574: x -= wp->xoff;
575: y -= wp->yoff;
576:
577: if (wp->mode != NULL) {
578: if (wp->mode->mouse != NULL)
579: wp->mode->mouse(wp, c, b, x, y);
580: } else
581: input_mouse(wp, b, x, y);
582: }
583:
584: char *
585: window_pane_search(struct window_pane *wp, const char *searchstr)
586: {
587: const struct grid_cell *gc;
588: const struct grid_utf8 *gu;
589: char *buf, *s;
590: size_t off;
591: u_int i, j, k;
592:
593: buf = xmalloc(1);
594:
595: for (j = 0; j < screen_size_y(&wp->base); j++) {
596: off = 0;
597: for (i = 0; i < screen_size_x(&wp->base); i++) {
598: gc = grid_view_peek_cell(wp->base.grid, i, j);
599: if (gc->flags & GRID_FLAG_UTF8) {
600: gu = grid_view_peek_utf8(wp->base.grid, i, j);
601: buf = xrealloc(buf, 1, off + 8);
602: for (k = 0; k < UTF8_SIZE; k++) {
603: if (gu->data[k] == 0xff)
604: break;
605: buf[off++] = gu->data[k];
606: }
607: } else {
608: buf = xrealloc(buf, 1, off + 1);
609: buf[off++] = gc->data;
610: }
611: }
612: while (off > 0 && buf[off - 1] == ' ')
613: off--;
614: buf[off] = '\0';
615:
616: if ((s = strstr(buf, searchstr)) != NULL) {
617: s = section_string(buf, off, s - buf, 40);
618: xfree(buf);
619: return (s);
620: }
621: }
622:
623: xfree(buf);
624: return (NULL);
625: }