/*
 *--------------------------------*-C-*---------------------------------
 * File:        command.c
 *
 * Copyright (c) 1992 John Bovey <jdb@ukc.ac.uk>
 * Copyright (c) 1994 R. Nation <nation@rocket.sanders.lockheed.com>
 * Copyright (c) 1995 Garrett D'Amore <garrett@netcom.com>
 * Copyright (c) 1995 Steven Hirsch <hirsch@emba.uvm.edu>
 * Copyright (c) 1995 Jakub Jelinek <jj@gnu.ai.mit.edu>
 * Copyright (c) 1997 Raul Garcia Garcia <rgg@tid.es> 
 * Copyright (c) 1997 Mj Olesen <olesen@me.QueensU.CA>
 * Copyright (c) 1997 Guylhem Aznar <guylhem@danmark.linux.eu.org>
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License version
 *  2 as published by the Free Software Foundation.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
 *----------------------------------------------------------------------
 */

/*{{{ 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 USES_XLOCALE
#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 "xiterm.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 xiterm) 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 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;

				/* xiterm 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;
					}
					if (ev->xbutton.button == Button1) {
						if (ev->xbutton.time - buttonpress_time < MULTICLICK_TIME)
							clicks++;
						else
							clicks = 1;
					}
					mouse_report(&(ev->xbutton));
				} else
					switch (ev->xbutton.button) {
					case Button1:
/* Click without selection (i.e. without moving the mouse) */
						if (ev->xbutton.time - buttonpress_time < MULTICLICK_TIME)
							clicks++;
						else
							clicks = 1;
						selection_click(0, clicks, ev->xbutton.x, ev->xbutton.y);
						selection_make(ev->xbutton.time);
						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();
			/*
			 * Xiterm-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;
				}
/* What hell is that for ? Without it, everything still work fine !
 * "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_click (1, clicks, ev->xbutton.x, ev->xbutton.y);
 */
				switch (ev->xbutton.button) {
				case Button1:
				case Button3:
/* Click without selection, part 2 (i.e. without moving the mouse) */
					selection_click(0, clicks, ev->xbutton.x, ev->xbutton.y);
					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)) {
				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
					/* Click & selection (i.e. click & drag) */
					selection_click(1, clicks, ev->xbutton.x, ev->xbutton.y);
				selection_make(ev->xbutton.time);
			}
		} 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 '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;

#ifndef NO_SECONDARY_SCREEN
			case 47:	/* secondary screen */
				PrivCases(PrivMode_Screen);
				scr_change_screen(state);
				break;
#endif

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

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