I want the pty to be seen.
[apps/madtty.git] / madtty / madtty.c
index 0d1eba6..8fbc469 100644 (file)
     Copyright © 2006 Pierre Habouzit
  */
 
+#include <errno.h>
 #include <stdlib.h>
-#ifdef USE_PTY
-#include <pty.h>
-#endif
+#include <fcntl.h>
 #include <stdio.h>
 #include <string.h>
+#include <sys/ioctl.h>
 
 #include "madtty.h"
 #include "roteprivate.h"
 
 #define ROTE_VT_UPDATE_ITERATIONS 5
 
-RoteTerm *rote_vt_create(int rows, int cols) {
-   RoteTerm *rt;
-   int i, j;
+RoteTerm *rote_vt_create(int rows, int cols)
+{
+    RoteTerm *rt;
+    int i, j;
 
-   if (rows <= 0 || cols <= 0) return NULL;
+    if (rows <= 0 || cols <= 0)
+        return NULL;
 
-   if (! (rt = (RoteTerm*) malloc(sizeof(RoteTerm))) ) return NULL;
-   memset(rt, 0, sizeof(RoteTerm));
+    rt = (RoteTerm*)calloc(sizeof(RoteTerm), 1);
+    if (!rt)
+        return NULL;
 
-   /* record dimensions */
-   rt->rows = rows;
-   rt->cols = cols;
+    /* record dimensions */
+    rt->rows = rows;
+    rt->cols = cols;
 
-   /* default mode is replace */
-   rt->insert = false; 
+    /* default mode is replace */
+    rt->insert = false; 
 
-   /* create the cell matrix */
-   rt->cells = (RoteCell**) malloc(sizeof(RoteCell*) * rt->rows);
-   for (i = 0; i < rt->rows; i++) {
-      /* create row */
-      rt->cells[i] = (RoteCell*) malloc(sizeof(RoteCell) * rt->cols);
+    /* create the cell matrix */
+    rt->cells = (RoteCell**) malloc(sizeof(RoteCell*) * rt->rows);
+    for (i = 0; i < rt->rows; i++) {
+        /* create row */
+        rt->cells[i] = (RoteCell*) malloc(sizeof(RoteCell) * rt->cols);
 
-      /* fill row with spaces */
-      for (j = 0; j < rt->cols; j++) {
-         rt->cells[i][j].ch = 0x20;    /* a space */
-         rt->cells[i][j].attr = 0x70;  /* white text, black background */
-      }
-   }
-   
-   /* allocate dirtiness array */
-   rt->line_dirty = (bool*) malloc(sizeof(bool) * rt->rows);
+        /* fill row with spaces */
+        for (j = 0; j < rt->cols; j++) {
+            rt->cells[i][j].ch   = 0x20;  /* a space */
+            rt->cells[i][j].attr = 0x70;  /* white text, black background */
+        }
+    }
 
-   /* initialization of other public fields */
-   rt->crow = rt->ccol = 0;
-   rt->curattr = 0x70;  /* white text over black background */
+    /* allocate dirtiness array */
+    rt->line_dirty = (bool*)calloc(sizeof(bool), rt->rows);
 
-   /* allocate private data */
-   rt->pd = (RoteTermPrivate*) malloc(sizeof(RoteTermPrivate));
-   memset(rt->pd, 0, sizeof(RoteTermPrivate));
+    /* initialization of other public fields */
+    rt->crow = rt->ccol = 0;
+    rt->curattr = 0x70;  /* white text over black background */
 
-   rt->pd->pty = -1;  /* no pty for now */
+    /* allocate private data */
+    rt->pd = (RoteTermPrivate*)calloc(sizeof(RoteTermPrivate), 1);
 
-   /* initial scrolling area is the whole window */
-   rt->pd->scrolltop = 0;
-   rt->pd->scrollbottom = rt->rows - 1;
+    rt->pty = -1;  /* no pty for now */
 
-   #ifdef DEBUG
-   fprintf(stderr, "Created a %d x %d terminal.\n", rt->rows, rt->cols);
-   #endif
-   
-   return rt;
-}
+    /* initial scrolling area is the whole window */
+    rt->pd->scrolltop = 0;
+    rt->pd->scrollbottom = rt->rows - 1;
 
-void rote_vt_destroy(RoteTerm *rt) {
-   int i;
-   if (!rt) return;
+    return rt;
+}
 
-   free(rt->pd);
-   free(rt->line_dirty);
-   for (i = 0; i < rt->rows; i++) free(rt->cells[i]);
-   free(rt->cells);
-   free(rt);
+void rote_vt_destroy(RoteTerm *rt)
+{
+    int i;
+    if (!rt)
+        return;
+
+    free(rt->pd);
+    free(rt->line_dirty);
+    for (i = 0; i < rt->rows; i++) {
+        free(rt->cells[i]);
+    }
+    free(rt->cells);
+    free(rt);
 }
 
 #ifdef USE_NCURSES
 
-static void default_cur_set_attr(WINDOW *win, unsigned char attr) {
-   int cp = ROTE_ATTR_BG(attr) * 8 + 7 - ROTE_ATTR_FG(attr);
-   if (!cp) wattrset(win, A_NORMAL);
-   else     wattrset(win, COLOR_PAIR(cp));
+static void default_cur_set_attr(WINDOW *win, unsigned char attr)
+{
+    int cp = ROTE_ATTR_BG(attr) * 8 + 7 - ROTE_ATTR_FG(attr);
+    if (!cp) wattrset(win, A_NORMAL);
+    else     wattrset(win, COLOR_PAIR(cp));
 
-   if (ROTE_ATTR_BOLD(attr))     wattron(win, A_BOLD);
-   if (ROTE_ATTR_BLINK(attr))    wattron(win, A_BLINK);
+    if (ROTE_ATTR_BOLD(attr))     wattron(win, A_BOLD);
+    if (ROTE_ATTR_BLINK(attr))    wattron(win, A_BLINK);
 }
 
-#endif
+static inline unsigned char ensure_printable(unsigned char ch)
+{
+    return ch >= 32 ? ch : 32;
+}
 
-static inline unsigned char ensure_printable(unsigned char ch) 
-                                        { return ch >= 32 ? ch : 32; }
+void rote_vt_draw(RoteTerm *rt, WINDOW *win, int srow, int scol, 
+                  void (*cur_set_attr)(WINDOW*,unsigned char)) {
 
-#ifdef USE_NCURSES
+    int i, j;
+    rote_vt_update(rt);
 
-void rote_vt_draw(RoteTerm *rt, WINDOW *win, int srow, int scol, 
-                                void (*cur_set_attr)(WINDOW*,unsigned char)) {
-
-   int i, j;
-   rote_vt_update(rt);
-   
-   if (!cur_set_attr) cur_set_attr = default_cur_set_attr;
-   for (i = 0; i < rt->rows; i++) {
-      wmove(win, srow + i, scol);
-      for (j = 0; j < rt->cols; j++) {
-         (*cur_set_attr)(win, rt->cells[i][j].attr);
-         waddch(win, ensure_printable(rt->cells[i][j].ch));
-      }
-   }
+    if (!cur_set_attr) cur_set_attr = default_cur_set_attr;
+    for (i = 0; i < rt->rows; i++) {
+        wmove(win, srow + i, scol);
+        for (j = 0; j < rt->cols; j++) {
+            (*cur_set_attr)(win, rt->cells[i][j].attr);
+            waddch(win, ensure_printable(rt->cells[i][j].ch));
+        }
+    }
 
-   wmove(win, srow + rt->crow, scol + rt->ccol);
+    wmove(win, srow + rt->crow, scol + rt->ccol);
 }
 
 #endif
 
-#ifdef USE_PTY
-
-pid_t rote_vt_forkpty(RoteTerm *rt, const char *command) {
-   struct winsize ws;
-   pid_t childpid;
-   
-   ws.ws_row = rt->rows;
-   ws.ws_col = rt->cols;
-   ws.ws_xpixel = ws.ws_ypixel = 0;
-
-   childpid = forkpty(&rt->pd->pty, NULL, NULL, &ws);
-   if (childpid < 0) return -1;
+/******************************************************/
+
+#define PTYCHAR1 "pqrstuvwxyz"
+#define PTYCHAR2 "0123456789abcdef"
+
+/* allocate one pty/tty pair */
+static int get_pty(char *tty_str)
+{
+   int fd;
+   char ptydev[] = "/dev/pty??";
+   char ttydev[] = "/dev/tty??";
+   int len = strlen(ttydev);
+   const char *c1, *c2;
+
+   for (c1 = PTYCHAR1; *c1; c1++) {
+       ptydev[len-2] = ttydev[len-2] = *c1;
+       for (c2 = PTYCHAR2; *c2; c2++) {
+           ptydev[len-1] = ttydev[len-1] = *c2;
+           if ((fd = open(ptydev, O_RDWR)) >= 0) {
+               if (access(ttydev, R_OK|W_OK) == 0) {
+                   strcpy(tty_str, ttydev);
+                   return fd;
+               }
+               close(fd);
+           }
+       }
+   }
+   return -1;
+}
 
-   if (childpid == 0) {
-      /* we are the child, running under the slave side of the pty. */
+static int
+run_process(const char *path, const char **argv, int *fd_ptr, int *pid_ptr)
+{
+    int pty_fd, pid, i, nb_fds;
+    char tty_name[32];
+    struct winsize ws;
+
+    pty_fd = get_pty(tty_name);
+    if (pty_fd < 0)
+        return -1;
+
+    fcntl(pty_fd, F_SETFL, O_NONBLOCK);
+
+    /* set dummy screen size */
+    ws.ws_col = 80;
+    ws.ws_row = 25;
+    ws.ws_xpixel = ws.ws_col;
+    ws.ws_ypixel = ws.ws_row;
+    ioctl(pty_fd, TIOCSWINSZ, &ws);
+
+    pid = fork();
+    if (pid < 0)
+        return -1;
+
+    if (pid == 0) {
+        /* child process */
+        nb_fds = getdtablesize();
+        for (i = 0; i < nb_fds; i++)
+            close(i);
+        /* open pseudo tty for standard i/o */
+        open(tty_name, O_RDWR);
+        dup(0);
+        dup(0);
+
+        setsid();
+
+        setenv("TERM", "linux", 1);
+        execv(path, (char *const*)argv);
+        fprintf(stderr, "\nexecv() failed.\nCommand: '%s'\n", argv[0]);
+        exit(1);
+    }
+    /* return file info */
+    *fd_ptr = pty_fd;
+    *pid_ptr = pid;
+    return 0;
+}
 
-      /* Cajole application into using linux-console-compatible escape
-       * sequences (which is what we are prepared to interpret) */
-      setenv("TERM", "linux", 1);
+pid_t rote_vt_forkpty(RoteTerm *rt, const char *path, const char *argv[])
+{
+    struct winsize ws;
 
-      /* Now we will exec /bin/sh -c command. */
-      execl("/bin/sh", "/bin/sh", "-c", command, NULL);
+    ws.ws_row = rt->rows;
+    ws.ws_col = rt->cols;
+    ws.ws_xpixel = ws.ws_ypixel = 0;
 
-      fprintf(stderr, "\nexecl() failed.\nCommand: '%s'\n", command);
-      exit(127);  /* error exec'ing */
-   }
+    if (run_process(path, argv, &rt->pty, &rt->childpid)) {
+        return -1;
+    }
 
-   /* if we got here we are the parent process */
-   rt->childpid = childpid;
-   return childpid;
+    ioctl(rt->pty, TIOCSWINSZ, &ws);
+    return rt->childpid;
 }
 
-void rote_vt_forsake_child(RoteTerm *rt) {
-   if (rt->pd->pty >= 0) close(rt->pd->pty);
-   rt->pd->pty = -1;
-   rt->childpid = 0;
+void rote_vt_forsake_child(RoteTerm *rt)
+{
+    if (rt->pty >= 0)
+        close(rt->pty);
+    rt->pty  = -1;
+    rt->childpid = 0;
 }
 
-#endif
-
-void rote_vt_update(RoteTerm *rt) {
-   fd_set ifs;
-   struct timeval tvzero;
-   char buf[512];
-   int bytesread;
-   int n = ROTE_VT_UPDATE_ITERATIONS;
-   if (rt->pd->pty < 0) return;  /* nothing to pump */
-
-   while (n--) { /* iterate at most ROVE_VT_UPDATE_ITERATIONS times.
-                  * As Phil Endecott pointed out, if we don't restrict this,
-                  * a program that floods the terminal with output
-                  * could cause this loop to iterate forever, never
-                  * being able to catch up. So we'll rely on the client
-                  * calling rote_vt_update often, as the documentation
-                  * recommends :-) */
-
-      /* check if pty has something to say */
-      FD_ZERO(&ifs); FD_SET(rt->pd->pty, &ifs);
-      tvzero.tv_sec = 0; tvzero.tv_usec = 0;
-   
-      if (select(rt->pd->pty + 1, &ifs, NULL, NULL, &tvzero) <= 0)
-         return; /* nothing to read, or select() failed */
-
-      /* read what we can. This is guaranteed not to block, since
-       * select() told us there was something to read. */
-      bytesread = read(rt->pd->pty, buf, 512);
-      if (bytesread <= 0) return;   
-
-      /* inject the data into the terminal */
-      rote_vt_inject(rt, buf, bytesread);
-   }
+void rote_vt_update(RoteTerm *rt)
+{
+    char buf[512];
+    int nbread;
+    int n = ROTE_VT_UPDATE_ITERATIONS;
+
+    while (n--) { /* iterate at most ROVE_VT_UPDATE_ITERATIONS times.
+                   * As Phil Endecott pointed out, if we don't restrict this,
+                   * a program that floods the terminal with output
+                   * could cause this loop to iterate forever, never
+                   * being able to catch up. So we'll rely on the client
+                   * calling rote_vt_update often, as the documentation
+                   * recommends :-) */
+
+        nbread = rote_vt_read(rt, buf, sizeof(buf));
+        if (nbread <= 0)
+            return;
+
+        /* inject the data into the terminal */
+        rote_vt_inject(rt, buf, nbread);
+    }
 }
 
-void rote_vt_write(RoteTerm *rt, const char *data, int len) {
-   if (rt->pd->pty < 0) {
-      /* no pty, so just inject the data plain and simple */
-      rote_vt_inject(rt, data, len);
-      return;
-   }
+int rote_vt_read(RoteTerm *rt, char *buf, int buflen)
+{
+    if (rt->pty < 0) {
+        errno = EINVAL;
+        return -1;
+    }
 
-   /* write data to pty. Keep calling write() until we have written
-    * everything. */
-   while (len > 0) {
-      int byteswritten = write(rt->pd->pty, data, len);
-      if (byteswritten < 0) {
-         /* very ugly way to inform the error. Improvements welcome! */
-         static char errormsg[] = "\n(ROTE: pty write() error)\n";
-         rote_vt_inject(rt, errormsg, strlen(errormsg));
-         return;
-      }
-
-      data += byteswritten;
-      len  -= byteswritten;
-   }
+    return read(rt->pty, buf, buflen);
 }
 
-void rote_vt_install_handler(RoteTerm *rt, rote_es_handler_t handler) {
-   rt->pd->handler = handler;
+void rote_vt_write(RoteTerm *rt, const char *data, int len)
+{
+    if (rt->pty < 0) {
+        /* no pty, so just inject the data plain and simple */
+        rote_vt_inject(rt, data, len);
+        return;
+    }
+
+    /* write data to pty. Keep calling write() until we have written
+     * everything. */
+    while (len > 0) {
+        int byteswritten = write(rt->pty, data, len);
+        if (byteswritten < 0) {
+            /* very ugly way to inform the error. Improvements welcome! */
+            static char errormsg[] = "\n(ROTE: pty write() error)\n";
+            rote_vt_inject(rt, errormsg, strlen(errormsg));
+            return;
+        }
+
+        data += byteswritten;
+        len  -= byteswritten;
+    }
 }
 
-void *rote_vt_take_snapshot(RoteTerm *rt) {
-   int i;
-   int bytes_per_row = sizeof(RoteCell) * rt->cols;
-   void *buf = malloc(bytes_per_row * rt->rows);
-   void *ptr = buf;
+void *rote_vt_take_snapshot(RoteTerm *rt)
+{
+    const int bytes_per_row = sizeof(RoteCell) * rt->cols;
+    void *buf = malloc(bytes_per_row * rt->rows);
+    void *ptr = buf;
+    int i;
 
-   for (i = 0; i < rt->rows; i++, ptr += bytes_per_row)
-      memcpy(ptr, rt->cells[i], bytes_per_row);
+    for (i = 0; i < rt->rows; i++, ptr += bytes_per_row)
+        memcpy(ptr, rt->cells[i], bytes_per_row);
 
-   return buf;
+    return buf;
 }
 
-void rote_vt_restore_snapshot(RoteTerm *rt, void *snapbuf) {
-   int i;
-   int bytes_per_row = sizeof(RoteCell) * rt->cols;
+void rote_vt_restore_snapshot(RoteTerm *rt, void *snapbuf)
+{
+    const int bytes_per_row = sizeof(RoteCell) * rt->cols;
 
-   for (i = 0; i < rt->rows; i++, snapbuf += bytes_per_row) {
-      rt->line_dirty[i] = true;
-      memcpy(rt->cells[i], snapbuf, bytes_per_row);
-   }
-}
+    int i;
 
-int rote_vt_get_pty_fd(RoteTerm *rt) {
-   return rt->pd->pty;
+    for (i = 0; i < rt->rows; i++, snapbuf += bytes_per_row) {
+        rt->line_dirty[i] = true;
+        memcpy(rt->cells[i], snapbuf, bytes_per_row);
+    }
 }
 
 static const char *keytable[KEY_MAX+1];
-static int initialized = 0;
-
-static void keytable_init();
 
-void rote_vt_keypress(RoteTerm *rt, int keycode) {
-   char c = (char) keycode;
+static void keytable_init()
+{
+    memset(keytable, 0, KEY_MAX+1 * sizeof(const char*));
+
+    keytable['\n']          = "\r";
+    keytable[KEY_UP]        = "\e[A";
+    keytable[KEY_DOWN]      = "\e[B";
+    keytable[KEY_RIGHT]     = "\e[C";
+    keytable[KEY_LEFT]      = "\e[D";
+    keytable[KEY_BACKSPACE] = "\b";
+    keytable[KEY_HOME]      = "\e[1~";
+    keytable[KEY_IC]        = "\e[2~";
+    keytable[KEY_DC]        = "\e[3~";
+    keytable[KEY_END]       = "\e[4~";
+    keytable[KEY_PPAGE]     = "\e[5~";
+    keytable[KEY_NPAGE]     = "\e[6~";
+    keytable[KEY_SUSPEND]   = "\x1A";  /* Ctrl+Z gets mapped to this */
+    keytable[KEY_F(1)]      = "\e[[A";
+    keytable[KEY_F(2)]      = "\e[[B";
+    keytable[KEY_F(3)]      = "\e[[C";
+    keytable[KEY_F(4)]      = "\e[[D";
+    keytable[KEY_F(5)]      = "\e[[E";
+    keytable[KEY_F(6)]      = "\e[17~";
+    keytable[KEY_F(7)]      = "\e[18~";
+    keytable[KEY_F(8)]      = "\e[19~";
+    keytable[KEY_F(9)]      = "\e[20~";
+    keytable[KEY_F(10)]     = "\e[21~";
+}
 
-   if (!initialized) keytable_init();
+void rote_vt_keypress(RoteTerm *rt, int keycode)
+{
+    char c = (char) keycode;
 
-   if (keycode >= 0 && keycode < KEY_MAX && keytable[keycode])
-      rote_vt_write(rt, keytable[keycode], strlen(keytable[keycode]));
-   else
-      rote_vt_write(rt, &c, 1); /* not special, just write it */
-}
+    if (keytable['\n'] == NULL)
+        keytable_init();
 
-static void keytable_init() {
-   initialized = 1;
-   memset(keytable, 0, KEY_MAX+1 * sizeof(const char*));
-
-   keytable['\n']          = "\r";
-   keytable[KEY_UP]        = "\e[A";
-   keytable[KEY_DOWN]      = "\e[B";
-   keytable[KEY_RIGHT]     = "\e[C";
-   keytable[KEY_LEFT]      = "\e[D";
-   keytable[KEY_BACKSPACE] = "\b";
-   keytable[KEY_HOME]      = "\e[1~";
-   keytable[KEY_IC]        = "\e[2~";
-   keytable[KEY_DC]        = "\e[3~";
-   keytable[KEY_END]       = "\e[4~";
-   keytable[KEY_PPAGE]     = "\e[5~";
-   keytable[KEY_NPAGE]     = "\e[6~";
-   keytable[KEY_SUSPEND]   = "\x1A";  /* Ctrl+Z gets mapped to this */
-   keytable[KEY_F(1)]      = "\e[[A";
-   keytable[KEY_F(2)]      = "\e[[B";
-   keytable[KEY_F(3)]      = "\e[[C";
-   keytable[KEY_F(4)]      = "\e[[D";
-   keytable[KEY_F(5)]      = "\e[[E";
-   keytable[KEY_F(6)]      = "\e[17~";
-   keytable[KEY_F(7)]      = "\e[18~";
-   keytable[KEY_F(8)]      = "\e[19~";
-   keytable[KEY_F(9)]      = "\e[20~";
-   keytable[KEY_F(10)]     = "\e[21~";
+    if (keycode >= 0 && keycode < KEY_MAX && keytable[keycode]) {
+        rote_vt_write(rt, keytable[keycode], strlen(keytable[keycode]));
+    } else {
+        rote_vt_write(rt, &c, 1); /* not special, just write it */
+    }
 }
 
-