/*  Copyright 1992 John Bovey, University of Kent at Canterbury.
 *
 *  You can do what you like with this source code as long as
 *  you don't try to make money out of it and you include an
 *  unaltered copy of this message (including the copyright).
 */

char xvt_command_c_sccsid[] = "@(#)command.c	1.1 14/7/92 (UKC)";

#include <stdarg.h>
#include <X11/Xlib.h>
#include <X11/Xutil.h>
#include <X11/Xauth.h>
#include <X11/keysym.h>
#include <sys/ioctl.h>
#include <sys/types.h>

#ifdef _AIX
#include <termios.h>
#else
#include <sys/termios.h>
#endif

#include <sys/socket.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <errno.h>
#include <unistd.h>
#include <signal.h>
#include <fcntl.h>
#include <netdb.h>
#include <utmp.h>
#include <grp.h>
#include <pwd.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#include "rvt.h"
#include "token.h"
#include "command.h"
#include "screen.h"
#include "xsetup.h"
#include "../common/common.h"

#define NLMAX	15		/* max number of lines to scroll */

#define KBUFSIZE	256	/* size of keyboard mapping buffer */
#define COM_BUF_SIZE	2048	/* size of buffer used to read from the
				 * command */
#define COM_PUSH_MAX	20	/* max number of characters that can be pushed
				 * back into the input queue */
#define MP_INTERVAL	500	/* multi-press interval in milliseconds */

/*  Special character returned by get_com_char().
 */
#define GCC_NULL	0x100	/* Input buffer is empty */
#define ESC		033

/*  Flags used to control get_com_char();
 */
#define BUF_ONLY	1
#define GET_XEVENTS	2

/*  Global variables that are set up at the beginning and then not changed
 */
extern Display *display;
extern Window vt_win;
extern Window sb_win;
extern Window main_win;

static int comm_fd = -1;	/* file descriptor connected to the command */
static int comm_pid;		/* process id if child */
static int x_fd;		/* file descriptor of the X server connection */
static int fd_width;		/* width of file descriptors being used */
static int app_cur_keys = 0;	/* flag to say cursor keys are in application
				 * mode */
static int app_kp_keys = 0;	/* flag to set application keypad keys */
static char *ttynam;
static Atom wm_del_win;

static char *send_buf = NULL;	/* characters waiting to be sent to the
				 * command */
static char *send_nxt = NULL;	/* next character to be sent */
static int send_count = 0;	/* number of characters waiting to be sent */

/*  Terminal mode structures.

static struct termios ttmode = {
	BRKINT | IGNPAR | ISTRIP | ICRNL | IXON | IMAXBEL,
	OPOST | ONLCR,
	B9600 | PARENB | CS7 | CREAD,
	ISIG | IEXTEN | ICANON | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE,
	{003, 034, 0177, 025,
		004, 000, 000, 000,
		021, 023, 032, 022,
		022, 017, 027, 026
	},
	0, 0
};*/
/*  Static variables used to record interesting X events.
 */
/*  Small X event structure used to queue interesting X events that need to
 *  be converted into tokens.
 */
struct xeventst {
	int xe_type;
	int xe_button;
	int xe_state;
	int xe_x;
	int xe_y;
	int xe_width;
	int xe_height;
	int xe_detail;
	unsigned long xe_time;
	Window xe_window;
	Atom xe_property;	/* for selection requests */
	Atom xe_target;
	Window xe_requestor;	/* ditto */
	struct xeventst *xe_next;
	struct xeventst *xe_prev;
};

static struct xeventst *xevent_start = NULL;
static struct xeventst *xevent_last = NULL;	/* start and end of queue */

/*  Variables used for buffered command input.
 */
static char com_buf[COM_BUF_SIZE];
static char *com_buf_next, *com_buf_top;
static char com_stack[COM_PUSH_MAX];	/* stack of pushed back characters */
static char *com_stack_top;

static void push_xevent(struct xeventst *);
static struct xeventst *pop_xevent(void);
static void catch_child(int);
static void catch_sig(int);
static int run_command(char *, char **);
static const char *lookup_key(XEvent *, int *);
static int get_com_char(int);
static void push_com_char(int);
static void show_token_args(struct tokenst *);
static void show_hex_token_args(struct tokenst *);

/*  Send a 'Magic Cookie' authorisation string to the command.
 */
void
send_auth(void)
{
	static char hexdigits[] = "0123456789abcdef";
	char *display_name, *nptr, *dot;
	char *buf, *optr;
	Xauth *auth;
	int i, nlen, len;
	struct hostent *h;
	char hostname[64];

	display_name = DisplayString(display);

	if ((nptr = strchr(display_name, ':')) == NULL)
		return;

	if (nptr == display_name || nptr - display_name > sizeof(hostname))
		return;

	memcpy(hostname, display_name, nptr - display_name);
	hostname[nptr - display_name] = '\0';
	++nptr;

	if ((h = gethostbyname(hostname)) == NULL)
		return;
	if (h->h_addrtype != AF_INET)
		return;

	if ((dot = strchr(nptr, '.')) != NULL)
		nlen = dot - nptr;
	else
		nlen = strlen(nptr);

	auth = XauGetAuthByAddr(FamilyInternet, 4, h->h_addr_list[0],
	    nlen, nptr, 0, "");
	if (auth == NULL)
		return;

	len = 2 + 2 +
	    2 + auth->address_length +
	    2 + auth->number_length +
	    2 + auth->name_length +
	    2 + auth->data_length;

	if ((buf = (char *) cmalloc(len * 2 + 1)) == NULL) {
		XauDisposeAuth(auth);
		return;
	}
	optr = buf;

#define PUTSHORT(o, n)	  *o++ = (n >> 8) & 0xff, *o++ = n & 0xff
#define PUTBYTES(o, s, n) PUTSHORT(o, n), memcpy(o, s, n), o += n

	PUTSHORT(optr, (len - 2) * 2);
	PUTSHORT(optr, auth->family);

	PUTBYTES(optr, auth->address, auth->address_length);
	PUTBYTES(optr, auth->number, auth->number_length);
	PUTBYTES(optr, auth->name, auth->name_length);
	PUTBYTES(optr, auth->data, auth->data_length);

#undef PUTSHORT
#undef PUTBYTES

	if (optr != buf + len)
		abort();

	for (i = len - 1; i >= 0; --i) {
		buf[i * 2 + 1] = hexdigits[buf[i] & 0xf];
		buf[i * 2] = hexdigits[(buf[i] >> 4) & 0xf];
	}

	buf[len * 2] = '\n';
	send_string(buf, len * 2 + 1);

	free(buf);
	XauDisposeAuth(auth);

	return;
}
/*  Push a mini X event onto the queue
 */
static void
push_xevent(xe)
	struct xeventst *xe;
{
	xe->xe_next = xevent_start;
	xe->xe_prev = NULL;
	if (xe->xe_next != NULL)
		xe->xe_next->xe_prev = xe;
	else
		xevent_last = xe;
}

static struct xeventst *
pop_xevent(void)
{
	struct xeventst *xe;

	if (xevent_last == NULL)
		return (NULL);

	xe = xevent_last;
	xevent_last = xe->xe_prev;
	if (xevent_last != NULL)
		xevent_last->xe_next = NULL;
	else
		xevent_start = NULL;
	return (xe);
}
/*  Catch a SIGCHLD signal and exit if the direct child has died.
 */

/* ARGSUSED */
static void
catch_child(sig)
	int sig;
{
	if (wait((int *) NULL) == comm_pid)
		quit(0);
}

/*  Catch a fatal signal and tidy up before quitting
 */
static void
catch_sig(sig)
	int sig;
{
	signal(sig, SIG_DFL);
	setuid(getuid());
	kill(getpid(), sig);
}

/*  Quit with the status after first removing our entry from the utmp file.
 */
void
quit(status)
	int status;
{
	exit(status);
}

/*  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(command, argv)
	char *command;
	char **argv;
{
	int ptyfd, ttyfd;
	int i;

	for (i = 1; i <= 15; i++)
		signal(i, catch_sig);
	signal(SIGCHLD, catch_child);
	openpty(&ptyfd, &ttyfd, ttynam, NULL, NULL);
	comm_pid = fork();
	if (comm_pid < 0) {
		error("Can't fork");
		return (-1);
	}
	if (comm_pid == 0) {
		close(ptyfd);
		login_tty(ttyfd);
		execvp(command, argv);
		error("Couldn't execute %s", command);
		quit(1);
	}
	close(ttyfd);
	return (ptyfd);
}
/*  Tell the teletype handler what size the window is.  Called after a window
 *  size change.
 */
void
tty_set_size(width, height)
	int width, height;
{
	struct winsize wsize;

	if (comm_fd < 0)
		return;
	wsize.ws_row = height;
	wsize.ws_col = width;
	ioctl(comm_fd, TIOCSWINSZ, (char *) &wsize);
}
/*  Initialise the command connection.  This should be called after the X
 *  server connection is established.
 */
void
init_command(command, argv)
	char *command;
	char **argv;
{
	/* Enable the delete window protocol. */
	wm_del_win = XInternAtom(display, "WM_DELETE_WINDOW", False);
	XSetWMProtocols(display, main_win, &wm_del_win, 1);

	if ((comm_fd = run_command(command, argv)) < 0) {
		error("Quitting");
		quit(1);
	}
	x_fd = XConnectionNumber(display);
	fd_width = getdtablesize();
	com_buf_next = com_buf_top = com_buf;
	com_stack_top = com_stack;
}
/*  Set the current cursor keys mode.
 */
void
set_cur_keys(mode)
	int mode;
{
	app_cur_keys = (mode == HIGH);
}
/*  Set the current keypad keys mode.
 */
void
set_kp_keys(mode)
	int mode;
{
	app_kp_keys = (mode == HIGH);
}
/*  Convert the keypress event into a string.
 */
static const char *
lookup_key(ev, pcount)
	int *pcount;
	XEvent *ev;
{
	KeySym keysym;
	XComposeStatus compose;
	int count;
	static char kbuf[KBUFSIZE];
	const char *s;

	count = XLookupString(&ev->xkey, kbuf, KBUFSIZE, &keysym, &compose);
	s = NULL;
	switch (keysym) {
	case XK_Up:
		s = app_cur_keys ? "\033OA" : "\033[A";
		break;
	case XK_Down:
		s = app_cur_keys ? "\033OB" : "\033[B";
		break;
	case XK_Right:
		s = app_cur_keys ? "\033OC" : "\033[C";
		break;
	case XK_Left:
		s = app_cur_keys ? "\033OD" : "\033[D";
		break;
	case XK_KP_F1:
		s = "\033OP";
		break;
	case XK_KP_F2:
		s = "\033OQ";
		break;
	case XK_KP_F3:
		s = "\033OR";
		break;
	case XK_KP_F4:
		s = "\033OS";
		break;
	case XK_KP_0:
		s = app_kp_keys ? "\033Op" : "0";
		break;
	case XK_KP_1:
		s = app_kp_keys ? "\033Oq" : "1";
		break;
	case XK_KP_2:
		s = app_kp_keys ? "\033Or" : "2";
		break;
	case XK_KP_3:
		s = app_kp_keys ? "\033Os" : "3";
		break;
	case XK_KP_4:
		s = app_kp_keys ? "\033Ot" : "4";
		break;
	case XK_KP_5:
		s = app_kp_keys ? "\033Ou" : "5";
		break;
	case XK_KP_6:
		s = app_kp_keys ? "\033Ov" : "6";
		break;
	case XK_KP_7:
		s = app_kp_keys ? "\033Ow" : "7";
		break;
	case XK_KP_8:
		s = app_kp_keys ? "\033Ox" : "8";
		break;
	case XK_KP_9:
		s = app_kp_keys ? "\033Oy" : "9";
		break;
	case XK_KP_Subtract:
		s = app_kp_keys ? "\033Om" : "-";
		break;
	case XK_KP_Separator:
		s = app_kp_keys ? "\033Ol" : ",";
		break;
	case XK_KP_Decimal:
		s = app_kp_keys ? "\033On" : ".";
		break;
	case XK_KP_Enter:
		s = app_kp_keys ? "\033OM" : "\r";
		break;
	}
	if (s != NULL)
		*pcount = strlen(s);
	else {
		s = kbuf;
		*pcount = count;
	}
	return (s);
}
/*  Return the next input character after first passing any keyboard input
 *  to the command.  If flags & BUF_ONLY is true then only buffered characters are
 *  returned and once the buffer is empty the special value GCC_NULL is
 *  returned.  If flags and GET_XEVENTS is true then GCC_NULL is returned
 *  when an X event arrives.
 */
static int
get_com_char(flags)
	int flags;
{
	XEvent event;
	struct xeventst *xe;
	fd_set in_fdset, out_fdset;
	const char *s;
	int count, sv;

	if (com_stack_top > com_stack)
		return (*--com_stack_top);

	if (com_buf_next < com_buf_top)
		return (*com_buf_next++ & 0177);
	else if (flags & BUF_ONLY)
		return (GCC_NULL);

	for (;;) {
		FD_ZERO(&in_fdset);
		while (XPending(display) == 0) {
			if (FD_ISSET(x_fd, &in_fdset))
				/* If we get to this point something is wrong
				 * because there is X input available but no
				 * events.  Exit the program to avoid looping
				 * forever. */
				quit(0);
			FD_SET(comm_fd, &in_fdset);
			FD_SET(x_fd, &in_fdset);
			FD_ZERO(&out_fdset);
			if (send_count > 0)
				FD_SET(comm_fd, &out_fdset);
			if ((sv = select(fd_width, &in_fdset, &out_fdset, NULL, NULL)) < 0) {
				error("select failed");
				quit(-1);
			}
			if (FD_ISSET(comm_fd, &in_fdset))
				break;

			if (FD_ISSET(comm_fd, &out_fdset)) {
				count = send_count < 100 ? send_count : 100;
				count = write(comm_fd, send_nxt, count);
				if (count < 0) {
					error("failed to write to command");
					quit(-1);
				}
				send_count -= count;
				send_nxt += count;
			}
		}
		if (FD_ISSET(comm_fd, &in_fdset))
			break;
		XNextEvent(display, &event);
		if (event.type == KeyPress) {
			s = lookup_key(&event, &count);
			if (count != 0) {
				if (write(comm_fd, s, count) <= 0) {
					if (errno == EWOULDBLOCK)
						XBell(display, 0);
					else {
						error("write to pty failed");
						quit(1);
					}
				}
			}
		} else if (event.type == ClientMessage) {
			if (event.xclient.format == 32 && event.xclient.data.l[0] == wm_del_win)
				quit(0);
		} else if (event.type == MappingNotify) {
			XRefreshKeyboardMapping(&event.xmapping);
		} else if (event.type == SelectionRequest) {
			xe = (struct xeventst *) cmalloc(sizeof(struct xeventst));
			xe->xe_type = event.type;
			xe->xe_window = event.xselectionrequest.owner;
			xe->xe_time = event.xselectionrequest.time;
			xe->xe_requestor = event.xselectionrequest.requestor;
			xe->xe_target = event.xselectionrequest.target;
			xe->xe_property = event.xselectionrequest.property;
			push_xevent(xe);
			if (flags & GET_XEVENTS)
				return (GCC_NULL);
		} else if (event.type == SelectionNotify) {
			xe = (struct xeventst *) cmalloc(sizeof(struct xeventst));
			xe->xe_type = event.type;
			xe->xe_time = event.xselection.time;
			xe->xe_requestor = event.xselection.requestor;
			xe->xe_property = event.xselection.property;
			push_xevent(xe);
			if (flags & GET_XEVENTS)
				return (GCC_NULL);
		} else if (event.type == FocusIn || event.type == FocusOut) {
			if (event.xfocus.mode != NotifyNormal)
				continue;
			switch (event.xfocus.detail) {
			case NotifyAncestor:
			case NotifyInferior:
			case NotifyNonlinear:
				break;
			default:
				continue;
			}
			xe = (struct xeventst *) cmalloc(sizeof(struct xeventst));
			xe->xe_type = event.type;
			xe->xe_time = event.xselection.time;
			xe->xe_detail = event.xfocus.detail;
			push_xevent(xe);
			if (flags & GET_XEVENTS)
				return (GCC_NULL);
		} else if ((event.type == Expose || event.type == GraphicsExpose) &&
		    event.xexpose.count != 0)
			continue;
		else {
			xe = (struct xeventst *) cmalloc(sizeof(struct xeventst));
			xe->xe_type = event.type;
			xe->xe_window = event.xany.window;
			if (event.type == Expose || event.type == GraphicsExpose) {
				if (event.xexpose.count != 0)
					continue;
				xe->xe_x = event.xexpose.x;
				xe->xe_y = event.xexpose.y;
				xe->xe_width = event.xexpose.width;
				xe->xe_height = event.xexpose.height;
			} else {
				xe->xe_time = event.xbutton.time;
				xe->xe_x = event.xbutton.x;
				xe->xe_y = event.xbutton.y;
				xe->xe_state = event.xbutton.state;
				xe->xe_button = event.xbutton.button;
			}
			push_xevent(xe);
			if (flags & GET_XEVENTS)
				return (GCC_NULL);
		}
	}

	count = read(comm_fd, com_buf, COM_BUF_SIZE);
	if (count <= 0)
		return (EOF);
	com_buf_next = com_buf;
	com_buf_top = com_buf + count;
	return (*com_buf_next++ & 0177);
}
/*  Push an input character back into the input queue.
 */
static void
push_com_char(c)
	int c;
{
	if (com_stack_top < com_stack + COM_PUSH_MAX)
		*com_stack_top++ = c;
}
/*  Send count characters directly to the command.
 */
void
send_string(buf, count)
	char *buf;
	int count;
{
	char *s;
	register char *s1, *s2;
	register int i;

	if (count == 0)
		return;

	if (send_count == 0) {
		if (send_buf != NULL) {
			free(send_buf);
			send_buf = NULL;
		}
		send_buf = (char *) cmalloc(count);
		s2 = send_buf;
		s1 = buf;
		for (i = 0; i < count; i++, s1++, s2++)
			*s2 = *s1 == '\n' ? '\r' : *s1;
		send_nxt = send_buf;
		send_count = count;
	} else {
		s = (char *) cmalloc(send_count + count);
		memcpy(s, send_nxt, send_count);
		s2 = s + send_count;
		s1 = buf;
		for (i = 0; i < count; i++, s1++, s2++)
			*s2 = *s1 == '\n' ? '\r' : *s1;
		free(send_buf);
		send_buf = send_nxt = s;
		send_count += count;
	}
}

/*  Send printf formatted output to the command.  Only used for small amounts
 *  of data.
 */
/*VARARGS1*/
void
cprintf(const char *fmt, ...)
{
	va_list args;
	static char buf[1024];

	va_start(args, fmt);

	vsprintf(buf, fmt, args);
	va_end(args);
	send_string(buf, strlen(buf));
}

/*  Return an input token
 */
void
get_token(tk)
	struct tokenst *tk;
{
	int c, i, n;
	struct xeventst *xe;
	static unsigned int time1 = 0, time2 = 0;

	tk->tk_private = 0;
	tk->tk_type = TK_NULL;

	if ((xe = pop_xevent()) != NULL) {
		if (xe->xe_window == vt_win)
			tk->tk_region = SCREEN;
		else if (xe->xe_window == sb_win)
			tk->tk_region = SCROLLBAR;
		else if (xe->xe_window == main_win)
			tk->tk_region = MAINWIN;
		else
			tk->tk_region = -1;
		switch (xe->xe_type) {
		case EnterNotify:
			tk->tk_type = TK_ENTRY;
			tk->tk_arg[0] = 1;
			tk->tk_nargs = 1;
			break;
		case LeaveNotify:
			tk->tk_type = TK_ENTRY;
			tk->tk_arg[0] = 0;
			tk->tk_nargs = 1;
			break;
		case FocusIn:
			tk->tk_type = TK_FOCUS;
			tk->tk_arg[0] = 1;
			tk->tk_arg[1] = xe->xe_detail;
			tk->tk_nargs = 2;
			break;
		case FocusOut:
			tk->tk_type = TK_FOCUS;
			tk->tk_arg[0] = 0;
			tk->tk_arg[1] = xe->xe_detail;
			tk->tk_nargs = 2;
			break;
		case Expose:
			tk->tk_type = TK_EXPOSE;
			tk->tk_arg[0] = xe->xe_x;
			tk->tk_arg[1] = xe->xe_y;
			tk->tk_arg[2] = xe->xe_width;
			tk->tk_arg[3] = xe->xe_height;
			tk->tk_nargs = 4;
			break;
		case ConfigureNotify:
			tk->tk_type = TK_RESIZE;
			tk->tk_nargs = 0;
			break;
		case SelectionClear:
			tk->tk_type = TK_SELCLEAR;
			tk->tk_arg[0] = xe->xe_time;
			tk->tk_nargs = 1;
			break;
		case SelectionNotify:
			tk->tk_type = TK_SELNOTIFY;
			tk->tk_arg[0] = xe->xe_time;
			tk->tk_arg[1] = xe->xe_requestor;
			tk->tk_arg[2] = xe->xe_property;
			tk->tk_nargs = 3;
			break;
		case SelectionRequest:
			tk->tk_type = TK_SELREQUEST;
			tk->tk_arg[0] = xe->xe_time;
			tk->tk_arg[1] = xe->xe_requestor;
			tk->tk_arg[2] = xe->xe_target;
			tk->tk_arg[3] = xe->xe_property;
			tk->tk_nargs = 4;
			break;
		case ButtonPress:
			if (xe->xe_state == ControlMask) {
				tk->tk_type = TK_SBSWITCH;
				tk->tk_nargs = 0;
				break;
			}
			if (xe->xe_state == Mod5Mask) {
				switch (xe->xe_button) {
				case Button1:
					tk->tk_type = TK_SBUP;
					tk->tk_arg[0] = 300;
					tk->tk_nargs = 1;
					break;
				case Button3:
					tk->tk_type = TK_SBDOWN;
					tk->tk_arg[0] = 300;
					tk->tk_nargs = 1;
					break;
				}
			}
			if (xe->xe_window == vt_win && xe->xe_state == 0) {
				switch (xe->xe_button) {
				case Button1:
					if (xe->xe_time - time2 < MP_INTERVAL) {
						time1 = 0;
						time2 = 0;
						tk->tk_type = TK_SELLINE;
					} else if (xe->xe_time - time1 < MP_INTERVAL) {
						time2 = xe->xe_time;
						tk->tk_type = TK_SELWORD;
					} else {
						time1 = xe->xe_time;
						tk->tk_type = TK_SELSTART;
					}
					break;
				case Button2:
					tk->tk_type = TK_NULL;
					break;
				case Button3:
					tk->tk_type = TK_SELEXTND;
					break;
				}
				tk->tk_arg[0] = xe->xe_x;
				tk->tk_arg[1] = xe->xe_y;
				tk->tk_nargs = 2;
				break;
			}
			if (xe->xe_window == sb_win) {
				if (xe->xe_button == Button2) {
					tk->tk_type = TK_SBGOTO;
					tk->tk_arg[0] = xe->xe_y;
					tk->tk_nargs = 1;
				}
			}
			break;
		case ButtonRelease:
			if (xe->xe_window == sb_win) {
				switch (xe->xe_button) {
				case Button1:
					tk->tk_type = TK_SBUP;
					tk->tk_arg[0] = xe->xe_y;
					tk->tk_nargs = 1;
					break;
				case Button3:
					tk->tk_type = TK_SBDOWN;
					tk->tk_arg[0] = xe->xe_y;
					tk->tk_nargs = 1;
					break;
				}
			} else if ((xe->xe_state & ControlMask) == 0) {
				switch (xe->xe_button) {
				case Button1:
				case Button3:
					tk->tk_type = TK_SELECT;
					tk->tk_arg[0] = xe->xe_time;
					tk->tk_nargs = 1;
					break;
				case Button2:
					tk->tk_type = TK_SELINSRT;
					tk->tk_arg[0] = xe->xe_time;
					tk->tk_arg[1] = xe->xe_x;
					tk->tk_arg[2] = xe->xe_y;
					tk->tk_nargs = 3;
					break;
				}
			}
			break;
		case MotionNotify:
			if (xe->xe_window == sb_win && (xe->xe_state & Button2Mask)) {
				Window root, child;
				int root_x, root_y, x, y;
				unsigned int mods;

				XQueryPointer(display, sb_win, &root, &child,
				    &root_x, &root_y, &x, &y, &mods);
				if (mods & Button2Mask) {
					tk->tk_type = TK_SBGOTO;
					tk->tk_arg[0] = y;
					tk->tk_nargs = 1;
				}
				break;
			}
			if (xe->xe_window == vt_win && (xe->xe_state == Button1Mask)) {
				tk->tk_type = TK_SELDRAG;
				tk->tk_arg[0] = xe->xe_x;
				tk->tk_arg[1] = xe->xe_y;
				tk->tk_nargs = 2;
				break;
			}
			break;

		}
		free((char *) xe);
		return;
	}
	if ((c = get_com_char(GET_XEVENTS)) == GCC_NULL) {
		tk->tk_type = TK_NULL;
		return;
	}
	if (c == EOF) {
		tk->tk_type = TK_EOF;
		return;
	}
	if (c >= ' ' || c == '\n' || c == '\r' || c == '\t') {
		i = 0;
		tk->tk_nlcount = 0;
		do {
			tk->tk_string[i++] = c;
			c = get_com_char(1);
			if (c == '\n' && ++tk->tk_nlcount >= NLMAX) {
				tk->tk_nlcount--;
				break;
			}
		} while (!(c & ~0177) &&
		    (c >= ' ' || c == '\n' || c == '\r' || c == '\t') && i < TKS_MAX);
		tk->tk_length = i;
		tk->tk_string[i] = 0;
		tk->tk_type = TK_STRING;
		if (c != GCC_NULL)
			push_com_char(c);
	} else if (c == ESC) {
		c = get_com_char(0);
		if (c == '[') {
			c = get_com_char(0);
			if (c >= '<' && c <= '?') {
				tk->tk_private = c;
				c = get_com_char(0);
			}
			/* read any numerical arguments */
			i = 0;
			do {
				n = 0;
				while (c >= '0' && c <= '9') {
					n = n * 10 + c - '0';
					c = get_com_char(0);
				}
				if (i < TK_MAX_ARGS)
					tk->tk_arg[i++] = n;
				if (c == ESC)
					push_com_char(c);
				if (c < ' ')
					return;
				if (c < '@')
					c = get_com_char(0);
			} while (c < '@' && c >= ' ');
			if (c == ESC)
				push_com_char(c);
			if (c < ' ')
				return;
			tk->tk_nargs = i;
			tk->tk_type = c;
		} else if (c == ']') {
			c = get_com_char(0);
			n = 0;
			while (c >= '0' && c <= '9') {
				n = n * 10 + c - '0';
				c = get_com_char(0);
			}
			tk->tk_arg[0] = n;
			tk->tk_nargs = 1;
			c = get_com_char(0);
			i = 0;
			while (!(c & ~0177) && c != 7 && i < TKS_MAX) {
				if (c >= ' ')
					tk->tk_string[i++] = c;
				c = get_com_char(0);
			}
			tk->tk_length = i;
			tk->tk_string[i] = 0;
			tk->tk_type = TK_TXTPAR;
		} else if (c == '#' || c == '(' || c == ')') {
			tk->tk_type = c;
			c = get_com_char(0);
			tk->tk_arg[0] = c;
			tk->tk_nargs = 1;
		} else if (c == '7' || c == '8' || c == '=' || c == '>') {
			tk->tk_type = c;
			tk->tk_nargs = 0;
		} else {
			switch (c) {
			case 'D':
				tk->tk_type = TK_IND;
				break;
			case 'E':
				tk->tk_type = TK_NEL;
				break;
			case 'H':
				tk->tk_type = TK_HTS;
				break;
			case 'M':
				tk->tk_type = TK_RI;
				break;
			case 'N':
				tk->tk_type = TK_SS2;
				break;
			case 'O':
				tk->tk_type = TK_SS3;
				break;
			case 'Z':
				tk->tk_type = TK_DECID;
				break;
			default:
				return;
			}
		}
	} else {
		tk->tk_type = TK_CHAR;
		tk->tk_char = c;
	}
}
/*  Print out a token's numerical arguments. Just used by show_token()
 */
static void
show_token_args(tk)
	struct tokenst *tk;
{
	int i;

	for (i = 0; i < tk->tk_nargs; i++) {
		if (i == 0)
			printf(" (%d", tk->tk_arg[i]);
		else
			printf(",%d", tk->tk_arg[i]);
	}
	if (tk->tk_nargs > 0)
		printf(")");
	if (tk->tk_private != 0)
		putchar(tk->tk_private);
}
/*  Print out a token's numerical arguments in hex. Just used by show_token()
 */
static void
show_hex_token_args(tk)
	struct tokenst *tk;
{
	int i;

	for (i = 0; i < tk->tk_nargs; i++) {
		if (i == 0)
			printf(" (0x%x", tk->tk_arg[i]);
		else
			printf(",0x%x", tk->tk_arg[i]);
	}
	if (tk->tk_nargs > 0)
		printf(")");
	if (tk->tk_private != 0)
		putchar(tk->tk_private);
}
/*  Print out the contents of an input token - used for debugging.
 */
void
show_token(tk)
	struct tokenst *tk;
{

	/* Screen out token types that are not currently of interest. */
	switch (tk->tk_type) {
	case TK_SELDRAG:
		return;
	}

	switch (tk->tk_type) {
	case TK_STRING:
		printf("token(TK_STRING)");
		printf(" \"%s\"", tk->tk_string);
		break;
	case TK_TXTPAR:
		printf("token(TK_TXTPAR)");
		printf(" (%d) \"%s\"", tk->tk_arg[0], tk->tk_string);
		break;
	case TK_CHAR:
		printf("token(TK_CHAR)");
		printf(" <%o>", tk->tk_char);
		break;
	case TK_EOF:
		printf("token(TK_EOF)");
		show_token_args(tk);
		break;
	case TK_FOCUS:
		printf("token(TK_FOCUS)");
		printf(" <%d>", tk->tk_region);
		show_token_args(tk);
		break;
	case TK_ENTRY:
		printf("token(TK_ENTRY)");
		printf(" <%d>", tk->tk_region);
		show_token_args(tk);
		break;
	case TK_SBSWITCH:
		printf("token(TK_SBSWITCH)");
		show_token_args(tk);
		break;
	case TK_SBGOTO:
		printf("token(TK_SBGOTO)");
		show_token_args(tk);
		break;
	case TK_SBUP:
		printf("token(TK_SBUP)");
		show_token_args(tk);
		break;
	case TK_SBDOWN:
		printf("token(TK_SBDOWN)");
		show_token_args(tk);
		break;
	case TK_EXPOSE:
		printf("token(TK_EXPOSE)");
		printf("(%d)", tk->tk_region);
		show_token_args(tk);
		break;
	case TK_RESIZE:
		printf("token(TK_RESIZE)");
		show_token_args(tk);
		break;
	case TK_SELSTART:
		printf("token(TK_SELSTART)");
		show_token_args(tk);
		break;
	case TK_SELEXTND:
		printf("token(TK_SELEXTND)");
		show_token_args(tk);
		break;
	case TK_SELDRAG:
		printf("token(TK_SELDRAG)");
		show_token_args(tk);
		break;
	case TK_SELINSRT:
		printf("token(TK_SELINSRT)");
		show_token_args(tk);
		break;
	case TK_SELECT:
		printf("token(TK_SELECT)");
		show_token_args(tk);
		break;
	case TK_SELWORD:
		printf("token(TK_SELWORD)");
		show_token_args(tk);
		break;
	case TK_SELLINE:
		printf("token(TK_SELLINE)");
		show_token_args(tk);
		break;
	case TK_SELCLEAR:
		printf("token(TK_SELCLEAR)");
		show_token_args(tk);
		break;
	case TK_SELNOTIFY:
		printf("token(TK_SELNOTIFY)");
		show_hex_token_args(tk);
		break;
	case TK_SELREQUEST:
		printf("token(TK_SELREQUEST)");
		show_hex_token_args(tk);
		break;
	case TK_CUU:
		printf("token(TK_CUU)");
		show_token_args(tk);
		break;
	case TK_CUD:
		printf("token(TK_CUD)");
		show_token_args(tk);
		break;
	case TK_CUF:
		printf("token(TK_CUF)");
		show_token_args(tk);
		break;
	case TK_CUB:
		printf("token(TK_CUB)");
		show_token_args(tk);
		break;
	case TK_CUP:
		printf("token(TK_CUP)");
		show_token_args(tk);
		break;
	case TK_ED:
		printf("token(TK_ED)");
		show_token_args(tk);
		break;
	case TK_EL:
		printf("token(TK_EL)");
		show_token_args(tk);
		break;
	case TK_IL:
		printf("token(TK_IL)");
		show_token_args(tk);
		break;
	case TK_DL:
		printf("token(TK_DL)");
		show_token_args(tk);
		break;
	case TK_DCH:
		printf("token(TK_DCH)");
		show_token_args(tk);
		break;
	case TK_ICH:
		printf("token(TK_ICH)");
		show_token_args(tk);
		break;
	case TK_DA:
		printf("token(TK_DA)");
		show_token_args(tk);
		break;
	case TK_HVP:
		printf("token(TK_HVP)");
		show_token_args(tk);
		break;
	case TK_TBC:
		printf("token(TK_TBC)");
		show_token_args(tk);
		break;
	case TK_SET:
		printf("token(TK_SET)");
		show_token_args(tk);
		break;
	case TK_RESET:
		printf("token(TK_RESET)");
		show_token_args(tk);
		break;
	case TK_SGR:
		printf("token(TK_SGR)");
		show_token_args(tk);
		break;
	case TK_DSR:
		printf("token(TK_DSR)");
		show_token_args(tk);
		break;
	case TK_DECSTBM:
		printf("token(TK_DECSTBM)");
		show_token_args(tk);
		break;
	case TK_DECSWH:
		printf("token(TK_DECSWH)");
		show_token_args(tk);
		break;
	case TK_SCS0:
		printf("token(TK_SCS0)");
		show_token_args(tk);
		break;
	case TK_SCS1:
		printf("token(TK_SCS1)");
		show_token_args(tk);
		break;
	case TK_DECSC:
		printf("token(TK_DECSC)");
		show_token_args(tk);
		break;
	case TK_DECRC:
		printf("token(TK_DECRC)");
		show_token_args(tk);
		break;
	case TK_DECPAM:
		printf("token(TK_DECPAM)");
		show_token_args(tk);
		break;
	case TK_DECPNM:
		printf("token(TK_DECPNM)");
		show_token_args(tk);
		break;
	case TK_IND:
		printf("token(TK_IND)");
		show_token_args(tk);
		break;
	case TK_NEL:
		printf("token(TK_NEL)");
		show_token_args(tk);
		break;
	case TK_HTS:
		printf("token(TK_HTS)");
		show_token_args(tk);
		break;
	case TK_RI:
		printf("token(TK_RI)");
		show_token_args(tk);
		break;
	case TK_SS2:
		printf("token(TK_SS2)");
		show_token_args(tk);
		break;
	case TK_SS3:
		printf("token(TK_SS3)");
		show_token_args(tk);
		break;
	case TK_DECID:
		printf("token(TK_DECID)");
		show_token_args(tk);
		break;
	case TK_NULL:
		return;
	default:
		printf("unknown token <%o>", tk->tk_type);
		show_token_args(tk);
		break;
	}
	printf("\n");
}


syntax highlighted by Code2HTML, v. 0.9.1