/*
 * neoXcute.c --
 *
 * Call Unix/Tcl Environment - Serial device access from Tcl
 *
 *---------------------------------------------------------------------------
 * Copyright (C) 1991-1997 Karl Lehenbauer, All Rights Reserved
 *
 * $Id: neoXcute.c,v 1.1.1.1 1999/03/31 20:34:37 damon Exp $
 *
 */

#include <stdio.h>
#include <sys/types.h>
#include "neo.h"
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h>
#endif

#ifdef HAVE_TERMIO_H
#include <termio.h>
#endif

#ifdef HAVE_TERMIOS_H
#include <termios.h>
#include <sys/ioctl.h>
#define termio termios

#ifndef TCGETA
#  define TCGETA TIOCGETA
#endif

#ifndef TCSETA
#  define TCSETA TIOCSETAW
#endif

#ifndef TCFLSH
#  define TCFLSH TIOCFLUSH
#endif

#ifndef TCSBRK
#  define TCSBRK TIOCSBRK
#endif

#endif

#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <ctype.h>
#include <errno.h>
#include <assert.h>

#include <tcl.h>
#include "neoXcute.h"

extern char *strerror();

extern int errno;

static char cute_expectbuf[2048];

struct termio cute_orig_term_settings;
struct termio cute_single_char_term_settings;

struct termio cute_orig_line_settings;
struct termio cute_async_line_settings;

int cute_linefd = -1;
int cute_line_up = 0;

int cute_interactive_connect = 0;

int cute_noControllingTerminal = 0;

int cute_local_echo = 0;
int cute_remote_echo = 0;

int cute_debug_level = 0;

int cute_stripHighBits = 1;

FILE *cute_captureFileP = NULL;

/* index to the next byte to write in the expect buffer */
int cute_bufindex = 0;

void
ppanic(s, fileName, lineNumber)
char *s;
char *fileName;
int lineNumber;
{
    fflush (stdout);
    fflush (stderr);
    fprintf (stderr, "panic in file \"%s\", line %d\n", fileName, lineNumber);
    perror (s);
    fflush (stderr);
    panic ();
}

/* dump a buffer of a specified length, printing preamble and exit text */
void
cute_dumpbuf(char *preamble, char *buf, int len, char *exitText)
{
    fprintf (stderr, "dumpbuf - %s", preamble);
    for (; len--; buf++) {
        if (isprint (*buf)) {
            fputc (*buf, stderr);
        } else if (iscntrl (*buf)) {
            fputc ('^', stderr);
            fputc ('A'+*buf-1, stderr);
        } else {
            fprintf (stderr,"\\%03d", *buf);
        }
    }
    fprintf (stderr, "%s", exitText);
} 

/* Get user's terminal settings, save them, and create a version of
 * them modified for input without canonical input processing.  That
 * way we can timeout input, get stuff without a newline, etc, etc. */

void
cute_term_setup()
{
    if (ioctl (0, TCGETA, &cute_orig_term_settings) < 0) {
	cute_noControllingTerminal = 1;
	return;
    }

    /* structure copy */
    cute_single_char_term_settings = cute_orig_term_settings; 

#ifdef HAVE_TERMIO
    cute_single_char_term_settings.c_iflag &= ~(INLCR|IGNCR|ICRNL|IUCLC);
    cute_single_char_term_settings.c_lflag &= ~(ICANON|ECHO|ISIG);
#else
    /* cfmakeraw(&cute_single_char_term_settings); */

    cute_single_char_term_settings.c_iflag &=
	~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);

    cute_single_char_term_settings.c_oflag &= ~OPOST;

    cute_single_char_term_settings.c_lflag &=
	~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);

    cute_single_char_term_settings.c_cflag &= ~(CSIZE|PARENB);
    cute_single_char_term_settings.c_cflag |= CS8;
#endif
    cute_single_char_term_settings.c_cc[VMIN] = 0;
    cute_single_char_term_settings.c_cc[VTIME] = MCU_VTIME;
    cute_single_char_term_settings.c_cc[VINTR] = 0377;
}

/* Put the user's terminal in a mode where we read as many characters as
 * are there but it returns in a tenth of a second. */
void
cute_timeout_single_char_mode()
{
    if (cute_noControllingTerminal) panic("no controlling terminal");
    cute_single_char_term_settings.c_cc[VMIN] = 0;
    cute_single_char_term_settings.c_cc[VTIME] = MCU_VTIME;
    if (ioctl (0, TCSETA, &cute_single_char_term_settings) < 0) {
        perror ("setting timeout-single-char-mode");
    }
}

void
cute_orig_line_mode()
{
    if (ioctl (cute_linefd, TCSETA, &cute_orig_line_settings) < 0) {
        perror ("setting original line mode");
    }
}

void
cute_async_line_mode()
{
    cute_async_line_settings.c_cc[VTIME] = 0;
    cute_async_line_settings.c_cc[VMIN] = 0;
    if (ioctl (cute_linefd, TCSETA, &cute_async_line_settings) < 0) {
        perror("setting async line mode");
    }
}

/* Restore the user's terminal to its original settings */
void
cute_orig_term_mode()
{
    if (cute_noControllingTerminal) return;
    if (ioctl (0, TCSETA, &cute_orig_term_settings) < 0) {
        perror ("setting original terminal mode");
    }
}

/* Put the user's terminal in a mode where we can read one character at a time.
 */
void
cute_single_char_mode()
{
    if (cute_noControllingTerminal) panic ("no controlling terminal");
    cute_single_char_term_settings.c_cc[VMIN] = 1;
    cute_single_char_term_settings.c_cc[VTIME] = 0;
    if (ioctl (0, TCSETA, &cute_single_char_term_settings) < 0) {
        perror ("setting single character mode");
    }
}

/* get a character or return -1 if one isn't typed within a timeout
 * interval.  */

int 
cute_getachar()		/* return a char or -1 after timeout */
{
    char c;
    int returnval;

    returnval = read (0, &c, 1);
    if (returnval <= 0)
	return(-1);
    else
	return(c);
}

/* goodbye - clean up and dump out */
void
cute_comm_goodbye()
{
    cute_orig_term_mode();

    if (cute_linefd != -1) {
	cute_orig_line_mode ();
	close (cute_linefd);
	sleep (1); /* give the line a chance to drop */
	cute_linefd = -1;
	cute_line_up = 0;
    }
}

/* Cute_Nap - nap for the specified number of milliseconds */
void
Cute_Nap (int ms)
{
#ifdef HAVE_USLEEP
        usleep (ms * 1000);
#else

#  ifdef HAVE_NAP
	nap (ms);
#  else
	/* No usleep(2) or nap(2).  
	 * Your slow sends are going to be really slow. 
	 */
	sleep ((ms + 999) / 1000);
#  endif /* HAVE_NAP */
#endif /* HAVE_USLEEP */
}


/*
 *----------------------------------------------------------------------
 *
 * Cute_OpenLineCmd --
 *     Select and open the specified asynchronous communications line.
 *
 *
 *----------------------------------------------------------------------
 */
int
Cute_OpenLineCmd(clientData, interp, argc, argv)
    char *clientData;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    if (argc != 2) {
	Tcl_AppendResult (interp, "bad # arg: ", argv[0], "tty_device_name",
			  (char *)NULL);
	return TCL_ERROR;
    }

    /* free existing line if one is selected */
    if (cute_linefd != -1) cute_comm_goodbye();

#ifdef TERMIO
    if ((cute_linefd = open (argv[1], O_NDELAY | O_RDWR | O_EXCL, 0)) == -1)
#else
    if ((cute_linefd = open (argv[1], O_NONBLOCK | O_RDWR | O_EXCL, 0)) == -1)
#endif
    {
        Tcl_AppendResult (interp, argv [0], ": ", Tcl_PosixError (interp), 
                          (char *) NULL);
	return TCL_ERROR;
    }

    if (ioctl (cute_linefd, TCGETA, &cute_orig_line_settings) < 0) {
        perror ("restoring original line settings");
    }
    cute_async_line_settings = cute_orig_line_settings; /* structure copy */

#ifdef HAVE_TERMIO
    cute_async_line_settings.c_cflag = CS8 | CREAD | HUPCL | CLOCAL;
    cute_async_line_settings.c_iflag = IXOFF;
    cute_async_line_settings.c_oflag = 0;
    cute_async_line_settings.c_lflag = 0;
    cute_async_line_settings.c_line = 0;
#else
    /* cfmakeraw (&cute_async_line_settings); */

    cute_async_line_settings.c_iflag &=
	~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON);

    cute_async_line_settings.c_oflag &= ~OPOST;

    cute_async_line_settings.c_lflag &=
	~(ECHO|ECHONL|ICANON|ISIG|IEXTEN);

    cute_async_line_settings.c_cflag = CS8 | CREAD | HUPCL | CLOCAL;
#endif
    cute_async_line_settings.c_cc[VTIME] = 0;
    cute_async_line_settings.c_cc[VMIN] = 0;

    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * Cute_CloseLineCmd --
 *     Close the asynchronous communications line, if one is open.
 *
 *
 *----------------------------------------------------------------------
 */
int
Cute_CloseLineCmd(clientData, interp, argc, argv)
    char *clientData;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    if (argc != 1) {
	Tcl_AppendResult (interp, "bad # arg: ", argv[0], (char *)NULL);
	return TCL_ERROR;
    }

    cute_comm_goodbye ();
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * Cute_ParityCmd --
 *     Set the parity of the asynchronous communications line.
 *     A line must have been selected before the speed can be set.
 *
 *
 *----------------------------------------------------------------------
 */
int
Cute_ParityCmd(clientData, interp, argc, argv)
    char *clientData;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int cflag;

    cflag = cute_async_line_settings.c_cflag;

    /* if only one argument, tell them the current setting */
    if (argc == 1) {
	if (!(cflag & PARENB)) {
	    strcpy (interp->result, "none");
	    return TCL_OK;
	}
	strcpy (interp->result, (cflag & PARODD) ? "odd" : "even");
	return TCL_OK;
    }

    if (argc != 2) {
	Tcl_AppendResult (interp, "bad # arg: ", argv[0], 
			  " even|odd|none", (char *)NULL);
	return TCL_ERROR;
    }

    if (cute_linefd == -1) {
	Tcl_AppendResult (interp, argv[0], 
	                  ": you must select a device before setting parity",
	                  (char *)NULL);
	return TCL_ERROR;
    }

    cflag = (cute_async_line_settings.c_cflag & ~(CSIZE|PARENB|PARODD));

    if (STREQU (argv[1], "even")) {
	cflag |= (CS7|PARENB);
    } else if (STREQU (argv[1], "odd")) {
	cflag |= (CS7|PARENB|PARODD);
    } else if (STREQU (argv[1], "none")) {
	cflag |= CS8;
    } else {
	Tcl_AppendResult (interp, argv[0], 
	                  ": parity must be even, odd or none.", argv[1],
	                  (char *)NULL);
        return TCL_ERROR;
    }
    cute_async_line_settings.c_cflag = cflag;

    if (ioctl (cute_linefd, TCSETA, &cute_async_line_settings) < 0) {
        perror ("setting async line settings");
    }
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * Cute_SelectSpeedCmd --
 *     Set the speed of the asynchronous communications line.
 *     A line must have been selected before the speed can be set.
 *
 *
 *----------------------------------------------------------------------
 */
int
Cute_SelectSpeedCmd(clientData, interp, argc, argv)
    char *clientData;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int bps_bits;
    static unsigned bits_per_second = 0;

    if (argc == 1) {
	sprintf (interp->result, "%d", bits_per_second);
	return TCL_OK;
    }

    if (argc != 2) {
	Tcl_AppendResult (interp, "bad # arg: ", argv[0], 
			  " bits_per_second", (char *)NULL);
	return TCL_ERROR;
    }

    if (cute_linefd == -1) {
	Tcl_AppendResult (interp, argv[0], 
	                  ": you must select a device before setting speed",
	                  (char *)NULL);
	return TCL_ERROR;
    }

#ifdef EXTA
    if (STREQU (argv[1], "exta")) {
	bps_bits = EXTA;
	goto setspeed;
    }
#endif

#ifdef EXTB
    if (STREQU (argv[1], "extb")) {
	bps_bits = EXTB;
	goto setspeed;
    }
#endif

    if (!TclX_StrToUnsigned (argv[1], 0, &bits_per_second)) {
	Tcl_AppendResult (interp, argv[0], 
	                  ": unrecognizable baud rate ", argv[1],
	                  (char *)NULL);
        return TCL_ERROR;
    }

    switch (bits_per_second)
    {
#ifdef B0
	case 0: bps_bits = B0; break;
#endif

#ifdef B50
	case 50: bps_bits = B50; break;
#endif

#ifdef B75
	case 75: bps_bits = B75; break;
#endif

#ifdef B110
	case 110: bps_bits = B110; break;
#endif

#ifdef B134
	case 134: bps_bits = B134; break;
#endif

#ifdef B150
	case 150: bps_bits = B150; break;
#endif

#ifdef B200
	case 200: bps_bits = B200; break;
#endif

#ifdef B300
	case 300: bps_bits = B300; break;
#endif

#ifdef B600
	case 600: bps_bits = B600; break;
#endif

#ifdef B1200
	case 1200: bps_bits = B1200; break;
#endif

#ifdef B1800
	case 1800: bps_bits = B1800; break;
#endif

#ifdef B2400
	case 2400: bps_bits = B2400; break;
#endif

#ifdef B4800
	case 4800: bps_bits = B4800; break;
#endif

#ifdef B7200
	case 7200: bps_bits = B7200; break;
#endif

#ifdef B9600
	case 9600: bps_bits = B9600; break;
#endif

#ifdef B14400
	case 14400: bps_bits = B14400; break;
#endif

#ifdef B19200
	case 19200: bps_bits = B19200; break;
#endif

#ifdef B28800
	case 28800: bps_bits = B28800; break;
#endif

#ifdef B38400
	case 38400: bps_bits = B38400; break;
#endif

#ifdef B57600
	case 57600: bps_bits = B57600; break;
#endif

#ifdef B115200
	case 115200: bps_bits = B115200; break;
#endif

#ifdef B230400
	case 230400: bps_bits = B230400; break;
#endif

	default:
            Tcl_AppendResult (interp, argv[0], ": unsupported bit rate ", 
		              argv[1], (char *)NULL);
	    return TCL_ERROR;
    }
  setspeed:

#ifdef TERMIO
    cute_async_line_settings.c_cflag = (cute_async_line_settings.c_cflag & ~(CBAUD)) | bps_bits;
#else
    /* cute_async_line_settings.c_ispeed = cute_async_line_settings.c_ospeed = bits_per_second; */
#ifdef sun
    cfsetispeed (&cute_async_line_settings, bps_bits);
    cfsetospeed (&cute_async_line_settings, bps_bits);
#else
    cfsetspeed (&cute_async_line_settings, bps_bits);
#endif
#endif
    cute_line_up = 1;
    return TCL_OK;
}

int
cute_isline_up(interp, cmd)
Tcl_Interp *interp;
char *cmd;
{
    if (cute_line_up) return TCL_OK;
    Tcl_AppendResult (interp, cmd, ": line or speed not selected", 
		      (char *)NULL);
    return TCL_ERROR;
}


/*
 *----------------------------------------------------------------------
 *
 * Cute_SendCmd --
 *     Sends the specified text to the previously selected async line.
 *
 *
 *----------------------------------------------------------------------
 */
int
Cute_SendCmd(clientData, interp, argc, argv)
    char *clientData;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int nocr = 0;
    static char newline = '\r';

    if ((argc < 2) || (argc > 3)) {
	err:
	Tcl_AppendResult (interp, "bad # arg: ", argv[0], " text [nonewline]",
			 (char *)NULL);
	return TCL_ERROR;
    }

    if (argc == 3) {
	if (!STREQU (argv[2], "nonewline")) goto err;
	nocr = 1;
    }

    if (cute_isline_up (interp, argv[0]) == TCL_ERROR) return TCL_ERROR;

    if (write(cute_linefd, argv[1], strlen(argv[1])) < 0) {
        Tcl_AppendResult (interp, argv [0], ": ", Tcl_PosixError (interp), 
                          (char *) NULL);
	return TCL_ERROR;
    }

    if (!nocr) {
	write (cute_linefd, &newline, 1);
    }
    return TCL_OK;
}

void
cute_disconnect()
{
    cute_interactive_connect = 0;
}


/*
    capture filehandle
    capture off
*/

void
cute_capture(buf, len)
char *buf;
int len;
{
    static previous_was_cr = 0;
    char c;
    static char cr = '\r';

    if (cute_captureFileP == NULL) return;

    while (len--)
    {
	c = *buf++;
	if (previous_was_cr)
	{
	    if (c != '\n')
		fputc (cr, cute_captureFileP);
	    previous_was_cr = 0;
	}
	if (c == '\r')
	{
	    previous_was_cr = 1;
	}
	else
	{
	    fputc (c, cute_captureFileP);
	}
    }
}


/*
 *----------------------------------------------------------------------
 *
 * Cute_CaptureCmd --
 *     Enable capturing of data received to the specified file
 *     descriptor, or turn it off.
 *
 *
 *----------------------------------------------------------------------
 *
 * ??? needs to be a filehandle
 */
int
Cute_CaptureCmd(clientData, interp, argc, argv)
    char *clientData;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    if (argc != 2) {
	Tcl_AppendResult (interp, "bad arg: ", argv[0], " file_handle|off", 
			  (char *)NULL);
	return TCL_ERROR;
    }
    if (STREQU (argv[1], "off")) {
	cute_captureFileP = NULL;
	return TCL_OK;
    }

    if (Tcl_GetOpenFile (interp, argv[1], 1, 1, &cute_captureFileP) != TCL_OK) {
	cute_captureFileP = NULL;
	return TCL_ERROR;
    }
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * Cute_ConnectCmd --
 *     Establish an interactive connection between the user on
 *     stdin/stdout and the previously selected async line.
 *
 *
 *----------------------------------------------------------------------
 */
int
Cute_ConnectCmd(clientData, interp, argc, argv)
    char *clientData;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int c;
    char rcvbuf[1024];

    if (cute_isline_up(interp, argv[0]) == TCL_ERROR) return TCL_ERROR;

    cute_interactive_connect = 1;

    cute_timeout_single_char_mode ();
    cute_async_line_mode ();
    while (cute_interactive_connect)
    {
	int returnval;
        fd_set read_fdset;
        struct timeval timeout;

        FD_ZERO (&read_fdset);
        FD_SET (0, &read_fdset);
        FD_SET (cute_linefd, &read_fdset);
        timeout.tv_sec = 60;
        timeout.tv_usec = 0;

        returnval = (select(cute_linefd + 1, &read_fdset, NULL, NULL, &timeout));
        if (returnval == 0) {printf("*timeout* "); fflush(stdout); }
        if (FD_ISSET(0, &read_fdset)) {
	    char myc;
	    if ((c = cute_getachar ()) < 0) {
                printf("cute_getachar failed\n"); fflush(stdout);
                continue;
            }
	    myc = (char)c;

	    /* 28 is a control-backslash, do backslash processing */
	    if (myc == 28) {
		char commandBuffer[32];

		if (Tcl_Eval (interp, "cuteprompt") == TCL_ERROR) {
		    printf("Error in prompt procedure: %s\n", interp->result);
                    Tcl_ResetResult(interp);
		}
		fflush (stdout);
		cute_single_char_mode ();
		c = cute_getachar ();
		cute_timeout_single_char_mode ();
		if (isprint (c) && (c != ' '))
		    sprintf (commandBuffer, "cute-command-key-%c", c);
		else
		    sprintf(commandBuffer, "cute-command-key-%03o", c);

		if (Tcl_Eval (interp, commandBuffer) == TCL_ERROR) {
		    puts(interp->result);
		    if (c == 'c') {
			printf("Ending interactive mode due to control-backslash-c proc failure.\n");
			cute_disconnect();
		    } else {
			printf("control-backslash ? for help...\n");
		    }
		}
	    }

	    if (cute_local_echo) write(1, &myc, 1);
	    write(cute_linefd, &myc, 1);
	}
        if (FD_ISSET (cute_linefd, &read_fdset)) {
	    returnval = read (cute_linefd, &rcvbuf[0], sizeof(rcvbuf));
	    if (returnval > 0) {
	        if (cute_stripHighBits) {
		    int i;
		    for (i = 0; i < returnval; i++) {
		        rcvbuf[i] &= 0x7f;
		    }
	        }
	        write (1, &rcvbuf[0], returnval);
	        cute_capture (&rcvbuf[0], returnval);
            }
	}
    }
    cute_disconnect ();
    cute_orig_term_mode ();
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * Cute_DisconnectCmd --
 *     Disconnect an established interactive connection between the 
 *     user on stdin/stdout and the previously selected async line.
 *
 *
 *----------------------------------------------------------------------
 */
int
Cute_DisconnectCmd(clientData, interp, argc, argv)
    char *clientData;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    cute_disconnect ();
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * Cute_HangupCmd --
 *     Hangup the previously selected async line.
 *
 *
 *----------------------------------------------------------------------
 */
int
Cute_HangupCmd(clientData, interp, argc, argv)
    char *clientData;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    cute_comm_goodbye ();
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * Cute_SlowSendCmd --
 *     Send the specified text to the previously selected async line,
 *     with tenth-of-a-second delays between each character.
 *
 *----------------------------------------------------------------------
 */
int
Cute_SlowSendCmd(clientData, interp, argc, argv)
    char *clientData;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int j, len;
    int nocr = 0;
    static char newline = '\r';

    cute_async_line_mode();
    if ((argc < 2) || (argc > 3)) {
	err:
	Tcl_AppendResult (interp, "bad # arg: ", argv[0], " text [nonewline]",
			  (char *)NULL);
	return TCL_ERROR;
    }

    if (argc == 3) {
	if (strcmp (argv[2], "nonewline") != 0) goto err;
	nocr = 1;
    }

    if (cute_isline_up (interp, argv[0]) == TCL_ERROR) return TCL_ERROR;

    len = strlen (argv[1]);
    for (j = 0; j < len; j++) {
	write (cute_linefd, &argv[1][j], 1);
	Cute_Nap (100);
    }

    if (!nocr) {
	write (cute_linefd, &newline, 1);
	Cute_Nap (100);
    }
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * Cute_GetLineCmd --
 *     Get the next line from the expect buffer, or read for the
 *     specified number of seconds or until a line comes in.
 *
 *     Set the text into the specified variable, if any was
 *     received, and return "0" if there was only a partial line,
 *     or no line at all (no CR was found in the buffer), or 
 *     return "1" if there was a whole line.
 *
 *     If only a partial line was found, it is still written into
 *     the specified variable, although it remains in the expect
 *     buffer.
 *
 *     A timeout of zero is valid.  getline will read any data that
 *     came in, but not wait.  If no timeout is specified, 0 is
 *     assumed.
 *
 *----------------------------------------------------------------------
 */
int
Cute_GetLineCmd(clientData, interp, argc, argv)
    char *clientData;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int newlineIndex;
    int timeout_seconds = 0;
    int second;

    if ((argc < 2) || (argc > 3)) {
	Tcl_AppendResult(interp, "bad # arg: ", argv[0], 
			 " varName [timeout]", (char *)NULL);
	return TCL_ERROR;
    }

    if (cute_isline_up (interp, argv[0]) == TCL_ERROR) return TCL_ERROR;

    if (argc == 3) {
        if (!TclX_StrToInt (argv[2], 10, &timeout_seconds)) {
	    Tcl_AppendResult(interp, "bad timeout arg: ", argv[0], 
			     " varName ", argv[2], (char *)NULL);
	    return TCL_ERROR;
        }
	if (timeout_seconds < 0) timeout_seconds = 0;
    }

    /* && !tclGotErrorSignal */
    for (second = -1; (second <= timeout_seconds); second++) 
    {
	int returnval;

	/* if the first pass and nothing is in the buffer, skip the pass */
	if ((second == -1) && (cute_bufindex == 0)) continue;

	/* if the first or second pass, don't sleep, otherwise sleep a second */
	if (second > 0) sleep(1);

	/* if the second pass or later, try to read the buffer */
	if (second >= 0)
	{
	    returnval = read(cute_linefd, &cute_expectbuf[cute_bufindex], 
			     sizeof(cute_expectbuf) - cute_bufindex);
	    if (returnval == 0) continue;
            if (returnval == -1) {
                if (errno == EAGAIN) continue;
                failppanic(returnval == -1);
            }

            if (cute_debug_level > 3)
                cute_dumpbuf("\ngetline read \"", &cute_expectbuf[cute_bufindex], 
			returnval, "\"\n");

	    if (cute_remote_echo) write(1, &cute_expectbuf[cute_bufindex], returnval);
	    cute_capture(&cute_expectbuf[cute_bufindex], returnval);
	    cute_bufindex += returnval;
	    cute_expectbuf[cute_bufindex] = '\0';
	}

	/* Find the first carriage return.
	 * If one is found, return all text up to it
	 */

        if (cute_debug_level > 5)
            cute_dumpbuf("\ngetline scanning \"", &cute_expectbuf[0], cute_bufindex, "\"\n");

	for (newlineIndex = 0; newlineIndex < cute_bufindex; newlineIndex++)
	{
	    if (cute_expectbuf[newlineIndex] == '\r')
	    {
		cute_expectbuf[newlineIndex] = '\0';
		if (Tcl_SetVar(interp, argv[1], cute_expectbuf, TCL_LEAVE_ERR_MSG)
		   == NULL)
		    return TCL_ERROR;

                /* trash the next character if it's a linefeed */
                if (newlineIndex+1 < cute_bufindex && cute_expectbuf[newlineIndex+1] == '\n')
                    newlineIndex++;

		cute_bufindex = cute_bufindex - newlineIndex - 1;

                assert(cute_bufindex >= 0);
                if (cute_bufindex != 0)
                   memcpy(&cute_expectbuf[0], &cute_expectbuf[newlineIndex+1], cute_bufindex);
		cute_expectbuf[cute_bufindex] = '\0';

                if (cute_debug_level > 6)
                    cute_dumpbuf("\ncopied down \"", &cute_expectbuf[0], cute_bufindex,
                        "\"\n");

                strcpy(interp->result, "1");
		return TCL_OK;

	    }
	}

    }
/* need a better way to handle this....
    if (tclGotErrorSignal) return TCL_OK;
*/

    strcpy(interp->result, "0");
    if (cute_bufindex == 0) return TCL_OK;
    /* no CR was found, set var to contain everything in the buffer so far */
    if (Tcl_SetVar(interp, argv[1], cute_expectbuf, TCL_LEAVE_ERR_MSG) == NULL)
	return TCL_ERROR;
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * Cute_ExpectCmd --
 *     Wait for a timeout period, looking for one or more strings
 *     to match from the data being read from the previously
 *     selected async line.
 *
 *----------------------------------------------------------------------
 */
/*
expect timeout_seconds timeout_action match_text action [match_text action...]
*/
int
Cute_ExpectCmd(clientData, interp, argc, argv)
    char *clientData;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int timeout_seconds;
    int i;
    int searchindex;
    int endindex = cute_bufindex;
    int matchtext_index;
    int second;

    /* arg count must be 5 or more, and odd */
    if ((argc < 5) || ((argc & 1) == 0)) {
	Tcl_AppendResult (interp, "bad # arg: ", argv[0],
	    ": timeout_secs timeout_action text action [text action..]",
	    (char *)NULL);
	return TCL_ERROR;
    }

    if (cute_isline_up(interp, argv[0]) == TCL_ERROR) return TCL_ERROR;


    if (cute_debug_level > 0)
    {
	int debugIndex;

        fprintf(stderr, "Expect (%s) ", argv[1]);
        for (debugIndex = 3; debugIndex < argc; debugIndex += 2)
            fprintf(stderr, "\"%s\" ", argv[debugIndex]);
        fprintf(stderr, "\n");
    }

    if (!TclX_StrToInt (argv[1], 10, &timeout_seconds)) {
        Tcl_AppendResult (interp, "bad timeout value: ", argv[0],
            ": ", argv[1], (char *)NULL);
        return TCL_ERROR;
    }
    if (timeout_seconds <= 0) timeout_seconds = 1;

    /* Run through the loop for the specified number of seconds.
     * The first time, just scan the existing buffer for a match without
     * a read.  The second time (the first second), don't sleep, just
     * read -- it might have already come in.  From then on, sleep
     * a second between passes */

    /* && !tclGotErrorSignal */
    for (second = -1; (second <= timeout_seconds); second++) 
    {
	int returnval;

	/* if the first pass and nothing is in the buffer, skip the pass */
	if ((second == -1) && (cute_bufindex == 0)) continue;

	/* if the first or second pass, don't sleep, otherwise sleep a second */
	if (second > 0) sleep(1);

	/* if the second pass or later, try to read the buffer */
	if (second >= 0)
	{
	    returnval = read(cute_linefd, &cute_expectbuf[cute_bufindex], 
			     sizeof(cute_expectbuf) - cute_bufindex);
	    if (returnval == 0) continue;
            if (returnval == -1) {
                if (errno == EAGAIN) continue;
                failppanic(returnval == -1);
            }


            if (cute_debug_level > 1)
                cute_dumpbuf("\nread \"", &cute_expectbuf[cute_bufindex], returnval, "\"\n");

	    if (cute_remote_echo) write(1, &cute_expectbuf[cute_bufindex], returnval);
	    cute_capture(&cute_expectbuf[cute_bufindex], returnval);
	    endindex = cute_bufindex + returnval;
	    cute_expectbuf[endindex] = '\0';
	}

        if (cute_debug_level > 2)
            cute_dumpbuf("\nscanning \"", cute_expectbuf, endindex, "\"\n");

	for (searchindex = 0; searchindex < endindex; searchindex++) 
	{
	    for (matchtext_index = 3; matchtext_index < argc; matchtext_index += 2) 
	    {
		if ((argv[matchtext_index][0] == cute_expectbuf[searchindex]) && (strncmp(argv[matchtext_index], &cute_expectbuf[searchindex], strlen(argv[matchtext_index])) == 0)) 
		{
                    int nextByte;

                    if (cute_debug_level > 0)
                        fprintf(stderr, "got \"%s\"\n", argv[matchtext_index]);

                    /* copy any remaining data down to the bottom of the buffer
                     */

                    nextByte = searchindex + strlen(argv[matchtext_index]);
                    if (nextByte < endindex)
                    {
                        cute_bufindex = endindex - nextByte;
                        memcpy(&cute_expectbuf[0], &cute_expectbuf[nextByte], cute_bufindex);
                        cute_expectbuf[cute_bufindex] = '\0';
                    }                        

                    if (cute_debug_level > 6)
                        cute_dumpbuf("\nleft \"", &cute_expectbuf[0], cute_bufindex, "\"\n");

		    return Tcl_Eval(interp, argv[matchtext_index+1]);
		}
	    }
	}
	/* search backwards through buffer for first newline, if
	 * one is found, copy everything above it down to the
	 * front of the buffer and update cute_bufindex to point to
	 * the first following byte, otherwise update cute_bufindex to
	 * point to where we wrote a null byte, i.e., to pick
	 * up where the last read left off,
	 */
	 for (i = endindex - 1; i >= 0; i--) 
	 {
	    if (cute_expectbuf[i] == '\n')
	    {
		int copylen = endindex - i - 1;
		memcpy(&cute_expectbuf[0], &cute_expectbuf[i+1], copylen);
		cute_bufindex = copylen;
		goto reloop;
	    }
	}
	cute_bufindex = endindex;  /* loop didn't find a newline */
	reloop: ;
    }
/*
    if (tclGotErrorSignal) return TCL_OK;
*/

    /* if we got here we timed out without getting what we wanted */
    return Tcl_Eval(interp, argv[2]);
}


/*
 *----------------------------------------------------------------------
 *
 * Cute_FlushLineCmd --
 *     Flush input, output, or both, on the previously selected
 *     async line.
 *
 *----------------------------------------------------------------------
 */
int
Cute_FlushLineCmd(clientData, interp, argc, argv)
    char *clientData;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    int flusharg;

    if (argc != 2)
    {
	err:
	Tcl_AppendResult (interp, "bad # arg: ", argv[0],
			  "input|output|both", (char *)NULL);
	return TCL_ERROR;
}

    switch(*argv[1])
    {
	case 'b': flusharg = 2; break;
	case 'i': flusharg = 0; break;
	case 'o': flusharg = 1; break;
	default: goto err;
    }

    if (cute_isline_up(interp, argv[0]) == TCL_ERROR) return TCL_ERROR;

    if (ioctl(cute_linefd, TCFLSH, (struct termio *) flusharg) < 0) {
        perror("flushing remote line");
    }
    return TCL_OK;
}


/*
 *----------------------------------------------------------------------
 *
 * Cute_BreakLineCmd --
 *     Send a break to the previously selected async line.
 *
 *----------------------------------------------------------------------
 */
int
Cute_BreakLineCmd(clientData, interp, argc, argv)
    char *clientData;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    if (cute_isline_up(interp, argv[0]) == TCL_ERROR) return TCL_ERROR;

    if (ioctl(cute_linefd, TCSBRK, (struct termio *) 0) < 0) {
        perror("unable to send break");
    }
    return TCL_OK;
}

struct cuteCommandStruct {
    const char *name;
    int (*function)();
};

struct cuteCommandStruct cuteCommands[] = {
    {"breakline", Cute_BreakLineCmd},
    {"capture", Cute_CaptureCmd},
    {"close", Cute_CloseLineCmd},
    {"connect", Cute_ConnectCmd},
    {"disconnect", Cute_DisconnectCmd},
    {"expect", Cute_ExpectCmd},
    {"flushline", Cute_FlushLineCmd},
    {"getline", Cute_GetLineCmd},
    {"hangup", Cute_HangupCmd},
    {"open", Cute_OpenLineCmd},
    {"parity", Cute_ParityCmd},
    {"send", Cute_SendCmd},
    {"slow_send", Cute_SlowSendCmd},
    {"speed", Cute_SelectSpeedCmd},
    {(char *)NULL, NULL}
};

int
Cute_Cmd(clientData, interp, argc, argv)
    char *clientData;
    Tcl_Interp *interp;
    int argc;
    char **argv;
{
    struct cuteCommandStruct *cmdptr = cuteCommands;

    if (argc < 2) {
	Tcl_AppendResult (interp, "bad # arg: ", argv[0], " subcommand ...",
	    (char *)NULL);
	return TCL_ERROR;
    }

    for (cmdptr = cuteCommands; cmdptr->name != NULL; cmdptr++) {
	if (STREQU (argv[1], cmdptr->name)) {
	    return cmdptr->function ((char *)NULL, interp, argc - 1, &argv[1]);
	}
    }

    Tcl_AppendResult(interp, "bad subcommand: '", argv[1], 
	     "', must be one of  breakline, capture, close, connect, ",
	     "disconnect, expect, flushline, getline, hangup, ",
	     "open, parity, send, slow_send or speed",
	     (char *)NULL);
    return TCL_ERROR;
}


/*
 *----------------------------------------------------------------------
 *
 * Cute_InitComm --
 *     Initialize the comm stuff, and add the cute commands to the
 *     specified interpreter.
 *
 *----------------------------------------------------------------------
 */
int
Cute_InitComm(interp)
Tcl_Interp *interp;
{
    cute_term_setup();
    cute_expectbuf[0] = '\0';

    if (Tcl_LinkVar (interp, 
		     "cute_local_echo",
		     (char *)&cute_local_echo,
		     TCL_LINK_BOOLEAN) == TCL_ERROR) return TCL_ERROR;

    if (Tcl_LinkVar (interp,
		     "cute_remote_echo",
		     (char *)&cute_remote_echo,
		     TCL_LINK_BOOLEAN) == TCL_ERROR) return TCL_ERROR;

    if (Tcl_LinkVar (interp,
                     "cute_strip_high_bits",
		     (char *)&cute_stripHighBits,
		     TCL_LINK_BOOLEAN) == TCL_ERROR) return TCL_ERROR;

    if (Tcl_LinkVar (interp,
		     "cute_debug_level",
		     (char *)&cute_debug_level,
		     TCL_LINK_INT) == TCL_ERROR) return TCL_ERROR;

    Tcl_CreateCommand  (interp, 
			"cute",
			Cute_Cmd,
			(ClientData)NULL,
			(void (*)())NULL);

    return TCL_OK;
}
