view ptyhelper.c @ 70:d7eb63cd7a16

Try to get some compatibility for keys. I'd thought using the DOM_VK_ names was a good idea. Symbolic names ought to be more portable than opaque numeric constants. Foolish me, expecting things to be sane. Keys now work with FF15 and Chrome 17.
author John "Elwin" Edwards <elwin@sdf.org>
date Thu, 21 Jun 2012 09:43:52 -0700
parents 9bef0941c6dd
children
line wrap: on
line source

/*
 * ptyhelper: a utility that runs a command in a pseudoterm and then streams
 * stdio to/from it.  Intended to get around the node.js loss of C/UNIX 
 * functionality.
 * Remember to compile with -lutil.
 */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <pty.h>
#include <utmp.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/select.h>
#include <termios.h>

int got_sighup = 0;

void handle_HUP(int signum) {
  if (signum == SIGHUP)
    got_sighup = 1;
  return;
}

int main(int argc, char *argv[]) {

  int ptymaster, ptyslave; /* File descriptors */
  int child;
  int status, selstatus;
  int w = 80, h = 24, t;
  struct sigaction sighup_act;
  fd_set readset;
  struct timeval select_time;
  char buf[4096];
  int nread;
  char *penv, *ptmp;
#if 0
  struct termios ptysettings;
#endif
  struct winsize ptysize;

  if (argc == 1) {
    fprintf(stderr, "No command given.\n");
    exit(1);
  }

  /* Set up the signal handler. */
  sighup_act.sa_handler = &handle_HUP;
  sighup_act.sa_flags = SA_RESTART;
  sigaction(SIGHUP, &sighup_act, NULL);

  /* Check the environment for configuration options. */
  penv = getenv("PTYHELPER");
  if (penv != NULL) {
    t = strtol(penv, &ptmp, 10);
    if (t > 0 && t < 256)
      h = t;
    if (*ptmp != '\0') {
      penv = ptmp + 1;
      t = strtol(penv, &ptmp, 10);
      if (t > 0 && t < 256)
        w = t;
    }
  }
  /* Set up the size. */
  ptysize.ws_row = h;
  ptysize.ws_col = w;

  /* Open a pty */
  if (openpty(&ptymaster, &ptyslave, NULL, NULL, &ptysize)) {
    return 1;
  }
#if 0
  /* Put it into raw mode. */
  tcgetattr(ptyslave, &ptysettings);
  cfmakeraw(&ptysettings);
  tcsetattr(ptyslave, TCSANOW, &ptysettings);
#endif

  /* Start the child */
  /* forkpty() might be more convenient. */
  if (!(child = fork())) {
    /* Child process */
    login_tty(ptyslave);
    close(ptymaster);
    execvp(argv[1], argv + 1);
    perror("execvp() failed");
    return 1;
  }
  close(ptyslave);

  while (1) {
    /* Now do a select() over stdin and ptymaster, and write anything that 
     * appears to ptymaster and stdout respectively. */
    FD_ZERO(&readset);
    FD_SET(0, &readset);
    FD_SET(ptymaster, &readset);
    select_time.tv_sec = 1;
    select_time.tv_usec = 0;
    selstatus = select(ptymaster + 1, &readset, NULL, NULL, &select_time);
    if (selstatus > 0) {
      /* TODO make sure it all gets written if a signal interrupts write(). */
      if (FD_ISSET(0, &readset)) {
        nread = read(0, buf, 4096);
        if (nread > 0) {
          write(ptymaster, buf, nread);
        }
      }
      if (FD_ISSET(ptymaster, &readset)) {
        nread = read(ptymaster, buf, 4096);
        if (nread > 0) {
          write(1, buf, nread);
        }
      }
    }

    /* Periodically check to see if we're done. */
    /* TODO: catch SIGCHLD and only wait() if it is delivered. */
    if (waitpid(child, &status, WNOHANG)) {
      break;
    }

    /* If node sighup's us, pass it along. */
    if (got_sighup) {
      kill(child, SIGHUP);
    }
  }

  /* Get any leftover output and clean up. */
  /* FIXME looping over select() is pointless if there's only one fd that
   * nothing's writing to.  Just loop over read() until it's empty. */
  while (1) {
    FD_ZERO(&readset);
    FD_SET(ptymaster, &readset);
    select_time.tv_sec = 0;
    select_time.tv_usec = 0;
    if (select(ptymaster + 1, &readset, NULL, NULL, &select_time) > 0) {
      nread = read(ptymaster, buf, 4096);
      if (nread > 0) {
        write(1, buf, nread);
      }
      else
        break;
    }
    else
      break;
  }
  close(ptymaster);

  /* Return the child's exit status. */
  if (WIFEXITED(status))
    return WEXITSTATUS(status);
  return 0;
}