/*
   ami_command.c (replaces command.c for AmigaOS)

   (c) 1996 by Bernhard Fastenrath (fasten@cs.bonn.edu)

   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).
*/
#include "rxvt.h"

#if 0
#define DEBUG_CMD
#endif

#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/keysym.h>
#include <X11/xtrans.h>

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <grp.h>
#include <string.h>
#include <time.h>
#include <sys/time.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <stdlib.h>

#include "command.h"
#include "debug.h"
#include "graphics.h"
#include "grkelot.h"
#include "sbar.h"
#include "screen.h"
#include "xsetup.h"

/* timeout for select() */
#define TIMEOUT_USEC	5000

/* 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! */
#ifndef KS_DELETE
# define KS_DELETE	"\033[3~"	/* Delete = Execute */
#endif
#ifdef NO_DELETE_KEY
# undef KS_DELETE			/* use X server definition */
#endif

/*----------------------------------------------------------------------*
 * extern functions referenced
 */
/* ami_pty.c */
extern int StartAmigaShell (void);
extern int PtyIO (char *cmdbuf_base, char **cmdbuf_endp, int timeout_flag);
extern int PtyWrite (int pseudofd, char *buf, int count);
extern long PtyMask (void);
extern void PtyKeyboardSignal (char s);


/*----------------------------------------------------------------------*
 * extern variables referenced
 */
/*----------------------------------------------------------------------*
 * extern variables declared here
 */
#ifdef KEYSYM_RESOURCE
extern const unsigned char * KeySym_map [256];	/* in xdefaults.c */
#endif

/* ... from screen.c */
/*extern WindowInfo MyWinInfo;*/
/* ... from graphics.c */
extern Display *display;
/* ... from Xsetup.c */
extern Window main_win;


/*----------------------------------------------------------------------*
 * local variables
 */
static int cmd_fd = -1;		/* file descriptor connected to the command */
static int Xfd;			/* file descriptor of X server connection */
static int num_fds;		/* number of file descriptors being used */
#ifndef AMIGA
static char *ptydev = NULL, *ttydev = NULL;	/* pty/tty name */
static pid_t cmd_pid = (pid_t) -1;	/* process id if child */
static struct stat ttyfd_stat;	/* original status of the tty we will use */
#endif
#ifdef META8_OPTION
static unsigned char meta_char = 033;	/* Alt-key prefix */
#endif

/* DEC private modes */
#define PrivMode_132		(1L<<0)
#define PrivMode_132Ok		(1L<<1)
#define PrivMode_rvideo		(1L<<2)
#define PrivMode_relorigin	(1L<<3)
#define PrivMode_screen		(1L<<4)
#define PrivMode_autowrap	(1L<<5)
#define PrivMode_aplcur		(1L<<6)
#define PrivMode_aplkp		(1L<<7)
#define PrivMode_backspace	(1L<<8)
#define PrivMode_viscursor	(1L<<9)
/* too annoying to implement X11 highlight tracking */
#define PrivMode_mouse_X10	(1L<<10)
#define PrivMode_mouse_X11	(1L<<11)
#define PrivMode_mouse_report	(PrivMode_mouse_X10|PrivMode_mouse_X11)

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

static unsigned long PrivateModes = (PrivMode_132Ok | PrivMode_autowrap
				     | PrivMode_aplkp | PrivMode_viscursor);

static unsigned long SavedModes = (PrivMode_132Ok | PrivMode_autowrap
				   | PrivMode_aplkp | PrivMode_viscursor);

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

static Atom wm_del_win;

/* command input buffering */
static unsigned char cmdbuf_base [CMDBUF_SIZE], * cmdbuf_ptr, * cmdbuf_endp;


/* AmigaOS/AmiWin specific */

struct timeval {
    long tv_sec;
    long tv_usec;
};

static struct SelectData {
  long mask;
  XTransFdset readfds;
  struct timeval tv;
} Sdm, Sdc;

static XtransConnInfo xtr_ci;
static int xtr_fd;

/*----------------------------------------------------------------------*
 * local functions referenced
 */
#ifdef AMIGA
static void pty_echo (const unsigned char *buf, int count);
#else
static void	privileges (int mode);
static RETSIGTYPE	catch_child (int);
static RETSIGTYPE	catch_sig (int);
static int	get_pty (void);
static int	get_tty (void);
#endif
static int	run_command (char * argv[]);
static void	lookup_key (XEvent *);
static unsigned char get_com_char (void);
static void	get_X_event (void);
/*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 (unsigned char mode, unsigned char priv,
				       int nargs, int arg[]);
static void	process_sgr_mode (int nargs, int arg[]);
static void	process_rxvt_graphics (void);

static void	stty_size (int fd, int width, int height);

/*----------------------------------------------------------------------*/
/*
 * 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 */
#ifdef ESCZ_RXVT_ANSWER		/* what to do with obsolete ESC-Z */
# define RXVT_ANS	"\033[?1;2C"	/* almost the same */
#else
# define RXVT_ANS	VT100_ANS	/* obsolete ANSI ESC[c */
#endif

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

/* 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

/*----------------------------------------------------------------------*
 *
 *----------------------------------------------------------------------*/

#ifndef _POSIX_VERSION
static int
getdtablesize (void)
{
  return 42; /* Doesn't mean anything in the Amiga version */
}
#endif

/*
 * Exit gracefully, clearing the utmp entry and restoring tty attributes
 *
 * AmigaOS:
 *   rxvt is the console handler; the device node is removed
 *   from an atexit () callback. clean_exit() is a NOP for now.
 */
void
clean_exit (void)
{
}

/* 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.
 *
 * AmigaOS:
 *   call StartAmigaShell() in ami_pty.c (which adds a PTY<n>: handler
 *   to the dos device list). Messages received by this pseudo device are
 *   dealt with in PtyIO() (in ami_pty.c).
 */
static int
run_command (char *argv[])
{
  int ptyfd;

  stty_size (0, TermWin.ncol, TermWin.nrow);

  ptyfd = StartAmigaShell (); /* argv[] is ignored */

  return ptyfd;
}

/*
 * Initialize the command connection.
 * This should be called after the X server connection is established.
 */
void
init_command (char * argv[])
{
   /* Enable delete window protocol */
   wm_del_win = XInternAtom (Xdisplay, "WM_DELETE_WINDOW", False);
   XSetWMProtocols (Xdisplay, TermWin.parent, &wm_del_win, 1);

   /* 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);
   }

   /* AmigaOS/AmiWin specific initialization */

   xtr_ci = XTransGetConnInfo (Xfd, XTA_USER);
   xtr_fd = XTransGetConnectionNumber (xtr_ci, XTA_USER);
   num_fds = xtr_fd + 1;
   Sdm.mask = PtyMask ();
   Sdm.tv.tv_usec = TIMEOUT_USEC;
   Sdm.tv.tv_sec = 0;
   XTransFdZero (&Sdm.readfds);
   XTransFdSet (xtr_fd, &Sdm.readfds);
}

/* Tell the teletype handler what size the window is.
 * Called after a window size change.
 *
 * AmigaOS: NOP
 */
static void
stty_size (int fd, int width, int height)
{
#if 0
  struct winsize wsize;
  
  if (fd < 0) return;

  wsize.ws_col = (unsigned short) width;
  wsize.ws_row = (unsigned short) height;
  ioctl (fd, TIOCSWINSZ, (unsigned char *) &wsize);
#endif
}

void
tty_resize (void)
{
   stty_size (cmd_fd, TermWin.ncol, TermWin.nrow);
}

#ifdef DEBUG_CMD
static void
print_keystring (unsigned keysym, char *str, int len)
{
   int i;

   /* Display the keyboard buffer contents -- rgg 06/12/95 */
   fprintf (stderr, "key 0x%04X [%d]: `", keysym, len);
   for (i = 0; i < len; i++)
     {
	if (str [i] < ' ' || str [i] >= '\177')
	  fprintf (stderr, "\\%03o", str[i]);
	else
	  fprintf (stderr, "%c", str[i]);
     }
   fprintf (stderr, "'\n");
}
#endif /* DEBUG_CMD */

/*
 * Convert the keypress event into a string
 *
 * AmigaOS: CTRL-C/D/E/F handling added
 */
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 int greek_mode = 0;
#endif
   static XComposeStatus compose = {NULL, 0};
   static unsigned char kbuf [KBUFSZ];
   int len, shift, ctrl, meta;
   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
    */
   shift= (ev->xkey.state & ShiftMask);
   ctrl = (ev->xkey.state & ControlMask);
   meta = (ev->xkey.state & Mod1Mask);
   len  = (ev->xkey.state & Mod5Mask);	/* tmp value */
   if (numlock_state || len)
     {
	numlock_state = len;	/* numlock toggle */
	PrivMode ((!numlock_state), PrivMode_aplkp);
     }

   len = XLookupString (&ev->xkey, kbuf, sizeof(kbuf), &keysym, &compose);

   /* AmigaOS CTRL-C/D/E/F handling */
   if (ctrl)
   {
     switch (toupper ((char) keysym))
     {
       case 'C':
       case 'D':
       case 'E':
       case 'F':
	 PtyKeyboardSignal ((char) keysym);
	 return;
     }
   }

#ifndef HOTKEY

# define HOTKEY	meta
#endif
   if (HOTKEY) {
      if (keysym == ks_pageup)
	{ scr_page (UP,   TermWin.nrow * 4/5); return; }
      else if (keysym == ks_pagedown)
	{ scr_page (DOWN, TermWin.nrow * 4/5); return; }
      else if (keysym == ks_bigfont)	{ new_font (UP); return; }
      else if (keysym == ks_smallfont)	{ new_font (DOWN); return; }
#ifdef MAPALERT_OPTION
      else if (keysym == ks_alert)	{ Options ^= Opt_mapAlert; return; }
#endif
   }

#ifdef GREEK_SUPPORT
   if (keysym == XK_Mode_switch)
     {
	greek_mode = !greek_mode;
	/* no consistent way to set/restore the title since it could have
	 * been set via an XTerm escape sequence, so don't try
	 */
	if (greek_mode) {
	   xterm_seq (NEW_TITLE_NAME,
		      (greek_getmode () == GREEK_ELOT928 ?
		       "[Greek: iso]" : "[Greek: ibm]"));
	   greek_reset ();
	} else {
	   xterm_seq (NEW_TITLE_NAME, APL_NAME "-" VERSION);
	}
	return;
     }
#endif
#ifdef DEBUG_SELECTION
   if (keysym == XK_Print) { debug_selection (); return; }
#endif
#ifdef PRINTPIPE
   if (keysym == ks_printscreen) { scr_printscreen (ctrl|shift); return; }
#endif

   if (keysym >= 0xFF00 && keysym <= 0xFFFF) {
      /* Shift + F1 - F10 generates F11 - F20 */
      if (shift && keysym >= XK_F1 && keysym <= XK_F10)
	{
	   shift = 0;		/* turn off */
	   keysym += (XK_F11 - XK_F1);
	}

#ifdef KEYSYM_RESOURCE
      if (KeySym_map [keysym - 0xFF00] != NULL)
	{
	   const unsigned char *kbuf;
	   kbuf = KeySym_map [keysym - 0xFF00];

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

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

	 case XK_Up:
	   len = 3; strcpy(kbuf, "\033[A");
	   if (PrivateModes & PrivMode_aplcur) kbuf [1] = 'O';
#ifdef CURSOR_SHIFT
	   else if (shift) kbuf [2] = 'a';
#endif
#ifdef CURSOR_CTRL
	   else if (ctrl) kbuf [2] = '\001';
#endif
	   break;
	 case XK_Down:
	   len = 3; strcpy(kbuf, "\033[B");
	   if (PrivateModes & PrivMode_aplcur) kbuf [1] = 'O';
#ifdef CURSOR_SHIFT
	   else if (shift) kbuf [2] = 'b';
#endif
#ifdef CURSOR_CTRL
	   else if (ctrl) kbuf [2] = '\002';
#endif
	   break;
	 case XK_Right:
	   len = 3; strcpy(kbuf, "\033[C");
	   if (PrivateModes & PrivMode_aplcur) kbuf [1] = 'O';
#ifdef CURSOR_SHIFT
	   else if (shift) kbuf [2] = 'c';
#endif
#ifdef CURSOR_CTRL
	   else if (ctrl) kbuf [2] = '\003';
#endif
	   break;
	 case XK_Left:
	   len = 3; strcpy(kbuf, "\033[D");
	   if (PrivateModes & PrivMode_aplcur) kbuf [1] = 'O';
#ifdef CURSOR_SHIFT
	   else if (shift) kbuf [2] = 'd';
#endif
#ifdef CURSOR_CTRL
	   else if (ctrl) kbuf [2] = '\004';
#endif
	   break;

	 case XK_Find:		len = 4; strcpy (kbuf, "\033[1~");	break;
	 case XK_Insert:	len = 4; strcpy (kbuf, "\033[2~");	break;
	 case XK_Execute:	len = 4; strcpy (kbuf, "\033[3~");	break;
	 case XK_Select:	len = 4; strcpy (kbuf, "\033[4~");	break;
	 case XK_Prior:		len = 4; strcpy (kbuf, "\033[5~");	break;
	 case XK_Next:		len = 4; strcpy (kbuf, "\033[6~");	break;
#ifdef KS_DELETE
	 case XK_Delete:	len = strlen(strcpy(kbuf,KS_DELETE));	break;
#endif
	 case XK_Home:		len = strlen(strcpy(kbuf,KS_HOME));	break;
	 case XK_End:		len = strlen(strcpy(kbuf,KS_END));	break;

	 case XK_KP_Enter:
	   /* allow shift to override */
	   if ((PrivateModes & PrivMode_aplkp) ? !shift : shift)
	     {
		len = 3; strcpy(kbuf, "\033OM");
	     }
	   else
	     {
		len = 1; kbuf[0] = '\r';
	     }
	   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 */
	   if ((PrivateModes & PrivMode_aplkp) ? !shift : shift)
	     {
		len = 3; strcpy(kbuf, "\033Oj");
		kbuf[2] += (keysym - XK_KP_Multiply);
	     }
	   else
	     {
		len = 1; kbuf[0] = ('*' + (keysym - XK_KP_Multiply));
	     }
	   break;

#define FKEY(n,fkey) len = 5;\
sprintf(kbuf, "\033[%2d~", (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_Help:
	 case XK_F15:	len = 5; strcpy(kbuf,"\033[28~");	break;
	 case XK_Menu:
	 case XK_F16:	len = 5; strcpy(kbuf,"\033[29~");	break;
	 case XK_F17:		/* "\033[31~" */
	 case XK_F18:		/* "\033[32~" */
	 case XK_F19:		/* "\033[33~" */
	 case XK_F20:		/* "\033[34~" */
	   FKEY (31, XK_F17);	break;
#undef FKEY
	}
   }
   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 *c;
	   for (c = kbuf; c < kbuf + len; c++)
	     *c |= 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 `~'
    */
   if (len > 3 && kbuf [len-1] == '~')
     {
	if (shift)
	  kbuf [len-1] = '$';
	else if (ctrl)
	  kbuf [len-1] = '^';
     }

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

#ifdef DEBUG_CMD
   if (debug_key)
     print_keystring (keysym, kbuf, len);
#endif

   tty_write (kbuf, len);
   pty_echo (kbuf, len);
   return;
}

/* AmigaOS-only: echos keyboard input back to the pty.
 *   (called by get_com_char()->get_X_event()->lookup_key())
 *
 */
static void
pty_echo (const unsigned char *buf, int count)
{
  char *cptr;

  if (cmdbuf_endp + count - cmdbuf_base > CMDBUF_SIZE)
  {
    XBell (Xdisplay, 50);
    return;
  }
  bcopy (buf, cmdbuf_endp, count);
  cmdbuf_endp += count;  

  cptr = cmdbuf_endp - 1;
  if (*cptr == 11 || *cptr == 13 || *cptr == 10 || *cptr == 0)
    *cptr = '\n';
}

/*
 * Return the next input character after first passing any keyboard input
 * to the command.
 *
 * AmigaOS: PtyIO() in ami_pty.c reads incoming messages.
 */
static unsigned char
get_com_char (void)
{
   static int refreshed = 0;
   int retval;
#ifndef AMIGA
   fd_set readfds;
   struct itimerval value;
#endif

   /* 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);
     }

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

   while (1)
     {
	/* process any X events that are pending */
	while (XPending (Xdisplay))
	  {
	     refreshed = 0;
	     get_X_event ();

	     /* Amiga: lookup_key() (called by get_X_event())
		       echos keyboard input to the pty.
	               That's why we have to check cmdbuf_ptr again.
             */
             if (cmdbuf_ptr < cmdbuf_endp)
	       goto Return_Char;
	  }

#ifndef AMIGA
	/* 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))
	  {
	     int count = sizeof(cmdbuf_base);
	     cmdbuf_ptr = cmdbuf_endp = cmdbuf_base;

	     while (count > sizeof(cmdbuf_base) / 2)
	       {
		  int n = read (cmd_fd, cmdbuf_endp, count);
		  if (n <= 0)
		    break;
		  cmdbuf_endp += n;
		  count -= n;
	       }
	     if (cmdbuf_ptr < cmdbuf_endp)
	       goto Return_Char;
	  }
#else
	bcopy (&Sdm, &Sdc, sizeof (struct SelectData));

	retval = XTransWaitSelect (num_fds, &Sdc.readfds, NULL, NULL,
			(refreshed ? NULL : &Sdc.tv), &Sdc.mask);

	if (1) // (Sdc.mask & Sdm.mask)
	{
	  cmdbuf_ptr = cmdbuf_endp = cmdbuf_base;

	  PtyIO (cmdbuf_base, &cmdbuf_endp, refreshed);

          if (cmdbuf_ptr < cmdbuf_endp)
	    goto Return_Char;
	}
#endif
	/* select statement timed out - better update the screen */
	if (retval == 0)
	  {
	     refresh_count = 0;
	     refresh_limit = 1;
	     if (!refreshed)
	       scr_refresh (refresh_type);
	     refreshed = 1;
#ifndef NO_SCROLLBAR
	     sbar_show (1);
#endif	/* NO_SCROLLBAR */
	  }
     }
   return 0;

Return_Char:

   refreshed = 0;
   return (*cmdbuf_ptr++);
}

/*
 * Receive and process an X event
 *
 * AmigaOS: unmodified
 */
static void
get_X_event (void)
{
   XEvent ev;
#ifndef NO_MULTIPLE_CLICK
   static Time buttonpress_time;
#endif
   static int clicks = 0;

#ifndef NO_MOUSE_REPORT
   static int bypass_keystate = 0;
   int reportmode;
#endif

#ifdef USE_SIZE_SET
   /* this doesn't seem to be needed, but leave for now */
   static int size_set = 0;	/* flag that window size has been set */
#endif

   XNextEvent (Xdisplay, &ev);
   switch (ev.type) {
    case KeyPress:
      lookup_key (&ev);
      break;

    case ClientMessage:
      if (ev.xclient.format == 32 && ev.xclient.data.l[0] == wm_del_win)
	exit (EXIT_SUCCESS);
      break;

    case MappingNotify:
      XRefreshKeyboardMapping (&ev.xmapping);
      break;

    case GraphicsExpose:
    case Expose:
#ifdef USE_SIZE_SET
      if (!size_set)
	{
	   /*
	    * Force resize if an exposure event arrives before the first
	    * resize event
	    */
	   resize_window ();
	   size_set = 1;
	}
#endif
      if (ev.xany.window == TermWin.vt)
	{
	   scr_touch (ev.xexpose.x, ev.xexpose.y,
		      ev.xexpose.width, ev.xexpose.height);
	}
      else
	{
	   XEvent dummy;

	   while (XCheckTypedWindowEvent (Xdisplay, ev.xany.window,
					  Expose, &dummy));
	   while (XCheckTypedWindowEvent (Xdisplay, ev.xany.window,
					  GraphicsExpose, &dummy));
#ifndef NO_SCROLLBAR
	   if ((Options & Opt_scrollBar) && (ev.xany.window == sbar.sb))
	     {
		sbar_setNone ();
		sbar_show (0);
	     }
	   else
#endif	/* NO_SCROLLBAR */
	     Gr_expose (ev.xany.window);
	}
      break;

    case VisibilityNotify:
      /* 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!
       */
      switch (ev.xvisibility.state) {
       case VisibilityUnobscured:
	 refresh_type = FAST_REFRESH;
	 /* scr_touch (0, 0, TermWin.width, TermWin.height);*/
	 break;
       case VisibilityPartiallyObscured:
	 refresh_type = SLOW_REFRESH;
	 /* scr_touch (0, 0, TermWin.width, TermWin.height);*/
	 break;
       default:
	 refresh_type = NO_REFRESH;
	 break;
      }
      break;

    case FocusIn:	TermWin.focus = 1;	break;
    case FocusOut:	TermWin.focus = 0;	break;
    case ConfigureNotify:
      resize_window ();
#ifdef USE_SIZE_SET
      size_set = 1;
#endif
      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 ButtonPress:
#ifndef NO_MOUSE_REPORT
      bypass_keystate = (ev.xbutton.state & (Mod1Mask|ShiftMask));
      reportmode = (bypass_keystate ?
		    0 : (PrivateModes & PrivMode_mouse_report));
#endif
      if (ev.xany.window == TermWin.vt)
	{
	   if (ev.xbutton.subwindow != None)
	     Gr_ButtonPress (ev.xbutton.x, ev.xbutton.y);
	   else
	     {
#ifndef NO_MOUSE_REPORT
		if (reportmode)
		  {
		     clicks = 0;
		     if (reportmode & PrivMode_mouse_X10)
		       ev.xbutton.state = 0;	/* no state info allowed */
		     mouse_report (&(ev.xbutton));
		  }
		else
#endif	/* NO_MOUSE_REPORT */
		  switch (ev.xbutton.button) {
		   case Button1:
#ifndef NO_MULTIPLE_CLICK
		     if (ev.xbutton.time - buttonpress_time < MULTICLICK_TIME)
		       clicks++;
		     else
		       clicks = 1;
		     buttonpress_time = ev.xbutton.time;
#endif	/* NO_MULTIPLE_CLICK */
		     selection_click (clicks, ev.xbutton.x, ev.xbutton.y);
		     break;

		   case Button3:
		     selection_extend (1, ev.xbutton.x, ev.xbutton.y);
		     break;
		  }
		return;
	     }
	}
#ifndef NO_SCROLLBAR
      if ((Options & Opt_scrollBar) && (ev.xany.window == sbar.sb))
	{
	   sbar_setNone ();
	   /*
	    * XTerm-style scrollbar:
	    * Move display proportional to pointer location
	    * pointer near top -> scroll one line
	    * pointer near bot -> scroll full page
	    *
	    * Rxvt-style scrollbar:
	    * move up if mouse if above sbar.top
	    * move dn if mouse if above sbar.bot
	    *
	    * Mouse report disabled scrollbar:
	    * arrow buttons - send up/down
	    * click on scrollbar - send pageup/down
	    */
	   if (ev.xbutton.y < sbar.beg)
	     {
# ifndef NO_MOUSE_REPORT_SCROLLBAR
		if (reportmode) { tty_printf ("\033[A"); break; }
# endif	/* NO_MOUSE_REPORT_SCROLLBAR */
		/* I would like continuous scrolling */
		if (scr_page (UP, 1)) sbar_setUp ();
	     }
	   else if (ev.xbutton.y > sbar.end)
	     {
# ifndef NO_MOUSE_REPORT_SCROLLBAR
		if (reportmode) { tty_printf ("\033[B"); break; }
# endif	/* NO_MOUSE_REPORT_SCROLLBAR */
		if (scr_page (DOWN, 1)) sbar_setDn ();
	     }
	   else
	     switch (ev.xbutton.button) {
	      case Button2:
# ifndef NO_MOUSE_REPORT_SCROLLBAR
		if (reportmode) { tty_printf ("\014"); break; }
# endif	/* NO_MOUSE_REPORT_SCROLLBAR */
# ifndef XTERM_SCROLLBAR
		if ((Options & Opt_sbArrows) &&
		    ((ev.xbutton.y < sbar.top) || (ev.xbutton.y > sbar.bot)))
# endif
		  scr_move_to ((ev.xbutton.y - sbar.beg),
			       (sbar.end - sbar.beg));
		sbar_setMotion ();
		break;

	      case Button1:
# ifndef NO_MOUSE_REPORT_SCROLLBAR
		if (reportmode) { tty_printf ("\033[6~"); break; }
# endif	/* NO_MOUSE_REPORT_SCROLLBAR */
		/*drop*/

	      case Button3:
# ifndef NO_MOUSE_REPORT_SCROLLBAR
		if (reportmode) { tty_printf ("\033[5~"); break; }
# endif	/* NO_MOUSE_REPORT_SCROLLBAR */
# ifndef XTERM_SCROLLBAR
		if (Options & Opt_sbArrows) /* scroll up/down 1/4 page */
		  {
		     if (ev.xbutton.y < sbar.top)
		       scr_page (UP, TermWin.nrow / 4);
		     else if (ev.xbutton.y > sbar.bot)
		       scr_page (DOWN, TermWin.nrow / 4);
		     else
		       sbar_setMotion ();
		  }
		else
# endif	/* XTERM_SCROLLBAR */
		  scr_page ((ev.xbutton.button == Button1 ? DOWN : UP),
			    (TermWin.nrow *
			     (ev.xbutton.y - sbar.beg) /
			     (sbar.end - sbar.beg))
			    );
		break;
	     }
	}
#endif	/* NO_SCROLLBAR */
      break;

    case ButtonRelease:
#ifndef NO_MOUSE_REPORT
      reportmode = (bypass_keystate ?
		    0 : (PrivateModes & PrivMode_mouse_report));
#endif
#ifndef NO_SCROLLBAR
      if (sbar_isUpDn ())
	{
	   sbar_setNone ();
	   sbar_show (0);
	}
#endif	/* NO_SCROLLBAR */

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

		      case PrivMode_mouse_X11:
			clicks = 0;
			ev.xbutton.state = bypass_keystate;
			ev.xbutton.button = AnyButton;
			mouse_report (&(ev.xbutton));
			break;
		     }
		     return;
		  }
#endif	/* NO_MOUSE_REPORT */

#ifndef NO_MOUSE_REPORT
		/*
		 * 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)
# ifndef NO_MULTIPLE_CLICK
		    && (clicks <= 1)
# endif
		    )
		  selection_extend (1, ev.xbutton.x, ev.xbutton.y);
#endif	/* NO_MOUSE_REPORT */

		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:
#ifndef NO_MOUSE_REPORT
      if ((PrivateModes & PrivMode_mouse_report) && !(bypass_keystate))
	return;
#endif
      if (ev.xany.window == TermWin.vt)
	{
	   if ((ev.xbutton.state & (Button1Mask|Button3Mask))
#ifndef NO_MULTIPLE_CLICK
	       && (clicks <= 1)
#endif
	       )
	     {
		Window root, child;
		int x, y, root_x, root_y;
		unsigned int mask;

		while (XCheckTypedWindowEvent (Xdisplay, TermWin.vt,
					       MotionNotify, &ev));
		XQueryPointer (Xdisplay, TermWin.vt, &root, &child,
			       &root_x, &root_y, &x, &y, &mask);
#ifdef MOUSE_THRESHOLD
		/* deal with a `jumpy' mouse */
		if ((ev.xmotion.time - buttonpress_time) > MOUSE_THRESHOLD)
#endif
		  selection_extend (1, x, y);
	     }
	}
#ifndef NO_SCROLLBAR
      else if ((ev.xany.window == sbar.sb) && sbar_isMotion ())
	{
	   Window root, child;
	   int x, y, root_x, root_y;
	   unsigned int mask;

	   while (XCheckTypedWindowEvent (Xdisplay, sbar.sb,
					  MotionNotify, &ev));
	   XQueryPointer (Xdisplay, sbar.sb, &root, &child,
			  &root_x, &root_y, &x, &y, &mask);
	   scr_move_to ((y - sbar.beg), (sbar.end - sbar.beg));
	   scr_refresh (refresh_type);
	   refresh_count = refresh_limit = 0;
	   sbar_show (1);
	}
#endif	/* NO_SCROLLBAR */
      break;
   }
}

/*
 * Send count characters directly to the command.
 */
void
tty_write (const unsigned char *buf,int count)
{
  int n;

  while (count > 0)
  {
    /* n = write (cmd_fd, buf, count); */
    n = PtyWrite (cmd_fd, buf, count);
  
    if (n > 0)
    {
      count -= n;
      buf += n;
    }
  }
}

/*
 * Send printf() formatted output to the command.
 * Only used for small ammounts of data.
 */
void
tty_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);
   DBUG("tty_printf\n");
   tty_write (buf, strlen (buf));
   DBUG("tty_printf ... done\n");
}

/*----------------------------------------------------------------------*/
#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__)
   return fclose (stream);
#else
   return pclose (stream);
#endif
}

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

   fd = popen_printer ();
   if (fd == NULL)
     return;

   for (index = 0; index < 4; /* nil*/)
     {
	c = get_com_char ();

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

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

/*
 * process escape sequences
 */
static void
process_escape_seq (void)
{
   unsigned char c = get_com_char ();

   switch (c) {
      /* case 1:	do_tek_mode ();	break; */
    case '#':	if ('8' == get_com_char ()) scr_E ();	break;
    case '(':	scr_charset_set (0, get_com_char ());	break;
    case ')':	scr_charset_set (1, get_com_char ());	break;
    case '*':	scr_charset_set (2, get_com_char ());	break;
    case '+':	scr_charset_set (3, get_com_char ());	break;
#ifdef KANJI
    case '$':	scr_charset_set (-2, get_com_char ());	break;
#endif
    case '7':	scr_cursor (SAVE);	break;
    case '8':	scr_cursor (RESTORE);	break;
    case '=':
    case '>':
      PrivMode ((c == '='), PrivMode_aplkp);
      break;
    case '@':	(void)get_com_char ();	break;
    case 'D':	scr_index (UP);		break;
    case 'E':	scr_add_lines ("\n\r", 1, 2);	break;
    case 'G':	process_rxvt_graphics ();	break;
    case 'H':	scr_set_tab (1);	break;
    case 'M':	scr_index (DOWN);	break;
    /*case 'N':	scr_single_shift (2);	break;*/
    /*case 'O':	scr_single_shift (3);	break;*/
    case 'Z':	tty_printf (RXVT_ANS);	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 c, priv = 0;
   int nargs, arg [ESC_ARGS];

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

   c = get_com_char ();
   if (c >= '<' && c <= '?')
     {
	priv = c;
	c = get_com_char ();
     }

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

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

	if (c < '@')
	  c = get_com_char ();
     }
   while (c >= ' ' && c < '@');
   if (c == 033)
     {
	process_escape_seq ();
	return;
     }
   else if (c < ' ')
     return;

   switch (c) {
#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':	tty_printf (VT100_ANS);		break;
    case 'm':	process_sgr_mode (nargs, arg);	break;
    case 'n':			/* request for information */
      switch (arg[0]) {
       case 5: tty_printf ("\033[0n");		break;	/* ready */
       case 6: scr_report_position ();			break;
#if defined (ENABLE_DISPLAY_ANSWER)
       case 7: tty_printf ("%s\n", display_name);	break;
#endif
       case 8:
	 xterm_seq (NEW_TITLE_NAME, APL_NAME "-" VERSION);
	 break;
      }
      break;
    case 'r':			/* set top and bottom margins */
      /* what's this about? something to do with vi on ESIX systems */
      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 'h':
    case 'l':
      process_terminal_mode (c, 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 c, string [STRING_MAX];
   int n, arg;

   c = get_com_char ();
   for (n = 0; isdigit (c); c = get_com_char ())
     n = n * 10 + (c - '0');
   arg = n;

   n = 0;
   while ((c = get_com_char ()) != 007)
     if ((n < sizeof(string)-1) && (c >= ' '))
       string [n++] = c;

   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
 * so no need for fancy checking
 */
static void
process_terminal_mode (unsigned char mode, unsigned char priv,
		       int nargs, int arg[])
{
   int i;
   /* saved modes */

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

   if (nargs == 0) return;

   switch (priv) {
    case 0:
      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)	switch (mode) {\
case 's': SavedModes |= (PrivateModes & bit); continue; break;\
case 'r': mode = (SavedModes & bit) ? 1 : 0;/*drop*/\
default:  PrivMode (mode, bit); }

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

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

	   /* case 4:	- smooth scrolling */

	 case 5:
	   PrivCases (PrivMode_rvideo);
	   scr_rvideo_mode (mode);
	   break;

	 case 6:
	   PrivCases (PrivMode_relorigin);
	   scr_relative_origin (mode);
	   break;

	 case 7:
	   PrivCases (PrivMode_autowrap);
	   scr_autowrap (mode);
	   break;

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

	 case 9:		/* X10 mouse reporting */
	   PrivCases (PrivMode_mouse_X10);
	   break;

	 case 25:
	   PrivCases (PrivMode_viscursor);
	   scr_cursor_visible (mode);
	   break;

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

	 case 47:
	   PrivCases (PrivMode_screen);
	   scr_change_screen (mode);
	   break;

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

	 case 1000:		/* X11 mouse reporting */
	   PrivCases (PrivMode_mouse_X11);
	   break;
#if 0
	 case 1001: break;	/* X11 mouse highlighting */
#endif
	}
      break;
   }
}

/*
 * process sgr sequences
 */
static void
process_sgr_mode (int nargs, int arg[])
{
   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 (COLORMIN + (arg [i] - 30), RS_Bold);
	break;
      case 39:			/* default fg */
	scr_color (RESTORE_FG, RS_Bold);
	break;

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

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

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

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

	c = get_com_char ();
	neg = (c == '-');
	if (neg || c == '+')
	  c = get_com_char ();

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

	nargs++;
	args [nargs] = 0;
	if (c !=  ';')
	  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] = get_com_char ();
	     text [len] = '\0';
	  }
     }

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

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

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

	   /*
	    * point to the start of the string,
	    * decrement first since already did get_com_char ()
	    */
	   str = --cmdbuf_ptr;

	   while (cmdbuf_ptr < cmdbuf_endp)
	     {
		c = *cmdbuf_ptr;
		if (c >= ' ' || c == '\t' || c == '\n' || c == '\r') /* (isprint (c))*/
		  {
		     cmdbuf_ptr++;
		     if (c == '\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 (c) {
	    case 005: tty_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 (c != EOF);
}
/*----------------------- end-of-file (C source) -----------------------*/
