|  | /* $Id: window.c,v 1.20 2007-10-19 20:47:09 nicm Exp $ */ | 
|  |  | 
|  | /* | 
|  | * Copyright (c) 2007 Nicholas Marriott <nicm@users.sourceforge.net> | 
|  | * | 
|  | * Permission to use, copy, modify, and distribute this software for any | 
|  | * purpose with or without fee is hereby granted, provided that the above | 
|  | * copyright notice and this permission notice appear in all copies. | 
|  | * | 
|  | * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | 
|  | * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | 
|  | * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | 
|  | * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | 
|  | * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER | 
|  | * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING | 
|  | * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | 
|  | */ | 
|  |  | 
|  | #include <sys/types.h> | 
|  | #include <sys/ioctl.h> | 
|  |  | 
|  | #include <fcntl.h> | 
|  | #include <paths.h> | 
|  | #include <signal.h> | 
|  | #include <stdlib.h> | 
|  | #include <string.h> | 
|  | #include <termios.h> | 
|  | #include <unistd.h> | 
|  |  | 
|  | #ifdef USE_LIBUTIL_H | 
|  | #include <libutil.h> | 
|  | #else | 
|  | #include <util.h> | 
|  | #endif | 
|  |  | 
|  | #include "tmux.h" | 
|  |  | 
|  | /* | 
|  | * Each window is attached to a pty. This file contains code to handle them. | 
|  | * | 
|  | * A window has two buffers attached, these are filled and emptied by the main | 
|  | * server poll loop. Output data is received from pty's in screen format, | 
|  | * translated and returned as a series of escape sequences and strings. | 
|  | * Input data is received in screen format and written directly to the pty | 
|  | * (translation is done in the client). | 
|  | * | 
|  | * Each window also has a "virtual" screen (screen.c) which contains the | 
|  | * current state and is redisplayed when the window is reattached to a client. | 
|  | * | 
|  | * A global list of windows is maintained, and a window may also be a member | 
|  | * of any number of sessions. A reference count is maintained and a window | 
|  | * removed from the global list and destroyed when it reaches zero. | 
|  | */ | 
|  |  | 
|  | /* Global window list. */ | 
|  | struct windows	windows; | 
|  |  | 
|  | /* Create a new window. */ | 
|  | struct window * | 
|  | window_create( | 
|  | const char *name, const char *cmd, const char **environ, u_int sx, u_int sy) | 
|  | { | 
|  | struct window	*w; | 
|  | struct winsize	 ws; | 
|  | struct termios	 tio; | 
|  | int		 fd, mode; | 
|  | char		*ptr, *copy; | 
|  | const char     **entry; | 
|  |  | 
|  | memset(&ws, 0, sizeof ws); | 
|  | ws.ws_col = sx; | 
|  | ws.ws_row = sy; | 
|  |  | 
|  | memset(&tio, 0, sizeof tio); | 
|  | tio.c_iflag = TTYDEF_IFLAG; | 
|  | tio.c_oflag = TTYDEF_OFLAG; | 
|  | tio.c_lflag = TTYDEF_LFLAG; | 
|  | tio.c_cflag = TTYDEF_CFLAG; | 
|  | memcpy(&tio.c_cc, ttydefchars, sizeof tio.c_cc); | 
|  | cfsetspeed(&tio, TTYDEF_SPEED); | 
|  |  | 
|  | switch (forkpty(&fd, NULL, &tio, &ws)) { | 
|  | case -1: | 
|  | return (NULL); | 
|  | case 0: | 
|  | for (entry = environ; *entry != NULL; entry++) { | 
|  | if (putenv(*entry) != 0) | 
|  | fatal("putenv failed"); | 
|  | } | 
|  | sigreset(); | 
|  | log_close(); | 
|  |  | 
|  | execl(_PATH_BSHELL, "sh", "-c", cmd, (char *) NULL); | 
|  | fatal("execl failed"); | 
|  | } | 
|  |  | 
|  | if ((mode = fcntl(fd, F_GETFL)) == -1) | 
|  | fatal("fcntl failed"); | 
|  | if (fcntl(fd, F_SETFL, mode|O_NONBLOCK) == -1) | 
|  | fatal("fcntl failed"); | 
|  |  | 
|  | mode = 1; | 
|  | if (ioctl(fd, TIOCPKT, &mode) == -1) | 
|  | fatal("ioctl failed"); | 
|  |  | 
|  | w = xmalloc(sizeof *w); | 
|  | w->fd = fd; | 
|  | w->in = buffer_create(BUFSIZ); | 
|  | w->out = buffer_create(BUFSIZ); | 
|  | screen_create(&w->screen, sx, sy); | 
|  | input_init(&w->ictx, &w->screen); | 
|  |  | 
|  | if (name == NULL) { | 
|  | /* XXX */ | 
|  | if (strncmp(cmd, "exec ", (sizeof "exec ") - 1) == 0) | 
|  | copy = xstrdup(cmd + (sizeof "exec ") - 1); | 
|  | else | 
|  | copy = xstrdup(cmd); | 
|  | if ((ptr = strchr(copy, ' ')) != NULL) { | 
|  | if (ptr != copy && ptr[-1] != '\\') | 
|  | *ptr = '\0'; | 
|  | else { | 
|  | while ((ptr = strchr(ptr + 1, ' ')) != NULL) { | 
|  | if (ptr[-1] != '\\') { | 
|  | *ptr = '\0'; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | w->name = xstrdup(xbasename(copy)); | 
|  | xfree(copy); | 
|  | } else | 
|  | w->name = xstrdup(name); | 
|  |  | 
|  | window_add(&windows, w); | 
|  | w->references = 1; | 
|  |  | 
|  | return (w); | 
|  | } | 
|  |  | 
|  | /* Find window index in list. */ | 
|  | int | 
|  | window_index(struct windows *ww, struct window *w, u_int *i) | 
|  | { | 
|  | for (*i = 0; *i < ARRAY_LENGTH(ww); (*i)++) { | 
|  | if (w == ARRAY_ITEM(ww, *i)) | 
|  | return (0); | 
|  | } | 
|  | return (-1); | 
|  | } | 
|  |  | 
|  | /* Add a window to a list. */ | 
|  | void | 
|  | window_add(struct windows *ww, struct window *w) | 
|  | { | 
|  | u_int	i; | 
|  |  | 
|  | if (window_index(ww, NULL, &i) != 0) | 
|  | ARRAY_ADD(ww, w); | 
|  | else | 
|  | ARRAY_SET(ww, i, w); | 
|  |  | 
|  | w->references++; | 
|  | } | 
|  |  | 
|  | /* Remove a window from a list. */ | 
|  | void | 
|  | window_remove(struct windows *ww, struct window *w) | 
|  | { | 
|  | u_int	i; | 
|  |  | 
|  | if (window_index(ww, w, &i) != 0) | 
|  | fatalx("window not found"); | 
|  | ARRAY_SET(ww, i, NULL); | 
|  | while (!ARRAY_EMPTY(ww) && ARRAY_LAST(ww) == NULL) | 
|  | ARRAY_TRUNC(ww, 1); | 
|  |  | 
|  | w->references--; | 
|  | if (w->references == 0) | 
|  | window_destroy(w); | 
|  | if (w->references == 1) | 
|  | window_remove(&windows, w); | 
|  | } | 
|  |  | 
|  | /* Destroy a window. */ | 
|  | void | 
|  | window_destroy(struct window *w) | 
|  | { | 
|  | close(w->fd); | 
|  |  | 
|  | input_free(&w->ictx); | 
|  |  | 
|  | screen_destroy(&w->screen); | 
|  |  | 
|  | buffer_destroy(w->in); | 
|  | buffer_destroy(w->out); | 
|  |  | 
|  | xfree(w->name); | 
|  | xfree(w); | 
|  | } | 
|  |  | 
|  | /* Locate next window in list. */ | 
|  | struct window * | 
|  | window_next(struct windows *ww, struct window *w) | 
|  | { | 
|  | u_int	i; | 
|  |  | 
|  | if (window_index(ww, w, &i) != 0) | 
|  | fatalx("window not found"); | 
|  |  | 
|  | if (i == ARRAY_LENGTH(ww) - 1) | 
|  | return (NULL); | 
|  | do { | 
|  | i++; | 
|  | w = window_at(ww, i); | 
|  | if (w != NULL) | 
|  | return (w); | 
|  | } while (i != ARRAY_LENGTH(ww) - 1); | 
|  | return (NULL); | 
|  | } | 
|  |  | 
|  | /* Locate previous window in list. */ | 
|  | struct window * | 
|  | window_previous(struct windows *ww, struct window *w) | 
|  | { | 
|  | u_int	i; | 
|  |  | 
|  | if (window_index(ww, w, &i) != 0) | 
|  | fatalx("window not found"); | 
|  | if (i == 0) | 
|  | return (NULL); | 
|  | do { | 
|  | i--; | 
|  | w = window_at(ww, i); | 
|  | if (w != NULL) | 
|  | return (w); | 
|  | } while (i != 0); | 
|  | return (NULL); | 
|  | } | 
|  |  | 
|  | /* Locate window at specific position in list. */ | 
|  | struct window * | 
|  | window_at(struct windows *ww, u_int i) | 
|  | { | 
|  | if (i >= ARRAY_LENGTH(ww)) | 
|  | return (NULL); | 
|  | return (ARRAY_ITEM(ww, i)); | 
|  | } | 
|  |  | 
|  | /* Resize a window. */ | 
|  | int | 
|  | window_resize(struct window *w, u_int sx, u_int sy) | 
|  | { | 
|  | struct winsize	ws; | 
|  |  | 
|  | if (sx == w->screen.sx && sy == w->screen.sy) | 
|  | return (-1); | 
|  |  | 
|  | memset(&ws, 0, sizeof ws); | 
|  | ws.ws_col = sx; | 
|  | ws.ws_row = sy; | 
|  |  | 
|  | screen_resize(&w->screen, sx, sy); | 
|  |  | 
|  | if (ioctl(w->fd, TIOCSWINSZ, &ws) == -1) | 
|  | fatal("ioctl failed"); | 
|  | return (0); | 
|  | } | 
|  |  | 
|  | /* Handle window poll results. This is special because of TIOCPKT. */ | 
|  | int | 
|  | window_poll(struct window *w, struct pollfd *pfd) | 
|  | { | 
|  | struct termios	 tio; | 
|  | size_t	 	 size; | 
|  | u_char		*ptr; | 
|  |  | 
|  | size = BUFFER_USED(w->in); | 
|  | if (buffer_poll(pfd, w->in, w->out) != 0) | 
|  | return (-1); | 
|  |  | 
|  | if (BUFFER_USED(w->in) == size) | 
|  | return (0); | 
|  | ptr = BUFFER_IN(w->in) - (BUFFER_USED(w->in) - size); | 
|  |  | 
|  | log_debug("window packet: %hhu", *ptr); | 
|  | switch (*ptr) { | 
|  | case TIOCPKT_DATA: | 
|  | case TIOCPKT_FLUSHREAD: | 
|  | case TIOCPKT_FLUSHWRITE: | 
|  | case TIOCPKT_STOP: | 
|  | case TIOCPKT_START: | 
|  | case TIOCPKT_DOSTOP: | 
|  | case TIOCPKT_NOSTOP: | 
|  | buffer_delete_range(w->in, size, 1); | 
|  | break; | 
|  | case TIOCPKT_IOCTL: | 
|  | buffer_delete_range(w->in, size, 1 + sizeof tio); | 
|  | break; | 
|  | } | 
|  |  | 
|  | return (0); | 
|  | } | 
|  |  | 
|  | /* Process window key. */ | 
|  | void | 
|  | window_key(struct window *w, int key) | 
|  | { | 
|  | input_translate_key(w->out, key); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Process window output. Output is translated into a series of escape | 
|  | * sequences and strings and returned. | 
|  | */ | 
|  | void | 
|  | window_data(struct window *w, struct buffer *b) | 
|  | { | 
|  | FILE	*f; | 
|  |  | 
|  | if (BUFFER_USED(w->in) == 0) | 
|  | return; | 
|  |  | 
|  | if (debug_level > 2) { | 
|  | f = fopen("tmux-in.log", "a+"); | 
|  | fwrite(BUFFER_OUT(w->in), BUFFER_USED(w->in), 1, f); | 
|  | fclose(f); | 
|  | } | 
|  |  | 
|  | input_parse(&w->ictx, BUFFER_OUT(w->in), BUFFER_USED(w->in), b); | 
|  | buffer_remove(w->in, BUFFER_USED(w->in)); | 
|  |  | 
|  | if (INPUT_FLAGS(&w->ictx) & INPUT_BELL) | 
|  | w->flags |= WINDOW_BELL; | 
|  | } |