/*--------------------------------*-C-*---------------------------------*
 * File:	command.c
 */
/*{{{ notes: */
/*----------------------------------------------------------------------*
 * Copyright 1992 John Bovey, University of Kent at Canterbury.
 *
 * You can do what you like with this source code as long as
 * you don't try to make money out of it and you include an
 * unaltered copy of this message (including the copyright).
 *
 * This module has been very heavily modified by R. Nation
 * <nation@rocket.sanders.lockheed.com>
 * No additional restrictions are applied
 *
 * Additional modification by Garrett D'Amore <garrett@netcom.com> to
 * allow vt100 printing.  No additional restrictions are applied.
 *
 * Integrated modifications by Steven Hirsch <hirsch@emba.uvm.edu> to
 * properly support X11 mouse report mode and support for DEC
 * "private mode" save/restore functions.
 *
 * Integrated key-related changes by Jakub Jelinek <jj@gnu.ai.mit.edu>
 * to handle Shift+function keys properly.
 * Should be used with enclosed termcap / terminfo database.
 *
 * Extensive modifications by mj olesen <olesen@me.QueensU.CA>
 * No additional restrictions.
 *
 * Further modification and cleanups for Solaris 2.x and Linux 1.2.x
 * by Raul Garcia Garcia <rgg@tid.es>. No additional restrictions.
 *
 * As usual, the author accepts no responsibility for anything, nor does
 * he guarantee anything whatsoever.
 *----------------------------------------------------------------------*/
/*}}} */
/*{{{ includes: */
#include "main.h"
#include "xdefaults.h"
#ifdef OFFIX_DND
#include <X11/Xatom.h>
#define DndFile	2
#define DndDir		5
#define DndLink	7
#endif

#include <X11/keysym.h>
#ifndef NO_XLOCALE
#if (XtSpecificationRelease < 6)
#define NO_XLOCALE
#else
#define X_LOCALE
#include <X11/Xlocale.h>
#endif
#endif /* NO_XLOCALE */

#include <errno.h>
#include <signal.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#ifdef HAVE_FCNTL_H
#include <fcntl.h>
#endif
#ifdef HAVE_SYS_IOCTL_H
#include <sys/ioctl.h>
#endif
#include <sys/types.h>

/* #define USE_GETGRNAME */
/*
 * this seems like a bad way to go, since there's no guarantee that
 * /dev/tty belongs to the group "tty" and not "system" or "wheel"
 */
#ifdef USE_GETGRNAME
#include <grp.h>
#endif

#ifdef TIME_WITH_SYS_TIME
#include <sys/time.h>
#include <time.h>
#else
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#else
#include <time.h>
#endif
#endif

#if defined (__svr4__)
#include <sys/resource.h>	/* for struct rlimit */
#include <sys/stropts.h>	/* for I_PUSH */
#define _NEW_TTY_CTRL		/* to get proper defines in <termios.h> */
#endif

#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif

#ifdef HAVE_TERMIOS_H
#include <termios.h>
#else
#include <sgtty.h>
#endif

#include <sys/wait.h>
#include <sys/stat.h>

#include "command.h"
#include "debug.h"
#include "graphics.h"
#include "grkelot.h"
#include "scrollbar.h"
#include "screen.h"
#include "defaults.h"

/*}}} */

/* #define DEBUG_TTYMODE */
/* #define DEBUG_CMD */

/*{{{ terminal mode defines: */
/* use the fastest baud-rate */
#ifdef B38400
#define BAUDRATE	B38400
#else
#ifdef B19200
#define BAUDRATE	B19200
#else
#define BAUDRATE	B9600
#endif
#endif

/* Disable special character functions */
#ifdef _POSIX_VDISABLE
#define VDISABLE	_POSIX_VDISABLE
#else
#define VDISABLE	255
#endif

/*----------------------------------------------------------------------*
 * system default characters if defined and reasonable
 */
#ifndef CINTR
#define CINTR		'\003'	/* ^C */
#endif
#ifndef CQUIT
#define CQUIT		'\034'	/* ^\ */
#endif
#ifndef CERASE
#define CERASE		'\010'	/* ^H */
#endif
#ifndef CKILL
#define CKILL		'\025'	/* ^U */
#endif
#ifndef CEOF
#define CEOF		'\004'	/* ^D */
#endif
#ifndef CSTART
#define CSTART		'\021'	/* ^Q */
#endif
#ifndef CSTOP
#define CSTOP		'\023'	/* ^S */
#endif
#ifndef CSUSP
#define CSUSP		'\032'	/* ^Z */
#endif
#ifndef CDSUSP
#define CDSUSP		'\031'	/* ^Y */
#endif
#ifndef CRPRNT
#define CRPRNT		'\022'	/* ^R */
#endif
#ifndef CFLUSH
#define CFLUSH		'\017'	/* ^O */
#endif
#ifndef CWERASE
#define CWERASE		'\027'	/* ^W */
#endif
#ifndef CLNEXT
#define CLNEXT		'\026'	/* ^V */
#endif

#ifndef VDISCRD
#ifdef VDISCARD
#define VDISCRD	VDISCARD
#endif
#endif

#ifndef VWERSE
#ifdef VWERASE
#define VWERSE	VWERASE
#endif
#endif
/*}}} */

/*{{{ defines: */

#define KBUFSZ		8	/* size of keyboard mapping buffer */
#define STRING_MAX	512	/* max string size for process_xterm_seq() */
#define ESC_ARGS	32	/* max # of args for esc sequences */

/* a large REFRESH_PERIOD causes problems with `cat' */
#define REFRESH_PERIOD	1

#ifndef REFRESH_PERIOD
#define REFRESH_PERIOD	10
#endif

#ifndef MULTICLICK_TIME
#define MULTICLICK_TIME	500
#endif

/* time factor to slow down a `jumpy' mouse */
#define MOUSE_THRESHOLD	50
#define CONSOLE		"/dev/console"	/* console device */

/*
 * key-strings: if only these keys were standardized <sigh>
 */
#ifdef LINUX_KEYS
#define KS_HOME	"\033[1~"	/* Home == Find */
#define KS_END	"\033[4~"	/* End == Select */
#else
#define KS_HOME	"\033[7~"	/* Home */
#define KS_END	"\033[8~"	/* End */
#endif

/* and this one too! */
#ifdef NO_DELETE_KEY
#undef KS_DELETE		/* use X server definition */
#else
#ifndef KS_DELETE
#define KS_DELETE	"\033[3~"	/* Delete = Execute */
#endif
#endif

/*
 * ESC-Z processing:
 *
 * By stealing a sequence to which other xterms respond, and sending the
 * same number of characters, but having a distinguishable sequence,
 * we can avoid having a timeout (when not under an rxvt) for every login
 * shell to auto-set its DISPLAY.
 *
 * This particular sequence is even explicitly stated as obsolete since
 * about 1985, so only very old software is likely to be confused, a
 * confusion which can likely be remedied through termcap or TERM. Frankly,
 * I doubt anyone will even notice.  We provide a #ifdef just in case they
 * don't care about auto-display setting.  Just in case the ancient
 * software in question is broken enough to be case insensitive to the 'c'
 * character in the answerback string, we make the distinguishing
 * characteristic be capitalization of that character. The length of the
 * two strings should be the same so that identical read(2) calls may be
 * used.
 */
#define VT100_ANS	"\033[?1;2c"	/* vt100 answerback */
#ifndef ESCZ_ANSWER
#define ESCZ_ANSWER	VT100_ANS	/* obsolete ANSI ESC[c */
#endif
/*}}} */

/*{{{ extern functions referenced */
extern void cleanutent (void);
extern void makeutent (const char *pty, const char *hostname);
/*}}} */
/* extern variables referenced */
/* extern variables declared here */
/*{{{ local variables */
static char *ptydev = NULL, *ttydev = NULL;	/* pty/tty name */
static int cmd_fd = -1;		/* file descriptor connected to the command */
static pid_t cmd_pid = -1;	/* process id if child */
static int Xfd = -1;		/* file descriptor of X server connection */
static unsigned int num_fds = 0;	/* number of file descriptors being used */
static struct stat ttyfd_stat;	/* original status of the tty we will use */

#ifdef META8_OPTION
static unsigned char meta_char = 033;	/* Alt-key prefix */
#endif

/* DEC private modes */
#define PrivMode_132		(1LU<<0)
#define PrivMode_132OK		(1LU<<1)
#define PrivMode_rVideo		(1LU<<2)
#define PrivMode_relOrigin	(1LU<<3)
#define PrivMode_Screen		(1LU<<4)
#define PrivMode_Autowrap	(1LU<<5)
#define PrivMode_aplCUR		(1LU<<6)
#define PrivMode_aplKP		(1LU<<7)
#define PrivMode_BackSpace	(1LU<<8)
#define PrivMode_ShiftKeys	(1LU<<9)
#define PrivMode_VisibleCursor	(1LU<<10)
#define PrivMode_MouseX10	(1LU<<11)
#define PrivMode_MouseX11	(1LU<<12)
/* too annoying to implement X11 highlight tracking */
/* #define PrivMode_MouseX11Track       (1LU<<13) */
#define PrivMode_scrollBar	(1LU<<14)

#define PrivMode_mouse_report	(PrivMode_MouseX10|PrivMode_MouseX11)
#define PrivMode(test,bit) do {\
if (test) PrivateModes |= (bit); else PrivateModes &= ~(bit);} while (0)

#define PrivMode_Default \
(PrivMode_Autowrap|PrivMode_aplKP|PrivMode_ShiftKeys|PrivMode_VisibleCursor)

static unsigned long PrivateModes = PrivMode_Default;
static unsigned long SavedModes = PrivMode_Default;

#undef PrivMode_Default

static int refresh_count = 0, refresh_limit = 1, refresh_type = SLOW_REFRESH;

static Atom wmDeleteWindow;
/* OffiX Dnd (drag 'n' drop) support */
#ifdef OFFIX_DND
static Atom DndProtocol, DndSelection;
#endif /* OFFIX_DND */

#ifndef NO_XLOCALE
static char *rs_inputMethod = "";	/* XtNinputMethod */
static char *rs_preeditType = NULL;	/* XtNpreeditType */
static XIC Input_Context;	/* input context */
#endif /* NO_XLOCALE */

/* command input buffering */
#ifndef BUFSIZ
#define BUFSIZ		4096
#endif
static unsigned char cmdbuf_base[BUFSIZ], *cmdbuf_ptr, *cmdbuf_endp;
/*}}} */
/*{{{ local functions referenced */
static void privileges (int mode);

static RETSIGTYPE Child_signal (int);
static RETSIGTYPE Exit_signal (int);
static int get_pty (void);
static int get_tty (void);
static int run_command (char * /* argv */ []);
static unsigned char cmd_getc (void);
static void lookup_key (XEvent * /* ev */ );
static void process_x_event (XEvent * /* ev */ );
/*static void   process_string (int); */
#ifdef PRINTPIPE
static void process_print_pipe (void);
#endif
static void process_escape_seq (void);
static void process_csi_seq (void);
static void process_xterm_seq (void);
static void process_terminal_mode (int /* mode */ ,
				   int /* priv */ ,
				   unsigned int /* nargs */ ,
				   int /* arg */ []);
static void process_sgr_mode (unsigned int /* nargs */ ,
			      int /* arg */ []);
static void process_graphics (void);

static void tt_winsize (int /* fd */ );

#ifndef NO_XLOCALE
static void init_xlocale (void);
#else
#define init_xlocale() ((void)0)
#endif
/*----------------------------------------------------------------------*/
/*}}} */

/*{{{ substitute system functions */
#ifndef _POSIX_VERSION
#if defined (__svr4__)
static int
getdtablesize (void)
{
  struct rlimit rlim;
  getrlimit (RLIMIT_NOFILE, &rlim);
  return rlim.rlim_cur;
}
#endif
#endif
/*}}} */

/*{{{ take care of suid/sgid super-user (root) privileges */
static void
privileges (int mode)
{
#ifdef HAVE_SETEUID
  static uid_t euid;
  static gid_t egid;

  switch (mode)
    {
    case IGNORE:
      /*
       * change effective uid/gid - not real uid/gid - so we can switch
       * back to root later, as required
       */
      seteuid (getuid ());
      setegid (getgid ());
      break;

    case SAVE:
      euid = geteuid ();
      egid = getegid ();
      break;

    case RESTORE:
      seteuid (euid);
      setegid (egid);
      break;
    }
#else
  switch (mode)
    {
    case IGNORE:
      setuid (getuid ());
      setgid (getgid ());
      break;

    case SAVE:
      break;
    case RESTORE:
      break;
    }
#endif
}
/*}}} */

/*{{{ signal handling, exit handler */
/*
 * Catch a SIGCHLD signal and exit if the direct child has died
 */
static RETSIGTYPE
Child_signal (int unused)
{
  int pid, save_errno = errno;

  do
    {
      errno = 0;
    }
  while ((-1 == (pid = waitpid (cmd_pid, NULL, WNOHANG))) &&
	 (errno == EINTR));

  if (pid == cmd_pid)
    exit (EXIT_SUCCESS);
  errno = save_errno;

  signal (SIGCHLD, Child_signal);
}

/*
 * Catch a fatal signal and tidy up before quitting
 */
static RETSIGTYPE
Exit_signal (int sig)
{
#ifdef DEBUG_CMD
  print_error ("signal %d", sig);
#endif
  signal (sig, SIG_DFL);

  privileges (RESTORE);
  cleanutent ();
  privileges (IGNORE);

  kill (getpid (), sig);
}

/*
 * Exit gracefully, clearing the utmp entry and restoring tty attributes
 */
static void
clean_exit (void)
{
#ifdef DEBUG_CMD
  fprintf (stderr, "Restoring \"%s\" to mode %03o, uid %d, gid %d\n",
	   ttydev, ttyfd_stat.st_mode, ttyfd_stat.st_uid, ttyfd_stat.st_gid);
#endif
  privileges (RESTORE);
  chmod (ttydev, ttyfd_stat.st_mode);
  chown (ttydev, ttyfd_stat.st_uid, ttyfd_stat.st_gid);
  cleanutent ();
  privileges (IGNORE);
}
/*}}} */

/*{{{ Acquire a pseudo-teletype from the system. */
/*
 * On failure, returns -1.
 * On success, returns the file descriptor.
 *
 * If successful, ttydev and ptydev point to the names of the
 * master and slave parts
 */
static int
get_pty (void)
{
  int fd = -1;
#if defined (__sgi)
  ptydev = ttydev = _getpty (&fd, O_RDWR | O_NDELAY, 0622, 0);
  if (ptydev == NULL)
    goto Failed;
#elif defined (__svr4__)
  {
    extern char *ptsname ();

    /* open the STREAMS, clone device /dev/ptmx (master pty) */
    if ((fd = open ("/dev/ptmx", O_RDWR)) < 0)
      {
	goto Failed;
      }
    else
      {
	grantpt (fd);		/* change slave permissions */
	unlockpt (fd);		/* unlock slave */
	ptydev = ttydev = ptsname (fd);		/* get slave's name */
      }
  }
#elif defined (_AIX)
  if ((fd = open ("/dev/ptc", O_RDWR)) < 0)
    goto Failed;
  else
    ptydev = ttydev = ttyname (fd);
#else
  static char pty_name[] = "/dev/pty??";
  static char tty_name[] = "/dev/tty??";
  int len = strlen (tty_name);
  char *c1, *c2;

  ptydev = pty_name;
  ttydev = tty_name;

#define	PTYCHAR1	"pqrstuvwxyz"
#define	PTYCHAR2	"0123456789abcdef"
  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)
		goto Found;
	      close (fd);
	    }
	}
    }
  goto Failed;
#endif

Found:
  fcntl (fd, F_SETFL, O_NDELAY);
  return fd;

Failed:
  print_error ("can't open pseudo-tty");
  return -1;
}
/*}}} */

/*{{{ establish a controlling teletype for new session */
/*
 * On some systems this can be done with ioctl() but on others we
 * need to re-open the slave tty.
 */
static int
get_tty (void)
{
  int fd;
  pid_t pid;

  /*
   * setsid() [or setpgrp] must be before open of the terminal,
   * otherwise there is no controlling terminal (Solaris 2.4, HP-UX 9)
   */
#ifndef ultrix
#ifdef NO_SETSID
  pid = setpgrp (0, 0);
#else
  pid = setsid ();
#endif
  if (pid < 0)
    perror (rs_name);
#ifdef DEBUG_TTYMODE
  print_error ("(%s: line %d): PID = %d\n", __FILE__, __LINE__, pid);
#endif
#endif /* ultrix */

  if ((fd = open (ttydev, O_RDWR)) < 0)
    {
      print_error ("can't open slave tty %s", ttydev);
      exit (EXIT_FAILURE);
    }
#if defined (__svr4__)
  /*
   * Push STREAMS modules:
   *    ptem: pseudo-terminal hardware emulation module.
   *    ldterm: standard terminal line discipline.
   *    ttcompat: V7, 4BSD and XENIX STREAMS compatibility module.
   */
  ioctl (fd, I_PUSH, "ptem");
  ioctl (fd, I_PUSH, "ldterm");
  ioctl (fd, I_PUSH, "ttcompat");
#else /* __svr4__ */
  {
    /* change ownership of tty to real uid and real group */
    unsigned int mode = 0622;
    gid_t gid = getgid ();
#ifdef USE_GETGRNAME
    {
      struct group *gr = getgrnam ("tty");
      if (gr)
	{
	  /* change ownership of tty to real uid, "tty" gid */
	  gid = gr->gr_gid;
	  mode = 0620;
	}
    }
#endif /* USE_GETGRNAME */

    privileges (RESTORE);
    fchown (fd, getuid (), gid);	/* fail silently */
    fchmod (fd, mode);
    privileges (IGNORE);
  }
#endif /* __svr4__ */

  /*
   * Close all file descriptors.  If only stdin/out/err are closed,
   * child processes remain alive upon deletion of the window.
   */
  {
    int i;
    for (i = 0; i < num_fds; i++)
      if (i != fd)
	close (i);
  }

  /* Reopen stdin, stdout and stderr over the tty file descriptor */
  dup (fd);			/* 0: stdin */
  dup (fd);			/* 1: stdout */
  dup (fd);			/* 2: stderr */

  if (fd > 2)
    close (fd);

#ifdef ultrix
  if ((fd = open ("/dev/tty", O_RDONLY)) >= 0)
    {
      ioctl (fd, TIOCNOTTY, 0);
      close (fd);
    }
  else
    {
      pid = setpgrp (0, 0);
      if (pid < 0)
	perror (rs_name);
    }

  /* no error, we could run with no tty to begin with */
#else /* ultrix */

#ifdef TIOCSCTTY
  ioctl (0, TIOCSCTTY, 0);
#endif

  /* set process group */
#if defined (_POSIX_VERSION) || defined (__svr4__)
  tcsetpgrp (0, pid);
#elif defined (TIOCSPGRP)
  ioctl (0, TIOCSPGRP, &pid);
#endif

  /* svr4 problems: reports no tty, no job control */
  /* # if !defined (__svr4__) && defined (TIOCSPGRP) */

  close (open (ttydev, O_RDWR, 0));
  /* # endif */
#endif /* ultrix */

  privileges (IGNORE);

  return fd;
}
/*}}} */

/*{{{ ways to deal with getting/setting termios structure */
#ifdef HAVE_TERMIOS_H
typedef struct termios ttymode_t;
#ifdef TCSANOW			/* POSIX */
#define GET_TERMIOS(fd,tios)	tcgetattr (fd, tios)
#define SET_TERMIOS(fd,tios)	do {\
cfsetospeed (tios, BAUDRATE);\
cfsetispeed (tios, BAUDRATE);\
tcsetattr (fd, TCSANOW, tios);\
} while (0)
#else
#ifdef TIOCSETA
#define GET_TERMIOS(fd,tios)	ioctl (fd, TIOCGETA, tios)
#define SET_TERMIOS(fd,tios)	do {\
tios->c_cflag |= BAUDRATE;\
ioctl (fd, TIOCSETA, tios);\
} while (0)
#else
#define GET_TERMIOS(fd,tios)	ioctl (fd, TCGETS, tios)
#define SET_TERMIOS(fd,tios)	do {\
tios->c_cflag |= BAUDRATE;\
ioctl (fd, TCSETS, tios);\
} while (0)
#endif
#endif
#define SET_TTYMODE(fd,tios)		SET_TERMIOS (fd, tios)
#else
/* sgtty interface */
typedef struct
{
  struct sgttyb sg;
  struct tchars tc;
  struct ltchars lc;
  int line;
  int local;
}
ttymode_t;

#define SET_TTYMODE(fd,tt)	do {	\
tt->sg.sg_ispeed = tt->sg.sg_ospeed = BAUDRATE;\
ioctl (fd, TIOCSETP, &(tt->sg));\
ioctl (fd, TIOCSETC, &(tt->tc));\
ioctl (fd, TIOCSLTC, &(tt->lc));\
ioctl (fd, TIOCSETD, &(tt->line));\
ioctl (fd, TIOCLSET, &(tt->local));\
} while (0)
#endif /* HAVE_TERMIOS_H */
/*}}} */

/*{{{ debug_ttymode() */
#ifdef DEBUG_TTYMODE
static void
debug_ttymode (ttymode_t * ttymode)
{
#ifdef HAVE_TERMIOS_H
  /* c_iflag bits */
  fprintf (stderr, "Input flags\n");

  /* cpp token stringize doesn't work on all machines <sigh> */
#define FOO(flag,name) \
if ((ttymode->c_iflag) & flag) fprintf (stderr, "%s ", name)

  /* c_iflag bits */
  FOO (IGNBRK, "IGNBRK");
  FOO (BRKINT, "BRKINT");
  FOO (IGNPAR, "IGNPAR");
  FOO (PARMRK, "PARMRK");
  FOO (INPCK, "INPCK");
  FOO (ISTRIP, "ISTRIP");
  FOO (INLCR, "INLCR");
  FOO (IGNCR, "IGNCR");
  FOO (ICRNL, "ICRNL");
  FOO (IXON, "IXON");
  FOO (IXOFF, "IXOFF");
#ifdef IUCLC
  FOO (IUCLC, "IUCLC");
#endif
#ifdef IXANY
  FOO (IXANY, "IXANY");
#endif
#ifdef IMAXBEL
  FOO (IMAXBEL, "IMAXBEL");
#endif
  fprintf (stderr, "\n\n");

#undef FOO
#define FOO(entry, name) \
fprintf (stderr, "%s = %#3o\n", name, ttymode->c_cc [entry])
  FOO (VINTR, "VINTR");
  FOO (VQUIT, "VQUIT");
  FOO (VERASE, "VERASE");
  FOO (VKILL, "VKILL");
  FOO (VEOF, "VEOF");
  FOO (VEOL, "VEOL");
#ifdef VEOL2
  FOO (VEOL2, "VEOL2");
#endif
#ifdef VSWTC
  FOO (VSWTC, "VSWTC");
#endif
#ifdef VSWTCH
  FOO (VSWTCH, "VSWTCH");
#endif
  FOO (VSTART, "VSTART");
  FOO (VSTOP, "VSTOP");
  FOO (VSUSP, "VSUSP");
#ifdef VDSUSP
  FOO (VDSUSP, "VDSUSP");
#endif
#ifdef VREPRINT
  FOO (VREPRINT, "VREPRINT");
#endif
#ifdef VDISCRD
  FOO (VDISCRD, "VDISCRD");
#endif
#ifdef VWERSE
  FOO (VWERSE, "VWERSE");
#endif
#ifdef VLNEXT
  FOO (VLNEXT, "VLNEXT");
#endif
  fprintf (stderr, "\n\n");

#undef FOO

#endif /* HAVE_TERMIOS_H */
}
#endif /* DEBUG_TTYMODE */
/*}}} */

/*{{{ get_ttymode() */
static void
get_ttymode (ttymode_t * tio)
{
#ifdef HAVE_TERMIOS_H
  /*
   * standard System V termios interface
   */
  if (GET_TERMIOS (0, tio) < 0)
    {
      /* return error - use system defaults */
      tio->c_cc[VINTR] = CINTR;
      tio->c_cc[VQUIT] = CQUIT;
      tio->c_cc[VERASE] = CERASE;
      tio->c_cc[VKILL] = CKILL;
      tio->c_cc[VSTART] = CSTART;
      tio->c_cc[VSTOP] = CSTOP;
      tio->c_cc[VSUSP] = CSUSP;
#ifdef VDSUSP
      tio->c_cc[VDSUSP] = CDSUSP;
#endif
#ifdef VREPRINT
      tio->c_cc[VREPRINT] = CRPRNT;
#endif
#ifdef VDISCRD
      tio->c_cc[VDISCRD] = CFLUSH;
#endif
#ifdef VWERSE
      tio->c_cc[VWERSE] = CWERASE;
#endif
#ifdef VLNEXT
      tio->c_cc[VLNEXT] = CLNEXT;
#endif
    }

  tio->c_cc[VEOF] = CEOF;
  tio->c_cc[VEOL] = VDISABLE;
#ifdef VEOL2
  tio->c_cc[VEOL2] = VDISABLE;
#endif
#ifdef VSWTC
  tio->c_cc[VSWTC] = VDISABLE;
#endif
#ifdef VSWTCH
  tio->c_cc[VSWTCH] = VDISABLE;
#endif
#if VMIN != VEOF
  tio->c_cc[VMIN] = 1;
#endif
#if VTIME != VEOL
  tio->c_cc[VTIME] = 0;
#endif

  /* input modes */
  tio->c_iflag = (BRKINT | IGNPAR | ICRNL | IXON
#ifdef IMAXBEL
		  | IMAXBEL
#endif
    );

  /* output modes */
  tio->c_oflag = (OPOST | ONLCR);

  /* control modes */
  tio->c_cflag = (CS8 | CREAD);

  /* line discipline modes */
  tio->c_lflag = (ISIG | ICANON | IEXTEN | ECHO | ECHOE | ECHOK
#if defined (ECHOCTL) && defined (ECHOKE)
		  | ECHOCTL | ECHOKE
#endif
    );

  /*
   * guess an appropriate value for Backspace
   */
#ifdef DONT_GUESS_BACKSPACE
  PrivMode (1, PrivMode_BackSpace);	/* always ^H */
#else
  PrivMode ((tio->c_cc[VERASE] == '\b'), PrivMode_BackSpace);
#endif /* DONT_GUESS_BACKSPACE */

#else /* HAVE_TERMIOS_H */

  /*
   * sgtty interface
   */

  /* get parameters -- gtty */
  if (ioctl (0, TIOCGETP, &(tio->sg)) < 0)
    {
      tio->sg.sg_erase = CERASE;	/* ^H */
      tio->sg.sg_kill = CKILL;	/* ^U */
    }
  tio->sg.sg_flags = (CRMOD | ECHO | EVENP | ODDP);

  /* get special characters */
  if (ioctl (0, TIOCGETC, &(tio->tc)) < 0)
    {
      tio->tc.t_intrc = CINTR;	/* ^C */
      tio->tc.t_quitc = CQUIT;	/* ^\ */
      tio->tc.t_startc = CSTART;	/* ^Q */
      tio->tc.t_stopc = CSTOP;	/* ^S */
      tio->tc.t_eofc = CEOF;	/* ^D */
      tio->tc.t_brkc = -1;
    }

  /* get local special chars */
  if (ioctl (0, TIOCGLTC, &(tio->lc)) < 0)
    {
      tio->lc.t_suspc = CSUSP;	/* ^Z */
      tio->lc.t_dsuspc = CDSUSP;	/* ^Y */
      tio->lc.t_rprntc = CRPRNT;	/* ^R */
      tio->lc.t_flushc = CFLUSH;	/* ^O */
      tio->lc.t_werasc = CWERASE;	/* ^W */
      tio->lc.t_lnextc = CLNEXT;	/* ^V */
    }

  /* get line discipline */
  ioctl (0, TIOCGETD, &(tio->line));
#ifdef NTTYDISC
  tio->line = NTTYDISC;
#endif /* NTTYDISC */
  tio->local = (LCRTBS | LCRTERA | LCTLECH | LPASS8 | LCRTKIL);

  /*
   * guess an appropriate value for Backspace
   */
#ifdef DONT_GUESS_BACKSPACE
  PrivMode (1, PrivMode_BackSpace);	/* always ^H */
#else
  PrivMode ((tio->sg.sg_erase == '\b'), PrivMode_BackSpace);
#endif /* DONT_GUESS_BACKSPACE */

#endif /* HAVE_TERMIOS_H */
}
/*}}} */

/*{{{ run_command() */
/*
 * Run the command in a subprocess and return a file descriptor for the
 * master end of the pseudo-teletype pair with the command talking to
 * the slave.
 */
static int
run_command (char *argv[])
{
  ttymode_t tio;
  int ptyfd;

  /* Save and then give up any super-user privileges */
  privileges (SAVE);
  privileges (IGNORE);

  ptyfd = get_pty ();
  if (ptyfd < 0)
    return -1;

  /* store original tty status for restoration clean_exit() -- rgg 04/12/95 */
  lstat (ttydev, &ttyfd_stat);
#ifdef DEBUG_CMD
  fprintf (stderr, "Original settings of %s are mode %o, uid %d, gid %d\n",
	   ttydev, ttyfd_stat.st_mode, ttyfd_stat.st_uid, ttyfd_stat.st_gid);
#endif

  /* install exit handler for cleanup */
#ifdef HAVE_ATEXIT
  atexit (clean_exit);
#else
#if defined (__sun__)
  on_exit (clean_exit, NULL);	/* non-ANSI exit handler */
#else
  print_error ("no atexit(), UTMP entries can't be cleaned");
#endif
#endif

  /*
   * get tty settings before fork()
   * and make a reasonable guess at the value for BackSpace
   */
  get_ttymode (&tio);
  /* add Backspace value */
  SavedModes |= (PrivateModes & PrivMode_BackSpace);

  /* add value for scrollBar */
  if (scrollbar_visible ())
    {
      PrivateModes |= PrivMode_scrollBar;
      SavedModes |= PrivMode_scrollBar;
    }

#ifdef DEBUG_TTYMODE
  debug_ttymode (&tio);
#endif

  /* spin off the command interpreter */
  signal (SIGHUP, Exit_signal);
#ifndef __svr4__
  signal (SIGINT, Exit_signal);
#endif
  signal (SIGQUIT, Exit_signal);
  signal (SIGTERM, Exit_signal);
  signal (SIGCHLD, Child_signal);

  /* need to trap SIGURG for SVR4 (Unixware) rlogin */
  /* signal (SIGURG, SIG_DFL); */

  cmd_pid = fork ();
  if (cmd_pid < 0)
    {
      print_error ("can't fork");
      return -1;
    }
  if (cmd_pid == 0)		/* child */
    {
      /* signal (SIGHUP, Exit_signal); */
      /* signal (SIGINT, Exit_signal); */
#ifdef HAVE_UNSETENV
      /* avoid passing old settings and confusing term size */
      unsetenv ("LINES");
      unsetenv ("COLUMNS");
      /* avoid passing termcap since terminfo should be okay */
      unsetenv ("TERMCAP");
#endif /* HAVE_UNSETENV */
      /* establish a controlling teletype for the new session */
      get_tty ();

      /* initialize terminal attributes */
      SET_TTYMODE (0, &tio);

      /* become virtual console, fail silently */
      if (Options & Opt_console)
	{
#ifdef TIOCCONS
	  unsigned int on = 1;
	  ioctl (0, TIOCCONS, &on);
#elif defined (SRIOCREDIR)
	  int fd = open (CONSOLE, O_WRONLY);
	  if (fd < 0 || ioctl (0, SRIOCSREDIR, fd) < 0)
	    {
	      if (fd >= 0)
		close (fd);
	    }
#endif /* SRIOCSREDIR */
	}
      tt_winsize (0);		/* set window size */

      /* reset signals and spin off the command interpreter */
      signal (SIGINT, SIG_DFL);
      signal (SIGQUIT, SIG_DFL);
      signal (SIGCHLD, SIG_DFL);
      /*
       * mimick login's behavior by disabling the job control signals
       * a shell that wants them can turn them back on
       */
#ifdef SIGTSTP
      signal (SIGTSTP, SIG_IGN);
      signal (SIGTTIN, SIG_IGN);
      signal (SIGTTOU, SIG_IGN);
#endif /* SIGTSTP */

      /* command interpreter path */
      if (argv != NULL)
	{
#ifdef DEBUG_CMD
	  int i;
	  for (i = 0; argv[i]; i++)
	    fprintf (stderr, "argv [%d] = \"%s\"\n", i, argv[i]);
#endif
	  execvp (argv[0], argv);
	  print_error ("can't execute \"%s\"", argv[0]);
	}
      else
	{
	  const char *argv0, *shell;

	  if ((shell = getenv ("SHELL")) == NULL || *shell == '\0')
	    shell = "/bin/sh";

	  argv0 = my_basename (shell);
	  if (Options & Opt_loginShell)
	    {
	      char *p = MALLOC ((strlen (argv0) + 2) * sizeof (char), argv0);
	      p[0] = '-';
	      strcpy (&p[1], argv0);
	      argv0 = p;
	    }
	  execlp (shell, argv0, NULL);
	  print_error ("can't execute \"%s\"", shell);
	}
      exit (EXIT_FAILURE);
    }

  privileges (RESTORE);
  if (!(Options & Opt_utmpInhibit))
    makeutent (ttydev, display_name);	/* stamp /etc/utmp */
  privileges (IGNORE);

  return ptyfd;
}
/*}}} */

/*{{{ init_command() */
void
init_command (char *argv[])
{
  /*
   * Initialize the command connection.
   * This should be called after the X server connection is established.
   */

  /* Enable delete window protocol */
  wmDeleteWindow = XInternAtom (Xdisplay, "WM_DELETE_WINDOW", False);
  XSetWMProtocols (Xdisplay, TermWin.parent, &wmDeleteWindow, 1);

#ifdef OFFIX_DND
  /* Enable OffiX Dnd (drag 'n' drop) protocol */
  DndProtocol = XInternAtom (Xdisplay, "DndProtocol", False);
  DndSelection = XInternAtom (Xdisplay, "DndSelection", False);
#endif /* OFFIX_DND */

  init_xlocale ();

  /* get number of available file descriptors */
#ifdef _POSIX_VERSION
  num_fds = sysconf (_SC_OPEN_MAX);
#else
  num_fds = getdtablesize ();
#endif

#ifdef META8_OPTION
  meta_char = (Options & Opt_meta8 ? 0x80 : 033);
#endif

#ifdef GREEK_SUPPORT
  greek_init ();
#endif

  Xfd = XConnectionNumber (Xdisplay);
  cmdbuf_ptr = cmdbuf_endp = cmdbuf_base;

  if ((cmd_fd = run_command (argv)) < 0)
    {
      print_error ("aborting");
      exit (EXIT_FAILURE);
    }
}
/*}}} */

/*{{{ Xlocale */
/*
 * This is more or less stolen straight from XFree86 xterm.
 * This should support all European type languages.
 */
#ifndef NO_XLOCALE
static void
init_xlocale (void)
{
  char *p, *s, buf[32], tmp[1024];
  XIM xim = NULL;
  XIMStyle input_style = 0;
  XIMStyles *xim_styles = NULL;
  int found;

  Input_Context = NULL;

#ifdef KANJI
  setlocale (LC_CTYPE, "");
#endif

  if (rs_inputMethod == NULL
#ifndef KANJI
      || !*rs_inputMethod	/* required ? */
#endif
    )
    {
      if ((p = XSetLocaleModifiers ("@im=none")) != NULL && *p)
	xim = XOpenIM (Xdisplay, NULL, NULL, NULL);
    }
  else
    {
      strcpy (tmp, rs_inputMethod);
      for (s = tmp; *s; /*nil */ )
	{
	  char *end, *next_s;
	  while (*s && isspace (*s))
	    s++;
	  if (!*s)
	    break;
	  end = s;
	  while (*end && (*end != ','))
	    end++;
	  next_s = end--;
	  while ((end >= s) && isspace (*end))
	    end--;
	  *(end + 1) = '\0';

	  if (*s)
	    {
	      strcpy (buf, "@im=");
	      strcat (buf, s);
	      if ((p = XSetLocaleModifiers (buf)) != NULL && *p &&
		  (xim = XOpenIM (Xdisplay, NULL, NULL, NULL)) != NULL)
		break;
	    }
	  if (!*next_s)
	    break;
	  s = (next_s + 1);
	}
    }

  if (xim == NULL && (p = XSetLocaleModifiers ("")) != NULL && *p)
    xim = XOpenIM (Xdisplay, NULL, NULL, NULL);

  if (xim == NULL)
    {
      print_error ("Failed to open input method");
      return;
    }

  if (XGetIMValues (xim, XNQueryInputStyle, &xim_styles, NULL) || !xim_styles)
    {
      print_error ("input method doesn't support any style");
      XCloseIM (xim);
      return;
    }

  strcpy (tmp, (rs_preeditType ? rs_preeditType : "Root"));
  for (found = 0, s = tmp; *s && !found; /*nil */ )
    {
      unsigned short i;
      char *end, *next_s;

      while (*s && isspace (*s))
	s++;
      if (!*s)
	break;
      end = s;
      while (*end && (*end != ','))
	end++;
      next_s = end--;
      while ((end >= s) && isspace (*end))
	*end-- = 0;

      if (!strcmp (s, "OverTheSpot"))
	input_style = (XIMPreeditPosition | XIMStatusArea);
      else if (!strcmp (s, "OffTheSpot"))
	input_style = (XIMPreeditArea | XIMStatusArea);
      else if (!strcmp (s, "Root"))
	input_style = (XIMPreeditNothing | XIMStatusNothing);

      for (i = 0; i < xim_styles->count_styles; i++)
	{
	  if (input_style == xim_styles->supported_styles[i])
	    {
	      found = 1;
	      break;
	    }
	}
      s = next_s;
    }
  XFree (xim_styles);

  if (found == 0)
    {
      print_error ("input method doesn't support my preedit type");
      XCloseIM (xim);
      return;
    }

  /*
   * This program only understands the Root preedit_style yet
   * Then misc.preedit_type should default to:
   *          "OverTheSpot,OffTheSpot,Root"
   *  /MaF
   */
  if (input_style != (XIMPreeditNothing | XIMStatusNothing))
    {
      print_error ("This program only supports the \"Root\" preedit type");
      XCloseIM (xim);
      return;
    }

  Input_Context = XCreateIC (xim, XNInputStyle, input_style,
			     XNClientWindow, TermWin.parent,
			     XNFocusWindow, TermWin.parent,
			     NULL);

  if (Input_Context == NULL)
    {
      print_error ("Failed to create input context");
      XCloseIM (xim);
    }
}
#endif /* NO_XLOCALE */
/*}}} */

/*{{{ window resizing */
/*
 * Tell the teletype handler what size the window is.
 * Called after a window size change.
 */
static void
tt_winsize (int fd)
{
  struct winsize ws;

  if (fd < 0)
    return;

  ws.ws_col = (unsigned short) TermWin.ncol;
  ws.ws_row = (unsigned short) TermWin.nrow;
  ws.ws_xpixel = ws.ws_ypixel = 0;
  ioctl (fd, TIOCSWINSZ, &ws);
}

void
tt_resize (void)
{
  tt_winsize (cmd_fd);
}
/*}}} */

/*{{{ Convert the keypress event into a string */
static void
lookup_key (XEvent * ev)
{
  static int numlock_state = 0;
#ifdef DEBUG_CMD
  static int debug_key = 1;	/* accessible by a debugger only */
#endif
#ifdef GREEK_SUPPORT
  static short greek_mode = 0;
#endif
  static XComposeStatus compose =
  {NULL, 0};
  static unsigned char kbuf[KBUFSZ];
  int ctrl, meta, shft, len;
  KeySym keysym;

  /*
   * use Num_Lock to toggle Keypad on/off.  If Num_Lock is off, allow an
   * escape sequence to toggle the Keypad.
   *
   * Always permit `shift' to override the current setting
   */
  shft = (ev->xkey.state & ShiftMask);
  ctrl = (ev->xkey.state & ControlMask);
  meta = (ev->xkey.state & Mod1Mask);
  if (numlock_state || (ev->xkey.state & Mod5Mask))
    {
      numlock_state = (ev->xkey.state & Mod5Mask);	/* numlock toggle */
      PrivMode ((!numlock_state), PrivMode_aplKP);
    }

#ifndef NO_XLOCALE
  if (!XFilterEvent (ev, *(&ev->xkey.window)))
    {
      if (Input_Context != NULL)
	{
	  Status status_return;
	  len = XmbLookupString (Input_Context, &ev->xkey, kbuf,
				 sizeof (kbuf), &keysym,
				 &status_return);
	}
      else
	{
	  len = XLookupString (&ev->xkey, kbuf,
			       sizeof (kbuf), &keysym,
			       &compose);
	}
    }
  else
    len = 0;
#else /* NO_XLOCALE */
  len = XLookupString (&ev->xkey, kbuf, sizeof (kbuf), &keysym, &compose);
  /*
   * have unmapped Latin[2-4] entries -> Latin1
   * good for installations  with correct fonts, but without XLOCAL
   */
  if (!len && (keysym >= 0x0100) && (keysym < 0x0400))
    {
      len = 1;
      kbuf[0] = (keysym & 0xFF);
    }
#endif /* NO_XLOCALE */

  /* for some backwards compatibility */
#if defined (HOTKEY_CTRL) || defined (HOTKEY_META)
#ifdef HOTKEY_CTRL
#define HOTKEY	ctrl
#else
#ifdef HOTKEY_META
#define HOTKEY	meta
#endif
#endif
  if (HOTKEY)
    {
      if (keysym == ks_bigfont)
	{
	  change_font (0, FONT_UP);
	  return;
	}
      else if (keysym == ks_smallfont)
	{
	  change_font (0, FONT_DN);
	  return;
	}
    }
#undef HOTKEY
#endif

  if (shft)
    {
      /* Shift + F1 - F10 generates F11 - F20 */
      if (keysym >= XK_F1 && keysym <= XK_F10)
	{
	  keysym += (XK_F11 - XK_F1);
	  shft = 0;		/* turn off Shift */
	}
      else if (!ctrl && !meta && (PrivateModes & PrivMode_ShiftKeys))
	{
	  switch (keysym)
	    {
	      /* normal XTerm key bindings */
	    case XK_Prior:	/* Shift+Prior = scroll back */
	      if (TermWin.saveLines)
		{
		  scr_page (UP, TermWin.nrow * 4 / 5);
		  return;
		}
	      break;

	    case XK_Next:	/* Shift+Next = scroll forward */
	      if (TermWin.saveLines)
		{
		  scr_page (DN, TermWin.nrow * 4 / 5);
		  return;
		}
	      break;

	    case XK_Insert:	/* Shift+Insert = paste mouse selection */
	      selection_request (ev->xkey.time, ev->xkey.x, ev->xkey.y);
	      return;
	      break;

	      /* rxvt extras */
	    case XK_KP_Add:	/* Shift+KP_Add = bigger font */
	      change_font (0, FONT_UP);
	      return;
	      break;

	    case XK_KP_Subtract:	/* Shift+KP_Subtract = smaller font */
	      change_font (0, FONT_DN);
	      return;
	      break;
	    }
	}
    }

  switch (keysym)
    {
    case XK_Print:
#ifdef DEBUG_SELECTION
      debug_selection ();
      return;
#else
#ifdef PRINTPIPE
      scr_printscreen (ctrl | shft);
      return;
#endif
#endif
      break;

    case XK_Mode_switch:
#ifdef GREEK_SUPPORT
      greek_mode = !greek_mode;
      if (greek_mode)
	{
	  xterm_seq (XTerm_title, (greek_getmode () == GREEK_ELOT928 ?
				   "[Greek: iso]" : "[Greek: ibm]"));
	  greek_reset ();
	}
      else
	xterm_seq (XTerm_title, APL_NAME "-" VERSION);
      return;
#endif
      break;
    }

  if (keysym >= 0xFF00 && keysym <= 0xFFFF)
    {
#ifdef KEYSYM_RESOURCE
      if (!(shft | ctrl) && KeySym_map[keysym - 0xFF00] != NULL)
	{
	  const unsigned char *kbuf;
	  unsigned int len;

	  kbuf = (KeySym_map[keysym - 0xFF00]);
	  len = *kbuf++;

	  /* escape prefix */
	  if (meta
#ifdef META8_OPTION
	      && (meta_char == 033)
#endif
	    )
	    {
	      const unsigned char ch = '\033';
	      tt_write (&ch, 1);
	    }
	  tt_write (kbuf, len);
	  return;
	}
      else
#endif
	switch (keysym)
	  {
	  case XK_BackSpace:
	    len = 1;
	    kbuf[0] = (((PrivateModes & PrivMode_BackSpace) ?
			!(shft | ctrl) : (shft | ctrl)) ? '\b' : '\177');
	    break;

	  case XK_Tab:
	    if (shft)
	      {
		len = 3;
		strcpy (kbuf, "\033[Z");
	      }
	    break;

	  case XK_Home:
	    len = strlen (strcpy (kbuf, KS_HOME));
	    break;
	  case XK_Left:	/* "\033[D" */
	  case XK_Up:		/* "\033[A" */
	  case XK_Right:	/* "\033[C" */
	  case XK_Down:	/* "\033[B" */
	    len = 3;
	    strcpy (kbuf, "\033[@");
	    kbuf[2] = ("DACB"[keysym - XK_Left]);
	    if (PrivateModes & PrivMode_aplCUR)
	      {
		kbuf[1] = 'O';
	      }
	    /* do Shift first */
	    else if (shft)
	      {
		kbuf[2] = ("dacb"[keysym - XK_Left]);
	      }
	    else if (ctrl)
	      {
		kbuf[1] = 'O';
		kbuf[2] = ("dacb"[keysym - XK_Left]);
	      }
	    break;
	  case XK_Prior:
	    len = 4;
	    strcpy (kbuf, "\033[5~");
	    break;
	  case XK_Next:
	    len = 4;
	    strcpy (kbuf, "\033[6~");
	    break;
	  case XK_End:
	    len = strlen (strcpy (kbuf, KS_END));
	    break;

	  case XK_Select:
	    len = 4;
	    strcpy (kbuf, "\033[4~");
	    break;
	  case XK_Execute:
	    len = 4;
	    strcpy (kbuf, "\033[3~");
	    break;
	  case XK_Insert:
	    len = 4;
	    strcpy (kbuf, "\033[2~");
	    break;

	  case XK_Menu:
	    len = 5;
	    strcpy (kbuf, "\033[29~");
	    break;
	  case XK_Find:
	    len = 4;
	    strcpy (kbuf, "\033[1~");
	    break;
	  case XK_Help:
	    len = 5;
	    strcpy (kbuf, "\033[28~");
	    break;

	  case XK_KP_Enter:
	    /* allow shift to send normal "\033OM" */
	    if ((PrivateModes & PrivMode_aplKP) ? !shft : shft)
	      {
		len = 1;
		kbuf[0] = '\r';
	      }
	    else
	      {
		len = 3;
		strcpy (kbuf, "\033OM");
	      }
	    break;

	  case XK_KP_F1:	/* "\033OP" */
	  case XK_KP_F2:	/* "\033OQ" */
	  case XK_KP_F3:	/* "\033OR" */
	  case XK_KP_F4:	/* "\033OS" */
	    len = 3;
	    strcpy (kbuf, "\033OP");
	    kbuf[2] += (keysym - XK_KP_F1);
	    break;

	  case XK_KP_Multiply:	/* "\033Oj" : "*" */
	  case XK_KP_Add:	/* "\033Ok" : "+" */
	  case XK_KP_Separator:	/* "\033Ol" : "," */
	  case XK_KP_Subtract:	/* "\033Om" : "-" */
	  case XK_KP_Decimal:	/* "\033On" : "." */
	  case XK_KP_Divide:	/* "\033Oo" : "/" */
	  case XK_KP_0:	/* "\033Op" : "0" */
	  case XK_KP_1:	/* "\033Oq" : "1" */
	  case XK_KP_2:	/* "\033Or" : "2" */
	  case XK_KP_3:	/* "\033Os" : "3" */
	  case XK_KP_4:	/* "\033Ot" : "4" */
	  case XK_KP_5:	/* "\033Ou" : "5" */
	  case XK_KP_6:	/* "\033Ov" : "6" */
	  case XK_KP_7:	/* "\033Ow" : "7" */
	  case XK_KP_8:	/* "\033Ox" : "8" */
	  case XK_KP_9:	/* "\033Oy" : "9" */
	    /* allow shift to override 'numlock_off' */
	    if ((PrivateModes & PrivMode_aplKP) == 0)
	      {
		/* if numlock = off, for non numerics do : */
		len = 1;
		kbuf[0] = ('*' + (keysym - XK_KP_Multiply));
	      }
	    break;

	  case XK_KP_Left:	/* "\033[D" */
	  case XK_KP_Up:	/* "\033[A" */
	  case XK_KP_Right:	/* "\033[C" */
	  case XK_KP_Down:	/* "\033[B" */
	    len = 3;
	    strcpy (kbuf, "\033[@");
	    kbuf[2] = ("DACB"[keysym - XK_KP_Left]);
	    if (PrivateModes & PrivMode_aplCUR)
	      {
		kbuf[1] = 'O';
	      }
	    else if (ctrl)
	      {
		kbuf[1] = 'O';
		kbuf[2] = ("dacb"[keysym - XK_KP_Left]);
	      }
	    break;
	  case XK_KP_Prior:
	    len = 4;
	    strcpy (kbuf, "\033[5~");
	    break;
	  case XK_KP_Next:
	    len = 4;
	    strcpy (kbuf, "\033[6~");
	    break;
	  case XK_KP_End:
	    len = strlen (strcpy (kbuf, KS_END));
	    break;
	  case XK_KP_Insert:
	    len = 4;
	    strcpy (kbuf, "\033[2~");
	    break;
	  case XK_KP_Home:
	    len = strlen (strcpy (kbuf, KS_HOME));
	    break;
	  case XK_KP_Delete:
	    len = strlen (strcpy (kbuf, KS_DELETE));
	    break;

/* Nothing associated with case KP_Begin: at the moment ... */

#define FKEY(n,fkey) len = 5;\
sprintf (kbuf,"\033[%02d~", (int)((n) + (keysym - fkey)))

	  case XK_F1:		/* "\033[11~" */
	  case XK_F2:		/* "\033[12~" */
	  case XK_F3:		/* "\033[13~" */
	  case XK_F4:		/* "\033[14~" */
	  case XK_F5:		/* "\033[15~" */
	    FKEY (11, XK_F1);
	    break;

	  case XK_F6:		/* "\033[17~" */
	  case XK_F7:		/* "\033[18~" */
	  case XK_F8:		/* "\033[19~" */
	  case XK_F9:		/* "\033[20~" */
	  case XK_F10:		/* "\033[21~" */
	    FKEY (17, XK_F6);
	    break;

	  case XK_F11:		/* "\033[23~" */
	  case XK_F12:		/* "\033[24~" */
	  case XK_F13:		/* "\033[25~" */
	  case XK_F14:		/* "\033[26~" */
	    FKEY (23, XK_F11);
	    break;

	  case XK_F15:		/* "\033[28~" */
	  case XK_F16:		/* "\033[29~" */
	    FKEY (28, XK_F15);
	    break;

	  case XK_F17:		/* "\033[31~" */
	  case XK_F18:		/* "\033[32~" */
	  case XK_F19:		/* "\033[33~" */
	  case XK_F20:		/* "\033[34~" */
	  case XK_F21:		/* "\033[35~" */
	  case XK_F22:		/* "\033[36~" */
	  case XK_F23:		/* "\033[37~" */
	  case XK_F24:		/* "\033[38~" */
	  case XK_F25:		/* "\033[39~" */
	  case XK_F26:		/* "\033[40~" */
	  case XK_F27:		/* "\033[41~" */
	  case XK_F28:		/* "\033[42~" */
	  case XK_F29:		/* "\033[43~" */
	  case XK_F30:		/* "\033[44~" */
	  case XK_F31:		/* "\033[45~" */
	  case XK_F32:		/* "\033[46~" */
	  case XK_F33:		/* "\033[47~" */
	  case XK_F34:		/* "\033[48~" */
	  case XK_F35:		/* "\033[49~" */
	    FKEY (31, XK_F17);
	    break;
#undef FKEY
#ifdef KS_DELETE
	  case XK_Delete:
	    len = strlen (strcpy (kbuf, KS_DELETE));
	    break;
#endif
	  }
    }
  else if (ctrl && keysym == XK_minus)
    {
      len = 1;
      kbuf[0] = '\037';		/* Ctrl-Minus generates ^_ (31) */
    }
  else
    {
#ifdef META8_OPTION
      /* set 8-bit on */
      if (meta && (meta_char == 0x80))
	{
	  unsigned char *ch;
	  for (ch = kbuf; ch < kbuf + len; ch++)
	    *ch |= 0x80;
	  meta = 0;
	}
#endif
#ifdef GREEK_SUPPORT
      if (greek_mode)
	len = greek_xlat (kbuf, len);
#endif
      /*nil */ ;
    }

  if (len <= 0)
    return;			/* not mapped */

  /*
   * these modifications only affect the static keybuffer
   * pass Shift/Control indicators for function keys ending with `~'
   *
   * eg,
   *   Prior = "ESC[5~"
   *   Shift+Prior = "ESC[5~"
   *   Ctrl+Prior = "ESC[5^"
   *   Ctrl+Shift+Prior = "ESC[5@"
   */
  if (kbuf[0] == '\033' && kbuf[1] == '[' && kbuf[len - 1] == '~')
    kbuf[len - 1] = (shft ? (ctrl ? '@' : '$') : (ctrl ? '^' : '~'));

  /* escape prefix */
  if (meta
#ifdef META8_OPTION
      && (meta_char == 033)
#endif
    )
    {
      const unsigned char ch = '\033';
      tt_write (&ch, 1);
    }

#ifdef DEBUG_CMD
  if (debug_key)		/* Display keyboard buffer contents */
    {
      char *p;
      int i;

      fprintf (stderr, "key 0x%04X [%d]: `", (unsigned int) keysym, len);
      for (i = 0, p = kbuf; i < len; i++, p++)
	fprintf (stderr, (*p >= ' ' && *p < '\177' ? "%c" : "\\%03o"), *p);
      fprintf (stderr, "'\n");
    }
#endif /* DEBUG_CMD */
  tt_write (kbuf, len);
}
/*}}} */

/*{{{ cmd_ungetc(), cmd_getc() */
static void
cmd_ungetc (unsigned char ch)
{
  if (cmdbuf_ptr > cmdbuf_base)
    {
      /* sneak one in */
      cmdbuf_ptr--;
      *cmdbuf_ptr = ch;
    }
  else
    {
      int n = (cmdbuf_endp - cmdbuf_base);
      if (n > 0)
	{
	  if (n == sizeof (cmdbuf_base))
	    {
	      /* drop one off the end to make room */
	      n--;
	      cmdbuf_endp--;
	    }
	  memmove ((cmdbuf_base + 1), cmdbuf_base, n);
	}
      *cmdbuf_ptr = ch;
      cmdbuf_endp++;
    }
}

/* attempt to `write' COUNT to the input buffer */
unsigned int
cmd_write (const unsigned char *str, unsigned int count)
{
  while (count--)
    cmd_ungetc (str[count]);

  return 0;
}
/* cmd_getc() - Return next input character */
/*
 * Return the next input character after first passing any keyboard input
 * to the command.
 */
static unsigned char
cmd_getc (void)
{
#define TIMEOUT_USEC	5000
  static short refreshed = 0;
  fd_set readfds;
  int retval;
  struct itimerval value;

  /* If there have been a lot of new lines, then update the screen
   * What the heck I'll cheat and only refresh less than every page-full.
   * the number of pages between refreshes is refresh_limit, which
   * is incremented here because we must be doing flat-out scrolling.
   *
   * refreshing should be correct for small scrolls, because of the
   * time-out */
  if (refresh_count > (refresh_limit * TermWin.nrow))
    {
      if (refresh_limit < REFRESH_PERIOD)
	refresh_limit++;
      refresh_count = 0;
      refreshed = 1;
      scr_refresh (refresh_type);
    }

  /* characters already read in */
  if (cmdbuf_ptr < cmdbuf_endp)
    goto Return_Char;

  while (1)
    {
      while (XPending (Xdisplay))	/* process pending X events */
	{
	  XEvent ev;
	  refreshed = 0;
	  XNextEvent (Xdisplay, &ev);
	  process_x_event (&ev);

	  /* in case button actions pushed chars to cmdbuf */
	  if (cmdbuf_ptr < cmdbuf_endp)
	    goto Return_Char;
	}
      /* Nothing to do! */
      FD_ZERO (&readfds);
      FD_SET (cmd_fd, &readfds);
      FD_SET (Xfd, &readfds);
      value.it_value.tv_usec = TIMEOUT_USEC;
      value.it_value.tv_sec = 0;

      retval = select (num_fds, &readfds, NULL, NULL,
		       (refreshed ? NULL : &value.it_value));

      /* See if we can read from the application */
      if (FD_ISSET (cmd_fd, &readfds))
	{
	  unsigned int count = sizeof (cmdbuf_base) - 1;
	  cmdbuf_ptr = cmdbuf_endp = cmdbuf_base;

	  /* while (count > sizeof(cmdbuf_base) / 2) */
	  while (count)
	    {
	      int n = read (cmd_fd, cmdbuf_endp, count);
	      if (n <= 0)
		break;
	      cmdbuf_endp += n;
	      count -= n;
	    }
	  /* some characters read in */
	  if (cmdbuf_ptr < cmdbuf_endp)
	    goto Return_Char;
	}
      /* select statement timed out - better update the screen */
      if (retval == 0)
	{
	  refresh_count = 0;
	  refresh_limit = 1;
	  if (!refreshed)
	    {
	      refreshed = 1;
	      scr_refresh (refresh_type);
	      scrollbar_show (1);
	    }
	}
    }
  return 0;

Return_Char:
  refreshed = 0;
  return (*cmdbuf_ptr++);
}
/*}}} */

/*{{{ process an X event */
static void
process_x_event (XEvent * ev)
{
  static Time buttonpress_time;
  static int clicks = 0;
#define clickOnce()	(clicks <= 1)
  static int bypass_keystate = 0;
  int reportmode;

  switch (ev->type)
    {
    case KeyPress:
      lookup_key (ev);
      break;

    case ClientMessage:
      if (ev->xclient.format == 32 && ev->xclient.data.l[0] == wmDeleteWindow)
	exit (EXIT_SUCCESS);
#ifdef OFFIX_DND
      /* OffiX Dnd (drag 'n' drop) protocol */
      if (ev->xclient.message_type == DndProtocol &&
	  (ev->xclient.data.l[0] == DndFile) ||
	  (ev->xclient.data.l[0] == DndDir) ||
	  (ev->xclient.data.l[0] == DndLink))
	{
	  /* Get Dnd data */
	  Atom ActualType;
	  int ActualFormat;
	  unsigned char *data;
	  unsigned long Size, RemainingBytes;
	  XGetWindowProperty (Xdisplay, Xroot,
			      DndSelection,
			      0L, 1000000L,
			      False, AnyPropertyType,
			      &ActualType, &ActualFormat,
			      &Size, &RemainingBytes,
			      &data);
	  XChangeProperty (Xdisplay, Xroot,
			   XA_CUT_BUFFER0, XA_STRING,
			   8, PropModeReplace,
			   data, strlen (data));
	  selection_paste (Xroot, XA_CUT_BUFFER0, True);
	  XSetInputFocus (Xdisplay, Xroot, RevertToNone, CurrentTime);
	}
#endif /* OFFIX_DND */
      break;

    case MappingNotify:
      XRefreshKeyboardMapping (&(ev->xmapping));
      break;

      /* Here's my conclusiion:
       * If the window is completely unobscured, use bitblt's
       * to scroll. Even then, they're only used when doing partial
       * screen scrolling. When partially obscured, we have to fill
       * in the GraphicsExpose parts, which means that after each refresh,
       * we need to wait for the graphics expose or Noexpose events,
       * which ought to make things real slow!
       */
    case VisibilityNotify:
      switch (ev->xvisibility.state)
	{
	case VisibilityUnobscured:
	  refresh_type = FAST_REFRESH;
	  break;

	case VisibilityPartiallyObscured:
	  refresh_type = SLOW_REFRESH;
	  break;

	default:
	  refresh_type = NO_REFRESH;
	  break;
	}
      break;

    case FocusIn:
      if (!TermWin.focus)
	{
	  TermWin.focus = 1;
#ifndef NO_XLOCALE
	  if (Input_Context != NULL)
	    XSetICFocus (Input_Context);
#endif
	}
      break;

    case FocusOut:
      if (TermWin.focus)
	{
	  TermWin.focus = 0;
#ifndef NO_XLOCALE
	  if (Input_Context != NULL)
	    XUnsetICFocus (Input_Context);
#endif
	}
      break;

    case ConfigureNotify:
      resize_window ();
      break;

    case SelectionClear:
      selection_clear ();
      break;

    case SelectionNotify:
      selection_paste (ev->xselection.requestor, ev->xselection.property, True);
      break;

    case SelectionRequest:
      selection_send (&(ev->xselectionrequest));
      break;

    case GraphicsExpose:
    case Expose:
      if (ev->xany.window == TermWin.vt)
	{
	  scr_expose (ev->xexpose.x, ev->xexpose.y,
		      ev->xexpose.width, ev->xexpose.height);
	}
      else
	{
	  XEvent unused_xevent;

	  while (XCheckTypedWindowEvent (Xdisplay, ev->xany.window,
					 Expose,
					 &unused_xevent));
	  while (XCheckTypedWindowEvent (Xdisplay, ev->xany.window,
					 GraphicsExpose,
					 &unused_xevent));
	  if (isScrollbarWindow (ev->xany.window))
	    {
	      scrollbar_setNone ();
	      scrollbar_show (0);
	    }
	  Gr_expose (ev->xany.window);
	}
      break;

    case ButtonPress:
      bypass_keystate = (ev->xbutton.state & (Mod1Mask | ShiftMask));
      reportmode = (bypass_keystate ?
		    0 : (PrivateModes & PrivMode_mouse_report));

      if (ev->xany.window == TermWin.vt)
	{
	  if (ev->xbutton.subwindow != None)
	    Gr_ButtonPress (ev->xbutton.x, ev->xbutton.y);
	  else
	    {
	      if (reportmode)
		{
		  if (reportmode & PrivMode_MouseX10)
		    {
		      /* no state info allowed */
		      ev->xbutton.state = 0;
		    }
#ifdef MOUSE_REPORT_DOUBLECLICK
		  if (ev->xbutton.button == Button1)
		    {
		      if (ev->xbutton.time - buttonpress_time < MULTICLICK_TIME)
			clicks++;
		      else
			clicks = 1;
		    }
#else
		  clicks = 1;
#endif /* MOUSE_REPORT_DOUBLECLICK */
		  mouse_report (&(ev->xbutton));
		}
	      else
		switch (ev->xbutton.button)
		  {
		  case Button1:
		    if (ev->xbutton.time - buttonpress_time < MULTICLICK_TIME)
		      clicks++;
		    else
		      clicks = 1;
		    selection_click (clicks, ev->xbutton.x, ev->xbutton.y);
		    break;

		  case Button3:
		    selection_extend (ev->xbutton.x, ev->xbutton.y);
		    break;
		  }
	      buttonpress_time = ev->xbutton.time;
	      return;
	    }
	}

      if (isScrollbarWindow (ev->xany.window))
	{
	  scrollbar_setNone ();
	  /*
	   * Rxvt-style scrollbar:
	   * move up if mouse if above slider
	   * move dn if mouse if below slider
	   *
	   * XTerm-style scrollbar:
	   * Move display proportional to pointer location
	   * pointer near top -> scroll one line
	   * pointer near bot -> scroll full page
	   */
#ifndef NO_SCROLLBAR_REPORT
	  if (reportmode)
	    {
	      /*
	       * Mouse report disabled scrollbar:
	       * arrow buttons - send up/down
	       * click on scrollbar - send pageup/down
	       */
	      if (scrollbar_upButton (ev->xbutton.y))
		tt_printf ("\033[A");
	      else if (scrollbar_dnButton (ev->xbutton.y))
		tt_printf ("\033[B");
	      else
		switch (ev->xbutton.button)
		  {
		  case Button2:
		    tt_printf ("\014");
		    break;
		  case Button1:
		    tt_printf ("\033[6~");
		    break;
		  case Button3:
		    tt_printf ("\033[5~");
		    break;
		  }
	    }
	  else
#endif /* NO_SCROLLBAR_REPORT */
	    {
	      if (scrollbar_upButton (ev->xbutton.y))
		{
		  /* I would like continuous scrolling */
		  if (scr_page (UP, 1))
		    scrollbar_setUp ();
		}
	      else if (scrollbar_dnButton (ev->xbutton.y))
		{
		  if (scr_page (DN, 1))
		    scrollbar_setDn ();
		}
	      else
		switch (ev->xbutton.button)
		  {
		  case Button2:
#ifndef XTERM_SCROLLBAR
		    if (scrollbar_above_slider (ev->xbutton.y) ||
			scrollbar_below_slider (ev->xbutton.y))
#endif
		      scr_move_to (scrollbar_position (ev->xbutton.y),
				   scrollbar_size ());
		    scrollbar_setMotion ();
		    break;

		  case Button1:
		    /*drop */

		  case Button3:
#ifndef XTERM_SCROLLBAR
		    if (scrollbar_above_slider (ev->xbutton.y))
		      scr_page (UP, TermWin.nrow / 4);
		    else if (scrollbar_below_slider (ev->xbutton.y))
		      scr_page (DN, TermWin.nrow / 4);
		    else
		      scrollbar_setMotion ();
#else /* XTERM_SCROLLBAR */
		    scr_page ((ev->xbutton.button == Button1 ? DN : UP),
			      (TermWin.nrow *
			       scrollbar_position (ev->xbutton.y) /
			       scrollbar_size ())
		      );
#endif /* XTERM_SCROLLBAR */
		    break;
		  }
	    }
	  return;
	}
      break;

    case ButtonRelease:
      reportmode = (bypass_keystate ?
		    0 : (PrivateModes & PrivMode_mouse_report));

      if (scrollbar_isUpDn ())
	{
	  scrollbar_setNone ();
	  scrollbar_show (0);
	}

      if (ev->xany.window == TermWin.vt)
	{
	  if (ev->xbutton.subwindow != None)
	    Gr_ButtonRelease (ev->xbutton.x, ev->xbutton.y);
	  else
	    {
	      if (reportmode)
		{
		  switch (reportmode & PrivMode_mouse_report)
		    {
		    case PrivMode_MouseX10:
		      break;

		    case PrivMode_MouseX11:
		      ev->xbutton.state = bypass_keystate;
		      ev->xbutton.button = AnyButton;
		      mouse_report (&(ev->xbutton));
		      break;
		    }
		  return;
		}

	      /*
	       * dumb hack to compensate for the failure of click-and-drag
	       * when overriding mouse reporting
	       */
	      if ((PrivateModes & PrivMode_mouse_report) &&
		  (bypass_keystate) &&
		  (ev->xbutton.button == Button1) &&
		  (clickOnce ()))
		selection_extend (ev->xbutton.x, ev->xbutton.y);

	      switch (ev->xbutton.button)
		{
		case Button1:
		case Button3:
		  selection_make (ev->xbutton.time);
		  break;

		case Button2:
		  selection_request (ev->xbutton.time,
				     ev->xbutton.x, ev->xbutton.y);
		  break;
		}
	    }
	}
      break;

    case MotionNotify:
      if ((PrivateModes & PrivMode_mouse_report) && !(bypass_keystate))
	break;

      if (ev->xany.window == TermWin.vt)
	{
	  if ((ev->xbutton.state & (Button1Mask | Button3Mask)) &&
	      clickOnce ())
	    {
	      Window unused_root, unused_child;
	      int unused_root_x, unused_root_y;
	      unsigned int unused_mask;

	      while (XCheckTypedWindowEvent (Xdisplay, TermWin.vt,
					     MotionNotify, ev));
	      XQueryPointer (Xdisplay, TermWin.vt,
			     &unused_root, &unused_child,
			     &unused_root_x, &unused_root_y,
			     &(ev->xbutton.x), &(ev->xbutton.y),
			     &unused_mask);
#ifdef MOUSE_THRESHOLD
	      /* deal with a `jumpy' mouse */
	      if ((ev->xmotion.time - buttonpress_time) > MOUSE_THRESHOLD)
#endif
		selection_extend ((ev->xbutton.x), (ev->xbutton.y));
	    }
	}
      else if ((ev->xany.window == scrollBar.win) && scrollbar_isMotion ())
	{
	  Window unused_root, unused_child;
	  int unused_root_x, unused_root_y;
	  unsigned int unused_mask;

	  while (XCheckTypedWindowEvent (Xdisplay, scrollBar.win,
					 MotionNotify, ev));
	  XQueryPointer (Xdisplay, scrollBar.win,
			 &unused_root, &unused_child,
			 &unused_root_x, &unused_root_y,
			 &(ev->xbutton.x), &(ev->xbutton.y),
			 &unused_mask);
	  scr_move_to (scrollbar_position (ev->xbutton.y),
		       scrollbar_size ());
	  scr_refresh (refresh_type);
	  refresh_count = refresh_limit = 0;
	  scrollbar_show (1);
	}
      break;
    }
}
/*}}} */

/*{{{ tt_write(), tt_printf() - output to command */
/*
 * Send count characters directly to the command
 */
void
tt_write (const unsigned char *buf, unsigned int count)
{
  while (count > 0)
    {
      int n = write (cmd_fd, buf, count);
      if (n > 0)
	{
	  count -= n;
	  buf += n;
	}
    }
}

/*
 * Send printf() formatted output to the command.
 * Only use for small ammounts of data.
 */
void
tt_printf (const unsigned char *fmt,...)
{
  static unsigned char buf[256];
  va_list arg_ptr;

  va_start (arg_ptr, fmt);
  vsprintf (buf, fmt, arg_ptr);
  va_end (arg_ptr);
  tt_write (buf, strlen (buf));
}
/*}}} */

/*{{{ print pipe */
/*----------------------------------------------------------------------*/
#ifdef PRINTPIPE
FILE *
popen_printer (void)
{
  FILE *stream = popen (rs_print_pipe, "w");
  if (stream == NULL)
    print_error ("can't open printer pipe");
  return stream;
}

int
pclose_printer (FILE * stream)
{
  fflush (stream);
  /* pclose() reported not to work on SunOS 4.1.3 */
#if defined (__sun__)
  /* pclose works provided SIGCHLD handler uses waitpid */
  return pclose (stream);	/* return fclose (stream); */
#else
  return pclose (stream);
#endif
}

/*
 * simulate attached vt100 printer
 */
static void
process_print_pipe (void)
{
  const char *const escape_seq = "\033[4i";
  const char *const rev_escape_seq = "i4[\033";
  int index;
  FILE *fd;

  if ((fd = popen_printer ()) != NULL)
    {
      for (index = 0; index < 4; /* nil */ )
	{
	  unsigned char ch = cmd_getc ();

	  if (ch == escape_seq[index])
	    index++;
	  else if (index)
	    for ( /*nil */ ; index > 0; index--)
	      fputc (rev_escape_seq[index - 1], fd);

	  if (index == 0)
	    fputc (ch, fd);
	}
      pclose_printer (fd);
    }
}
#endif /* PRINTPIPE */
/*}}} */

/*{{{ process escape sequences */
static void
process_escape_seq (void)
{
  unsigned char ch = cmd_getc ();

  switch (ch)
    {
      /* case 1:        do_tek_mode (); break; */
    case '#':
      if (cmd_getc () == '8')
	scr_E ();
      break;
    case '(':
      scr_charset_set (0, cmd_getc ());
      break;
    case ')':
      scr_charset_set (1, cmd_getc ());
      break;
    case '*':
      scr_charset_set (2, cmd_getc ());
      break;
    case '+':
      scr_charset_set (3, cmd_getc ());
      break;
#ifdef KANJI
    case '$':
      scr_charset_set (-2, cmd_getc ());
      break;
#endif
    case '7':
      scr_cursor (SAVE);
      break;
    case '8':
      scr_cursor (RESTORE);
      break;
    case '=':
    case '>':
      PrivMode ((ch == '='), PrivMode_aplKP);
      break;
    case '@':
      (void) cmd_getc ();
      break;
    case 'D':
      scr_index (UP);
      break;
    case 'E':
      scr_add_lines ("\n\r", 1, 2);
      break;
    case 'G':
      process_graphics ();
      break;
    case 'H':
      scr_set_tab (1);
      break;
    case 'M':
      scr_index (DN);
      break;
      /*case 'N': scr_single_shift (2);   break; */
      /*case 'O': scr_single_shift (3);   break; */
    case 'Z':
      tt_printf (ESCZ_ANSWER);
      break;			/* steal obsolete ESC [ c */
    case '[':
      process_csi_seq ();
      break;
    case ']':
      process_xterm_seq ();
      break;
    case 'c':
      scr_poweron ();
      break;
    case 'n':
      scr_charset_choose (2);
      break;
    case 'o':
      scr_charset_choose (3);
      break;
    }
}
/*}}} */

/*{{{ process CSI (code sequence introducer) sequences `ESC[' */
static void
process_csi_seq (void)
{
  unsigned char ch, priv;
  unsigned int nargs;
  int arg[ESC_ARGS];

  nargs = 0;
  arg[0] = 0;
  arg[1] = 0;

  priv = 0;
  ch = cmd_getc ();
  if (ch >= '<' && ch <= '?')
    {
      priv = ch;
      ch = cmd_getc ();
    }

  /* read any numerical arguments */
  do
    {
      int n;
      for (n = 0; isdigit (ch); ch = cmd_getc ())
	n = n * 10 + (ch - '0');

      if (nargs < ESC_ARGS)
	arg[nargs++] = n;
      if (ch == '\b')
	{
	  scr_backspace ();
	}
      else if (ch == 033)
	{
	  process_escape_seq ();
	  return;
	}
      else if (ch < ' ')
	{
	  scr_add_lines (&ch, 0, 1);
	  return;
	}
      if (ch < '@')
	ch = cmd_getc ();
    }
  while (ch >= ' ' && ch < '@');
  if (ch == 033)
    {
      process_escape_seq ();
      return;
    }
  else if (ch < ' ')
    return;

  switch (ch)
    {
#ifdef PRINTPIPE
    case 'i':			/* printing */
      switch (arg[0])
	{
	case 0:
	  scr_printscreen (0);
	  break;
	case 5:
	  process_print_pipe ();
	  break;
	}
      break;
#endif
    case 'A':
    case 'e':			/* up <n> */
      scr_gotorc ((arg[0] ? -arg[0] : -1), 0, RELATIVE);
      break;
    case 'B':			/* down <n> */
      scr_gotorc ((arg[0] ? +arg[0] : +1), 0, RELATIVE);
      break;
    case 'C':
    case 'a':			/* right <n> */
      scr_gotorc (0, (arg[0] ? +arg[0] : +1), RELATIVE);
      break;
    case 'D':			/* left <n> */
      scr_gotorc (0, (arg[0] ? -arg[0] : -1), RELATIVE);
      break;
    case 'E':			/* down <n> & to first column */
      scr_gotorc ((arg[0] ? +arg[0] : +1), 0, R_RELATIVE);
      break;
    case 'F':			/* up <n> & to first column */
      scr_gotorc ((arg[0] ? -arg[0] : -1), 0, R_RELATIVE);
      break;
    case 'G':
    case '`':			/* move to col <n> */
      scr_gotorc (0, (arg[0] ? +arg[0] : +1), R_RELATIVE);
      break;
    case 'd':			/* move to row <n> */
      scr_gotorc ((arg[0] ? +arg[0] : +1), 0, C_RELATIVE);
      break;
    case 'H':
    case 'f':			/* position cursor */
      switch (nargs)
	{
	case 0:
	  scr_gotorc (0, 0, 0);
	  break;
	case 1:
	  scr_gotorc ((arg[0] ? arg[0] - 1 : 0), 0, 0);
	  break;
	default:
	  scr_gotorc (arg[0] - 1, arg[1] - 1, 0);
	  break;
	}
      break;
    case 'I':
      scr_tab (arg[0] ? +arg[0] : +1);
      break;
    case 'Z':
      scr_tab (arg[0] ? -arg[0] : -1);
      break;
    case 'J':
      scr_erase_screen (arg[0]);
      break;
    case 'K':
      scr_erase_line (arg[0]);
      break;
    case '@':
      scr_insdel_chars ((arg[0] ? arg[0] : 1), INSERT);
      break;
    case 'L':
      scr_insdel_lines ((arg[0] ? arg[0] : 1), INSERT);
      break;
    case 'M':
      scr_insdel_lines ((arg[0] ? arg[0] : 1), DELETE);
      break;
    case 'X':
      scr_insdel_chars ((arg[0] ? arg[0] : 1), ERASE);
      break;
    case 'P':
      scr_insdel_chars ((arg[0] ? arg[0] : 1), DELETE);
      break;

    case 'c':
      tt_printf (VT100_ANS);
      break;
    case 'm':
      process_sgr_mode (nargs, arg);
      break;
    case 'n':			/* request for information */
      switch (arg[0])
	{
	case 5:
	  tt_printf ("\033[0n");
	  break;		/* ready */
	case 6:
	  scr_report_position ();
	  break;
#if defined (ENABLE_DISPLAY_ANSWER)
	case 7:
	  tt_printf ("%s\n", display_name);
	  break;
#endif
	case 8:
	  xterm_seq (XTerm_title, APL_NAME "-" VERSION);
	  break;
	}
      break;
    case 'r':			/* set top and bottom margins */
      if (priv != '?')
	{
	  if (nargs < 2 || arg[0] >= arg[1])
	    scr_scroll_region (0, 10000);
	  else
	    scr_scroll_region (arg[0] - 1, arg[1] - 1);
	  break;
	}
      /* drop */
    case 's':
    case 't':
    case 'h':
    case 'l':
      process_terminal_mode (ch, priv, nargs, arg);
      break;
    case 'g':
      switch (arg[0])
	{
	case 0:
	  scr_set_tab (0);
	  break;		/* delete tab */
	case 3:
	  scr_set_tab (-1);
	  break;		/* clear all tabs */
	}
      break;
    case 'W':
      switch (arg[0])
	{
	case 0:
	  scr_set_tab (1);
	  break;		/* = ESC H */
	case 2:
	  scr_set_tab (0);
	  break;		/* = ESC [ 0 g */
	case 5:
	  scr_set_tab (-1);
	  break;		/* = ESC [ 3 g */
	}
      break;
    }
}
/*}}} */

/*{{{ process xterm text parameters sequences `ESC ] Ps ; Pt BEL' */
static void
process_xterm_seq (void)
{
  unsigned char ch, string[STRING_MAX];
  int arg;

  ch = cmd_getc ();
  for (arg = 0; isdigit (ch); ch = cmd_getc ())
    arg = arg * 10 + (ch - '0');

  if (ch == ';')
    {
      int n = 0;
      while ((ch = cmd_getc ()) != 007)
	{
	  if ((n < sizeof (string) - 1))
	    {
	      /* silently translate whitespace to space char */
	      if (isspace (ch))
		ch = ' ';
	      if (ch >= ' ')
		string[n++] = ch;
	    }
	}
      string[n] = '\0';
      xterm_seq (arg, string);
    }
}
/*}}} */

/*{{{ process DEC private mode sequences `ESC [ ? Ps mode' */
/*
 * mode can only have the following values:
 *      'l' = low
 *      'h' = high
 *      's' = save
 *      'r' = restore
 *      't' = toggle
 * so no need for fancy checking
 */
static void
process_terminal_mode (int mode, int priv,
		       unsigned int nargs, int arg[])
{
  unsigned int i;
  int state;

  if (nargs == 0)
    return;

  /* make lo/hi boolean */
  switch (mode)
    {
    case 'l':
      mode = 0;
      break;
    case 'h':
      mode = 1;
      break;
    }

  switch (priv)
    {
    case 0:
      if (mode && mode != 1)
	return;			/* only do high/low */
      for (i = 0; i < nargs; i++)
	switch (arg[i])
	  {
	  case 4:
	    scr_insert_mode (mode);
	    break;

	  case 36:
	    PrivMode (mode, PrivMode_BackSpace);
	    break;
	    /* case 38:  TEK mode */
	  }
      break;

#define PrivCases(bit)	\
if (mode == 't') state = !(PrivateModes & bit); else state = mode;\
switch (state) {\
case 's': SavedModes |= (PrivateModes & bit); continue; break;\
case 'r': state = (SavedModes & bit) ? 1 : 0;/*drop*/\
default:  PrivMode (state, bit); }

    case '?':
      for (i = 0; i < nargs; i++)
	switch (arg[i])
	  {
	  case 1:		/* application cursor keys */
	    PrivCases (PrivMode_aplCUR);
	    break;

	    /* case 2:   - reset charsets to USASCII */

	  case 3:		/* 80/132 */
	    PrivCases (PrivMode_132);
	    if (PrivateModes & PrivMode_132OK)
	      set_width (state ? 132 : 80);
	    break;

	    /* case 4:   - smooth scrolling */

	  case 5:		/* reverse video */
	    PrivCases (PrivMode_rVideo);
	    scr_rvideo_mode (state);
	    break;

	  case 6:		/* relative/absolute origins  */
	    PrivCases (PrivMode_relOrigin);
	    scr_relative_origin (state);
	    break;

	  case 7:		/* autowrap */
	    PrivCases (PrivMode_Autowrap);
	    scr_autowrap (state);
	    break;

	    /* case 8:   - auto repeat, can't do on a per window basis */

	  case 9:		/* X10 mouse reporting */
	    PrivCases (PrivMode_MouseX10);
	    /* orthogonal */
	    if (PrivateModes & PrivMode_MouseX10)
	      PrivateModes &= ~(PrivMode_MouseX11);
	    break;

#ifdef scrollBar_esc
	  case scrollBar_esc:
	    PrivCases (PrivMode_scrollBar);
	    map_scrollBar (state);
	    break;
#endif

	  case 25:		/* visible/invisible cursor */
	    PrivCases (PrivMode_VisibleCursor);
	    scr_cursor_visible (state);
	    break;

	  case 35:
	    PrivCases (PrivMode_ShiftKeys);
	    break;

	  case 36:
	    PrivCases (PrivMode_BackSpace);
	    break;

	  case 40:		/* 80 <--> 132 mode */
	    PrivCases (PrivMode_132OK);
	    break;

	  case 47:		/* secondary screen */
	    PrivCases (PrivMode_Screen);
	    scr_change_screen (state);
	    break;

	  case 66:		/* application key pad */
	    PrivCases (PrivMode_aplKP);
	    break;

	  case 1000:		/* X11 mouse reporting */
	    PrivCases (PrivMode_MouseX11);
	    /* orthogonal */
	    if (PrivateModes & PrivMode_MouseX11)
	      PrivateModes &= ~(PrivMode_MouseX10);
	    break;

#if 0
	  case 1001:
	    break;		/* X11 mouse highlighting */
#endif
	  }
#undef PrivCases
      break;
    }
}
/*}}} */

/*{{{ process sgr sequences */
static void
process_sgr_mode (unsigned int nargs, int arg[])
{
  unsigned int i;

  if (nargs == 0)
    {
      scr_rendition (0, ~RS_None);
      return;
    }
  for (i = 0; i < nargs; i++)
    switch (arg[i])
      {
      case 0:
	scr_rendition (0, ~RS_None);
	break;
      case 1:
	scr_rendition (1, RS_Bold);
	break;
      case 4:
	scr_rendition (1, RS_Uline);
	break;
      case 5:
	scr_rendition (1, RS_Blink);
	break;
      case 7:
	scr_rendition (1, RS_RVid);
	break;
      case 22:
	scr_rendition (0, RS_Bold);
	break;
      case 24:
	scr_rendition (0, RS_Uline);
	break;
      case 25:
	scr_rendition (0, RS_Blink);
	break;
      case 27:
	scr_rendition (0, RS_RVid);
	break;

      case 30:
      case 31:			/* set fg color */
      case 32:
      case 33:
      case 34:
      case 35:
      case 36:
      case 37:
	scr_color (minColor + (arg[i] - 30), RS_Bold);
	break;
      case 39:			/* default fg */
	scr_color (restoreFG, RS_Bold);
	break;

      case 40:
      case 41:			/* set bg color */
      case 42:
      case 43:
      case 44:
      case 45:
      case 46:
      case 47:
	scr_color (minColor + (arg[i] - 40), RS_Blink);
	break;
      case 49:			/* default bg */
	scr_color (restoreBG, RS_Blink);
	break;
      }
}
/*}}} */

/*{{{ process Rob Nation's own graphics mode sequences */
static void
process_graphics (void)
{
  unsigned char ch, cmd = cmd_getc ();
#ifndef RXVT_GRAPHICS
  if (cmd == 'Q')		/* query graphics */
    {
      tt_printf ("\033G0\n");	/* no graphics */
      return;
    }
  /* swallow other graphics sequences until terminating ':' */
  do
    ch = cmd_getc ();
  while (ch != ':');
#else
  int nargs;
  int args[NGRX_PTS];
  unsigned char *text = NULL;

  if (cmd == 'Q')		/* query graphics */
    {
      tt_printf ("\033G1\n");	/* yes, graphics (color) */
      return;
    }

  for (nargs = 0; nargs < (sizeof (args) / sizeof (args[0])) - 1; /*nil */ )
    {
      int neg;

      ch = cmd_getc ();
      neg = (ch == '-');
      if (neg || ch == '+')
	ch = cmd_getc ();

      for (args[nargs] = 0; isdigit (ch); ch = cmd_getc ())
	args[nargs] = args[nargs] * 10 + (ch - '0');
      if (neg)
	args[nargs] = -args[nargs];

      nargs++;
      args[nargs] = 0;
      if (ch != ';')
	break;
    }

  if ((cmd == 'T') && (nargs >= 5))
    {
      int i, len = args[4];

      text = MALLOC ((len + 1) * sizeof (char), "text");
      if (text != NULL)
	{
	  for (i = 0; i < len; i++)
	    text[i] = cmd_getc ();
	  text[len] = '\0';
	}
    }

  Gr_do_graphics (cmd, nargs, args, text);
#endif
}
/*}}} */

/*{{{ Read and process output from the application */
void
main_loop (void)
{
  int ch;

  do
    {
      while ((ch = cmd_getc ()) == 0);	/* wait for something */
      if (ch >= ' ' || ch == '\t' || ch == '\n' || ch == '\r')
	{
	  /* Read a text string from the input buffer */
	  int nlines = 0;
	  unsigned char *str;

	  /*
	   * point to the start of the string,
	   * decrement first since already did get_com_char ()
	   */
	  str = --cmdbuf_ptr;
	  while (cmdbuf_ptr < cmdbuf_endp)
	    {
	      ch = *cmdbuf_ptr;
	      if (ch >= ' ' || ch == '\t' || ch == '\n' || ch == '\r')
		{
		  cmdbuf_ptr++;
		  if (ch == '\n')
		    {
		      nlines++;
		      refresh_count++;

		      if (refresh_count > (refresh_limit * TermWin.nrow))
			break;
		    }
		}
	      else
		/* unprintable */
		{
		  break;
		}
	    }
	  scr_add_lines (str, nlines, (cmdbuf_ptr - str));
	}
      else
	{
	  switch (ch)
	    {
	    case 005:
	      tt_printf (VT100_ANS);
	      break;		/* terminal Status */
	    case 007:
	      scr_bell ();
	      break;		/* bell */
	    case '\b':
	      scr_backspace ();
	      break;		/* backspace */
	    case 013:
	    case 014:
	      scr_index (UP);
	      break;		/* vertical tab, form feed */
	    case 016:
	      scr_charset_choose (1);
	      break;		/* shift out - acs */
	    case 017:
	      scr_charset_choose (0);
	      break;		/* shift in - acs */
	    case 033:
	      process_escape_seq ();
	      break;
	    }
	}
    }
  while (ch != EOF);
}
/*}}} */
/*----------------------- end-of-file (C source) -----------------------*/
