/*
 * txCommands.c --
 *
 * 	Reads commands from devices and sends them to the window package,
 *	which sends them to a particular window.
 *
 *     *********************************************************************
 *     * Copyright (C) 1985, 1990 Regents of the University of California. *
 *     * Permission to use, copy, modify, and distribute this              *
 *     * software and its documentation for any purpose and without        *
 *     * fee is hereby granted, provided that the above copyright          *
 *     * notice appear in all copies.  The University of California        *
 *     * makes no representations about the suitability of this            *
 *     * software for any purpose.  It is provided "as is" without         *
 *     * express or implied warranty.  Export of this software outside     *
 *     * of the United States of America may require an export license.    *
 *     *********************************************************************
 */

#ifndef lint
static char rcsid[] __attribute__ ((unused)) ="$Header: /usr/cvsroot/magic-8.0/textio/txCommands.c,v 1.6 2009/09/11 16:09:49 tim Exp $";
#endif  /* not lint */

#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
#include <signal.h>
#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

#include "tcltk/tclmagic.h"
#include "utils/magic.h"
#include "textio/textio.h"
#include "utils/geometry.h"
#include "textio/txcommands.h"
#include "textio/textioInt.h"
#include "utils/macros.h"
#include "windows/windows.h"
#include "tiles/tile.h"
#include "utils/hash.h"
#include "database/database.h"
#include "dbwind/dbwind.h"
#include "drc/drc.h"
#include "utils/signals.h"
#include "graphics/graphics.h"
#include "utils/dqueue.h"
#include "utils/malloc.h"
#include "utils/utils.h"
#ifdef SCHEME_INTERPRETER
#include "lisp/lisp.h"
#endif

/* C99 compat */
#include "windows/windows.h"
#include "utils/main.h"

/* Turning this flag on prints out input events and commands as they
 * are processed.
 */

bool TxDebug = FALSE;

/* A mask of the file descriptors for all input devices.
 */
static fd_set txInputDescriptors;
/* Used for the 'nfds' field for select() syscall, the highest
 * fd number that is set in the txInputDescriptors bitmask.
 * Don't forget to plus 1 for select().
 */
static int txInputDescriptors_nfds;

#define   TX_MAX_INPUT_DEVICES       20
/* A table of all input devices.
 */
static txInputDevRec txInputDevice[TX_MAX_INPUT_DEVICES];
static int txLastInputEntry;

/* The current point -- reset by the 'setpoint' command and for each
 * interactive command.  Calls to TxClearPoint clear previous setpoints,
 *
 * Each input point is associated with a window, as windows may use
 * different coordinate systems.
 *
 * Also, keep around the last input event.
 */
static bool txHaveCurrentPoint = FALSE;
static Point txCurrentPoint = {100, 100};
static int txCurrentWindowID = WIND_UNKNOWN_WINDOW;
TxInputEvent txLastEvent;


/* Input queues.  We have an input queue for low-level input events, and
 * a queue for assembled interactive commands and file commands.  Also, there
 * are free lists for these structures.
 */

#ifdef MAGIC_WRAPPER
extern void send_interpreter(char *);

char *TxBuffer;			/* For use with special stdin processing */
unsigned char TxInputRedirect;	/* For redirecting Tk events to the terminal */
#endif

DQueue txInputEvents;
DQueue txFreeEvents;
int txNumInputEvents;	/* Number of events recieved by Magic so far. */

DQueue txFreeCommands;

/* A zero time value, used for doing a poll via 'select()'.
 */

static struct timeval txZeroTime;


/* Mask of buttons down, as of the last command in the queue (not the last
 * command executed).
 */
int TxCurButtons = 0;

/*
 * Commands are numbered sequentially starting at zero.  This number says
 * which command we are collecting or executing.
 */

int TxCommandNumber = 0;

/*
 * The "cmd" structure is shared by lisp and eval using this pointer
 */
static TxCommand *lisp_cur_cmd = NULL;

/*
 * ----------------------------------------------------------------------------
 *
 * FD_IsZero --
 * FD_OrSet --
 * FD_MaxFd --
 *      Returns the highest 'fd' in the mask for select(2) nfds argument, or
 *      -1 when 'fdmask' is empty.
 *
 *	Routines for manipulating set of file descriptors.
 *
 * ----------------------------------------------------------------------------
 */

bool
FD_IsZero(
    const fd_set *fdmask)
{
    int fd;
    for (fd = 0; fd < FD_SETSIZE; fd++)
	if (FD_ISSET(fd, fdmask)) return FALSE;
    return TRUE;
}

void
FD_OrSet(
    const fd_set *fdmask,
    fd_set *dst)
{
    int fd;
    for (fd = 0; fd < FD_SETSIZE; fd++)
	if (FD_ISSET(fd, fdmask)) FD_SET(fd, dst);
}

/* A bitmask find max bit set operation */
int
FD_MaxFd(
    const fd_set *fdmask)
{
    int fd;
    for (fd = FD_SETSIZE-1; fd >= 0; fd--) /* backwards */
	if (FD_ISSET(fd, fdmask)) return fd;
    return -1;
}

/* update txInputDescriptors_nfds with the correct value
 * call this everytime txInputDescriptors is modified
 */
static void
TxInputDescriptorsRecalc(void)
{
    int nfds = FD_MaxFd(&txInputDescriptors);
    txInputDescriptors_nfds = (nfds >= 0) ? nfds : 0;
}

/*
 * ----------------------------------------------------------------------------
 *
 * TxReleaseButton --
 *
 *	Pretend that a certain button is up, even though we think that it is
 *	down.  Used only in rare circumstances, such as when SunWindows
 *	reads a button push behind our back.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

void
TxReleaseButton(
    int but)
{
    TxCurButtons &= ~but;
}

/*
 * ----------------------------------------------------------------------------
 *
 * TxPrintEvent --
 *
 *	Print an event's contents to stderr.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	Text appears on the terminal.
 *
 * ----------------------------------------------------------------------------
 */

void
TxPrintEvent(
    TxInputEvent *event)
{
    TxError("Input event at %p\n    ", (void *) event);
    if (event->txe_button == TX_EOF) {
	TxError("EOF event");
    } else if (event->txe_button == TX_BYPASS) {
	TxError("Bypass event");
    } else if (event->txe_button == TX_CHARACTER) {
	char *strc = MacroName(event->txe_ch);
	TxError("Character '%s'", strc);
	freeMagic(strc);
    } else {
	switch (event->txe_button) {
	    case TX_LEFT_BUTTON: {TxError("Left button"); break;};
	    case TX_MIDDLE_BUTTON: {TxError("Middle button"); break;};
	    case TX_RIGHT_BUTTON: {TxError("Right button"); break;};
	    default: {TxError("UNKNOWN button"); break;};
	}
	switch (event->txe_buttonAction) {
	    case TX_BUTTON_UP: {TxError(" up"); break;};
	    case TX_BUTTON_DOWN: {TxError(" down"); break;};
	    default: {TxError(" UNKNOWN-ACTION"); break;};
	}
    }
    TxError(" at (%d, %d)\n    Window: ", event->txe_p.p_x, event->txe_p.p_y);
    switch (event->txe_wid) {
	case WIND_UNKNOWN_WINDOW: {TxError("unknown\n"); break;};
	case WIND_NO_WINDOW: {TxError("none\n"); break;};
	default: {TxError("%d\n", event->txe_wid); break;};
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * TxPrintCommand --
 *
 *	Print a command's contents to stderr.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	Text appears on the terminal.
 *	Repaired so that unprintable text does NOT appear (March 2000, Tim)
 *
 * ----------------------------------------------------------------------------
 */

void
TxPrintCommand(
    TxCommand *cmd)
{
    int i, j;
    char TxTemp[200];

    TxError("Command at %p\n    ", (void *) cmd);
    if (cmd->tx_button == TX_CHARACTER) {
	TxError("Text command with %d words: ", cmd->tx_argc);
	for (i = 0; i < cmd->tx_argc; i++) {
	    for (j = 0; cmd->tx_argv[i][j] && (j < 199); j++)
	       TxTemp[j] = isprint(cmd->tx_argv[i][j]) ? cmd->tx_argv[i][j] : '*';
	    TxTemp[j] = '\0';
	    TxError(" \"%s\"", TxTemp);
	}
    } else {
	switch (cmd->tx_button) {
	    case TX_LEFT_BUTTON: {TxError("Left button"); break;};
	    case TX_MIDDLE_BUTTON: {TxError("Middle button"); break;};
	    case TX_RIGHT_BUTTON: {TxError("Right button"); break;};
	    default: {TxError("UNKNOWN button"); break;};
	}
	switch (cmd->tx_buttonAction) {
	    case TX_BUTTON_UP: {TxError(" up"); break;};
	    case TX_BUTTON_DOWN: {TxError(" down"); break;};
	    default: {TxError(" UNKNOWN-ACTION"); break;};
	}
    }
    TxError(" at (%d, %d)\n    Window: ", cmd->tx_p.p_x, cmd->tx_p.p_y);
    switch (cmd->tx_wid) {
	case WIND_UNKNOWN_WINDOW: {TxError("unknown\n"); break;};
	case WIND_NO_WINDOW: {TxError("none\n"); break;};
	default: {TxError("%d\n", cmd->tx_wid); break;};
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * TxNewEvent --
 *
 *	Get a new event, ready to be filled in.
 *
 * Results:
 *	A pointer to a new event.
 *
 * Side Effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

TxInputEvent *
TxNewEvent(void)
{
    TxInputEvent *event;
    event = (TxInputEvent *) DQPopFront(&txFreeEvents);
    if (event == NULL) event = (TxInputEvent *) mallocMagic(sizeof(TxInputEvent));
    event->txe_button = TX_CHARACTER;
    event->txe_buttonAction = TX_BUTTON_UP;
    event->txe_wid = WIND_UNKNOWN_WINDOW;
    event->txe_p.p_x = GR_CURSOR_X;
    event->txe_p.p_y = GR_CURSOR_Y;
    event->txe_ch = 0;
    return event;
}

/*
 * ----------------------------------------------------------------------------
 *
 * TxAddEvent --
 *
 *	Add a new event into our input queue.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

void
TxAddEvent(
    TxInputEvent *event)
{
    ASSERT(event != NULL, "TxAddEvent");
    DQPushRear(&txInputEvents, (ClientData) event);
    txNumInputEvents++;
}

/*
 * ----------------------------------------------------------------------------
 *
 * TxFreeEvent --
 *
 *	Free an event.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

void
TxFreeEvent(
    TxInputEvent *event)
{
    ASSERT(event != NULL, "TxFreeEvent");
    DQPushRear(&txFreeEvents, (ClientData) event);
}

/*
 * ----------------------------------------------------------------------------
 *
 * TxNewCommand --
 *
 *	Get a new command, ready to be filled in.
 *
 * Results:
 *	A pointer to a new command.
 *
 * Side Effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

TxCommand *
TxNewCommand(void)
{
    TxCommand *command;
    command = (TxCommand *) DQPopFront(&txFreeCommands);
    if (command == NULL)
	command = (TxCommand *) mallocMagic(sizeof(TxCommand));
    command->tx_button = TX_CHARACTER;
    return command;
}


/*
 * ----------------------------------------------------------------------------
 *
 * TxFreeCommand --
 *
 *	Free a command.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	None.
 *
 * ----------------------------------------------------------------------------
 */

void
TxFreeCommand(
    TxCommand *command)
{
    ASSERT(command != NULL, "TxFreeCommand");
#ifdef MAGIC_WRAPPER
    /* No Tx Queue in MAGIC_WRAPPER; call is just to free memory */
    freeMagic((char *)command);
#else
    DQPushRear(&txFreeCommands, (ClientData) command);
#endif
}

/*
 * ----------------------------------------------------------------------------
 * TxAddInputDevice --
 * TxAdd1InputDevice --
 *
 *	Add a device which is able to deliever commands to magic.
 *	The caller must ensure that SIGIO signals will be send whenever
 *	this file descriptor becomes ready.
 *
 *	The usual form of inputProc is:
 *
 *		proc(fd, cdata)
 *		    int fd;	-- file descriptor that is ready
 *		    ClientData; -- ClientData as passed to TxAddInputDevice()
 *		{
 *		    TxInputEvent *event;
 *		    event = TxNewEvent();
 *		    -- read fd here, and fill in event
 *		    TxAddEvent(event);
 *		    -- might do this more than once for multiple events
 *		}
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Modifies internal tables.
 * ----------------------------------------------------------------------------
 */

void
TxAddInputDevice(
    const fd_set *fdmask,	/* A mask of file descriptors that this
				 * device will handle.
				 */
    const cb_textio_input_t inputProc,
				/* A routine to call.  This routine will
				 * be passed a single file descriptor that
				 * is ready, and should read that file and
				 * add events(s) by calling TxNewEvent()
				 * followed by TxAddEvent().
				 */
    ClientData cdata)		/* Will be passed back to the proc whenever
				 * it is called.
				 */
{
    TxDeleteInputDevice(fdmask);
    if (txLastInputEntry == TX_MAX_INPUT_DEVICES)
    {
	TxError("Too many input devices.\n");
	return;
    }

    txInputDevice[txLastInputEntry].tx_fdmask = *fdmask;
    txInputDevice[txLastInputEntry].tx_inputProc = inputProc;
    txInputDevice[txLastInputEntry].tx_cdata = cdata;
    txLastInputEntry++;

    FD_OrSet(fdmask, &txInputDescriptors);
    TxInputDescriptorsRecalc();
}

void
TxAdd1InputDevice(
    int fd,
    const cb_textio_input_t inputProc,
    ClientData cdata)
{
    fd_set fs;
    ASSERT(fd >= 0 && fd < FD_SETSIZE, "fd>=0&&fd<FD_SETSIZE");
    if (fd < 0 || fd >= FD_SETSIZE)
    {
	TxError("WARNING: TxAdd1InputDevice(fd=%d) called with fd out of range 0..%d\n", fd, FD_SETSIZE-1);
	return; /* allowing things to continue is UB */
    }
    FD_ZERO(&fs);
    FD_SET(fd, &fs);
    TxAddInputDevice(&fs, inputProc, cdata);
}

/*
 * ----------------------------------------------------------------------------
 * TxDeleteInputDevice --
 *
 *	Delete an input device from our tables.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	modifies internal tables.
 * ----------------------------------------------------------------------------
 */

void
TxDeleteInputDevice(
    const fd_set *fdmask)	/* A mask of file descriptors that are
				 * no longer active.
				 */
{
    int fd;
    for (fd = 0; fd < FD_SETSIZE; fd++)
	if (FD_ISSET(fd, fdmask)) TxDelete1InputDevice(fd);
}

void
TxDelete1InputDevice(
    int fd)
{
    ASSERT(fd >= 0 && fd < FD_SETSIZE, "fd>=0&&fd<FD_SETSIZE");
    if (fd < 0 || fd >= FD_SETSIZE)
    {
	TxError("WARNING: TxDelete1InputDevice(fd=%d) called with fd out of range 0..%d\n", fd, FD_SETSIZE-1);
	return; /* allowing things to continue is UB */
    }

restart:
    {
	int i;
	for (i = 0; i < txLastInputEntry; i++)
	{
	    FD_CLR(fd, &txInputDevice[i].tx_fdmask);
	    if (FD_IsZero(&txInputDevice[i].tx_fdmask))
	    {
		int j;
		for (j = i+1; j < txLastInputEntry; j++)
		    txInputDevice[j-1] = txInputDevice[j];
		txLastInputEntry--;
		/* we shuffled entries down one, so txInputDevice[i] now
		 * looks at potential next one to inspect, but we're about
		 * to i++ (if we continue) and that will incorrectly skip
		 * inspection of it.
		 * lets just start over
		 */
		goto restart;
	    }
	}
    }
    FD_CLR(fd, &txInputDescriptors);
    TxInputDescriptorsRecalc();
}


/*
 * ----------------------------------------------------------------------------
 * TxSetPoint --
 *
 *	Set the point and window that will be used for the next command.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Changes global set point variables.
 * ----------------------------------------------------------------------------
 */

void
TxSetPoint(
    int x,
    int y,
    int wid)
{
    txHaveCurrentPoint = TRUE;
    txCurrentPoint.p_x = x;
    txCurrentPoint.p_y = y;
    txCurrentWindowID = wid;
}

/*
 * ----------------------------------------------------------------------------
 * TxGetPoint --
 *
 *	Return the window ID and set the point (if non-NULL) set for the
 *	next command.
 *
 * Results:
 *	integer ID of the current window, or -1 if txHaveCurrentPoint is
 *	TX_POINT_CLEAR
 *
 * Side effects:
 *	Current point goes into
 *
 * ----------------------------------------------------------------------------
 */

int
TxGetPoint(
    Point *tx_p)
{
    if (txHaveCurrentPoint)
    {
	if (tx_p != (Point *)NULL)
	    *tx_p = txCurrentPoint;
	return txCurrentWindowID;
    }
    return -1;
}

/*
 * ----------------------------------------------------------------------------
 * TxClearPoint --
 *
 *	Clear the point that will be used for the next command, forcing it to
 *	be read from the mouse or button push.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Nullifies the effect of any previous TxSetPoint() call.
 * ----------------------------------------------------------------------------
 */

void
TxClearPoint(void)
{
    txHaveCurrentPoint = FALSE;
}

static FILE *txLogFile = NULL;
unsigned char txLogFlags;

/*
 * ----------------------------------------------------------------------------
 * TxLogStart --
 *
 *	Log all further commands to the given file name.  If the file is NULL,
 *	turn off logging.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	File IO.
 * ----------------------------------------------------------------------------
 */

void
TxLogStart(
    const char *fileName,
    MagWindow *mw)	 /* Window commands are logged from */
{
    if (txLogFile != NULL)
    {
	TxError("There is already a log file open!\n");
	return;
    }

    txLogFlags = 0;
    txLogFile = fopen(fileName, "w");
    if (txLogFile == NULL)
	TxError("Could not open file '%s' for writing.\n", fileName);
    else
    {
	time_t t_stamp = time((time_t *)NULL);
	struct tm *clock = localtime(&t_stamp);
	char *now = ctime(&t_stamp);

	TxPrintf("Logging commands to file \"%s\"\n", fileName);
	/* Write comment line header into command file and log the current
	 * window view position so that cursor positions in the file match
	 * the layout.  If the cursor box is defined, then issue a "box
	 * values" command so that relative box adjustments are correct.
	 */
#ifdef MAGIC_WRAPPER
	fprintf(txLogFile, "# Magic command log file\n");
	fprintf(txLogFile, "# Using technology: %s\n", DBTechName);
	if (mw != NULL)
	    fprintf(txLogFile, "# Title: %s\n", mw->w_caption);
	fprintf(txLogFile, "# Date: %s", now);
#endif
	if (mw != NULL)
	{
	    CellDef *rootBoxDef;
	    Rect rootBox;

#ifndef MAGIC_WRAPPER
	    // Colon preceeds commands in the non-Tcl verson of the log file.
	    fprintf(txLogFile, ":");
#endif
	    fprintf(txLogFile, "view %di %di %di %di\n",
			mw->w_surfaceArea.r_xbot, mw->w_surfaceArea.r_ybot,
			mw->w_surfaceArea.r_xtop, mw->w_surfaceArea.r_ytop);
	    if (ToolGetBox(&rootBoxDef, &rootBox))
	    {
		fprintf(txLogFile, "box values %di %di %di %di\n",
				rootBox.r_xbot, rootBox.r_ybot,
				rootBox.r_xtop, rootBox.r_ytop);
	    }
	}
    }
}

/*
 * ----------------------------------------------------------------------------
 * TxLogStop --
 *
 *	Turn off logging.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	File IO.
 * ----------------------------------------------------------------------------
 */

void
TxLogStop(void)
{
    if (txLogFile != NULL)
    {
	TxPrintf("Ending command logging to file.\n");
	fclose(txLogFile);
	txLogFile = NULL;
	txLogFlags = 0;
    }
}

/*
 * ----------------------------------------------------------------------------
 * TxLogUpdate --
 *
 *	Set the log file flags to force a display refresh after logged commands
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	File IO.
 * ----------------------------------------------------------------------------
 */

void
TxLogUpdate(void)
{
    if (txLogFile == NULL)
    {
	TxError("There is no log file to set an update flag on.\n");
	return;
    }
    if (txLogFlags & TX_LOG_UPDATE)
    {
	txLogFlags &= ~TX_LOG_UPDATE;
	TxPrintf("No display refresh after logged commands.\n");
    }
    else
    {
	txLogFlags |= TX_LOG_UPDATE;
	TxPrintf("Forcing display refresh after logged commands.\n");
    }
}

/*
 * ----------------------------------------------------------------------------
 * TxLogSuspend --
 *
 *	Suspend command logging
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	File IO.
 * ----------------------------------------------------------------------------
 */

void
TxLogSuspend(void)
{
    if (txLogFile == NULL)
	return;

    txLogFlags |= TX_LOG_SUSPEND;
}

/*
 * ----------------------------------------------------------------------------
 * TxLogResume --
 *
 *	Resume command logging
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	File IO.
 * ----------------------------------------------------------------------------
 */

void
TxLogResume(void)
{
    if (txLogFile == NULL)
	return;

    txLogFlags &= ~TX_LOG_SUSPEND;
}

/*
 * ----------------------------------------------------------------------------
 * txLogCommand --
 *
 *	Log a command in the log file.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 * ----------------------------------------------------------------------------
 */

void
txLogCommand(
    TxCommand *cmd)
{
    static const char *txButTable[] =
    {
	"left",
	"middle",
	"right",
	0
    };
    static const char *txActTable[] =
    {
	"down",
	"up",
	0
    };

#ifdef MAGIC_WRAPPER
    const char *pfix = "";
#else
    const char *pfix = ":";
#endif

    /* Do not do anything if there is no log file */
    if (txLogFile == (FILE *) NULL) return;

    /* Do not write anything if the log file is suspended */
    if (txLogFlags & TX_LOG_SUSPEND) return;

    if (cmd->tx_argc > 0)
    {
	int i;
	char *postns;

	/* Do not output "logcommand" commands to the log file.
	 * Do not output "*bypass" commands to the log file.
	 * Do not output "setpoint" commands to the log file.
	 * Do not output "wire show" commands to the log file (excessive output).
	 */
	postns = strstr(cmd->tx_argv[0], "::");
	if (postns == NULL)
	    postns = cmd->tx_argv[0];
	else
	    postns += 2;

	if (!strncmp(postns, "logc", 4))
	    return;
	else if (!strcmp(postns, "*bypass"))
	    return;
	else if (!strcmp(postns, "setpoint"))
	    return;
	else if (!strcmp(postns, "wire") && !strcmp(cmd->tx_argv[1], "show"))
	    return;

	fprintf(txLogFile, "%s%s", pfix, cmd->tx_argv[0]);
	for (i = 1; i < cmd->tx_argc; i++)
	{
	    bool needQuotes = (strchr(cmd->tx_argv[i], ' ') == NULL) ? FALSE : TRUE;
	    fprintf(txLogFile, " ");
	    if (needQuotes) fprintf(txLogFile, "\"");
	    fprintf(txLogFile, "%s", cmd->tx_argv[i]);
	    if (needQuotes) fprintf(txLogFile, "\"");
	}
	fprintf(txLogFile, "\n");
    }
    else if (cmd->tx_button == TX_CHARACTER) {
	/* its a no-op command */
	return;
    }
    else {
	int but, act;

	switch(cmd->tx_button) {
	    case TX_LEFT_BUTTON: { but = 0; break; };
	    case TX_MIDDLE_BUTTON: { but = 1; break; };
	    case TX_RIGHT_BUTTON: { but = 2; break; };
	    default: {ASSERT(FALSE, "txLogCommand"); but = -1; break; };
	}
	switch(cmd->tx_buttonAction) {
	    case TX_BUTTON_DOWN: { act = 0; break; };
	    case TX_BUTTON_UP: { act = 1; break; };
	    default: {ASSERT(FALSE, "txLogCommand"); act = -1; break; };
	}

	if (but >= 0 && act >= 0)
	    fprintf(txLogFile, "%spushbutton %s %s\n",
		pfix, txButTable[but], txActTable[act]);
    }
    if (txLogFlags & TX_LOG_UPDATE)
	fprintf(txLogFile, "%supdatedisplay\n", pfix);

    fflush(txLogFile);
}

/*
 * ----------------------------------------------------------------------------
 *
 * TxGetInputEvent --
 *
 *	Get some events and put them into the event queue.  If returnOnSigWinch
 *	is on, we will return early if some event (such as a SigWinch) requires
 *	immediate attention.  In that case, the input event queue may be empty
 *	even if block is TRUE.
 *
 * Results:
 *	"TRUE" if it got some input.
 *
 * Side Effects:
 *	Some IO may be done, and things may appear in the event queue.
 *
 * ----------------------------------------------------------------------------
 */

bool
TxGetInputEvent(
    bool block,		/* If TRUE, we will wait for an event.  Otherwise, we
			 * just poll.
			 */
    bool returnOnSigWinch)
			/* If we get a Sig-Winch signal, should we abondon
			 * our quest to read an input event and return
			 * immediately instead?
			 */
{

    struct timeval *waitTime;
    fd_set inputs;
    int numReady;
    bool gotSome;
    int i, fd, lastNum;

    ASSERT(!FD_IsZero(&txInputDescriptors), "TxGetInputEvent");

    if (block)
	waitTime = NULL;
    else
	waitTime = &txZeroTime;

    gotSome = FALSE;
    do {
	do
	{
	    if (returnOnSigWinch && SigGotSigWinch) return gotSome;
	    inputs = txInputDescriptors;
	    numReady = select(txInputDescriptors_nfds + 1, &inputs, (fd_set *)NULL,
		(fd_set *)NULL, waitTime);
	    if (numReady <= 0) FD_ZERO(&inputs);	/* no fd is ready */
	} while ((numReady <= 0) && (errno == EINTR));

	if ((numReady < 0) && (errno != EINTR))
	{
	    perror("magic");
	}

	/* 0..1023 using numReady==0 to terminate early */
	for (fd = 0; numReady && fd <= txInputDescriptors_nfds; fd++)
	{
	    if (!FD_ISSET(fd, &inputs))
		continue; /* this fd is was not monitored or is not ready */

	    /* find the input device receiver entry */
	    for (i = 0; i < txLastInputEntry; i++)
	    {
		if (!FD_ISSET(fd, &txInputDevice[i].tx_fdmask))
		    continue;

		/* This device has data on its file descriptor, call
		 * it so that it can add events to the input queue.
		 */
		lastNum = txNumInputEvents;

		(*txInputDevice[i].tx_inputProc)(fd, txInputDevice[i].tx_cdata);

		/* Did this driver choose to add an event? */
		if (txNumInputEvents != lastNum) gotSome = TRUE;
		numReady--;

		/* original code would FD_CLR() which would effectively break here
		 * so this only allows the first found receiver to receive the data
		 */
		break;
	    }
	}
	/*
	 * At this point we have handled all the bits in 'inputs' -- almost.
	 * It is possible for an input handler to remove or add other handlers
	 * via calls to TxDeleteDevice or TxAddDevice.  Therefore, there may be
	 * bits in inputs that haven't been turned off.
	 */
    } while (block && !gotSome);
    return gotSome;
}

#ifndef MAGIC_WRAPPER

/*
 * ----------------------------------------------------------------------------
 *
 * TxParseString_internal --
 *
 *	Parse a string into commands, and add them to the rear of a queue.
 *	The commands in the queue should eventually be freed by the caller
 *	via TxFreeCommand().
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	None.
 *
 * Module internal API.  See TxParseString() for public version.
 *
 * ----------------------------------------------------------------------------
 */

static void
TxParseString_internal(
    const char *str,		/* The string to be parsed. */
    DQueue *q,			/* Add to the tail of this queue. */
    TxInputEvent *event)	/* An event to supply the point, window ID,
				 * etc. .  If NULL, we will use the last
				 * event processed.
				 */
{
    const char *remainder;
    TxCommand *cmd;

    if (event == NULL) event = &txLastEvent;

    remainder = str;
    while (remainder != NULL) {
	/* Parse a single command. */
	cmd = TxNewCommand();
	strncpy(cmd->tx_argstring, remainder, TX_MAX_CMDLEN);
	cmd->tx_argstring[TX_MAX_CMDLEN - 1] = '\0';

	if (ParsSplit(cmd->tx_argstring, TX_MAXARGS,
	    &(cmd->tx_argc), cmd->tx_argv, &remainder) )
	    {
		if (event == NULL) event = &txLastEvent;
		cmd->tx_button = TX_CHARACTER;
		cmd->tx_p = event->txe_p;
		cmd->tx_wid = event->txe_wid;
		DQPushRear(q, (ClientData) cmd);
	    }
	    else
	    {
		TxError("Unable to completely parse command line '%s'\n",
		    str);
		TxFreeCommand(cmd);
		return;
	    }
    }
}

/*
 * ----------------------------------------------------------------------------
 *
 * TxParseString --
 *
 *	Parse a string into commands, and add them to the rear of a queue.
 *	The commands in the queue should eventually be freed by the caller
 *	via TxFreeCommand().
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	None.
 *
 * Public API.  See also TxParseString_internal().
 *
 * ----------------------------------------------------------------------------
 */
void
TxParseString(
    const char *str)		/* The string to be parsed. */
{
    TxParseString_internal(str, NULL, NULL);
}

#else

/* tclmagic.c defines TxParseString() so we need a shim to other way
 *
 * FIXME it feels like there is some design error here, only this
 *  file cares about DQueue/TxInputEvent arguments but they are
 *  passed but not used by tclmagic.c TxParseString()
 *
 * It must be that !MAGIC_WRAPPER needs the DQueue/TxInputEvent for
 * the other builds, such as WASM.
 *
 */
static void
TxParseString_internal(
    const char *str,            /* The string to be parsed. */
    DQueue *q,
    TxInputEvent *event)
{
    TxParseString(str);
}

#endif  /* !MAGIC_WRAPPER */

/*
 * ----------------------------------------------------------------------------
 * txGetInteractiveCommand:
 *
 *	Get one or more commands from one of the interactive input devices,
 *	and put it into the input queue.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	None.
 * ----------------------------------------------------------------------------
 */

void
txGetInteractiveCommand(
    bool block,			/* If TRUE, then wait until we have a command.
				 * If FALSE, then get one only if input is
				 * available.
				 */
    DQueue *queue)		/* Queue to receive the new commands. */
{
    static char inputLine[TX_MAX_CMDLEN] = "";
    TxInputEvent *event;
    TxCommand *cmd;
    int ch;

    /* Get one new event and return (except for text commands, then collect
     * an entire line.
     */
    if (DQIsEmpty(&txInputEvents)) TxGetInputEvent(block, TRUE);
    if (DQIsEmpty(&txInputEvents)) return;

    event = (TxInputEvent *) DQPopFront(&txInputEvents);
    txLastEvent = *event;
    if (TxDebug) TxPrintEvent(event);
    ASSERT(event != NULL, "txGetInteractiveCommand");

    if (event->txe_button == TX_EOF) {
	/* We have an end of file.  Put it at the end of our command queue,
	 * but only if we are waiting for a command.  We don't want to
	 * put in EOF's if we are just polling.
	 */
	if (block)
	{
	    cmd = TxNewCommand();
	    cmd->tx_button = TX_EOF;
	    cmd->tx_p = event->txe_p;
	    cmd->tx_wid = event->txe_wid;
	    cmd->tx_argc = 0;
	    cmd->tx_argv[0] = NULL;
	    DQPushRear(queue, (ClientData) cmd);
	}
	TxFreeEvent(event);
    } else if ((TxCurButtons != 0) && (event->txe_button == TX_CHARACTER)) {
	/* We have a keyboard character, but buttons are still down.
	 * Push fake button up commands into the queue in front of this
	 * event, so that all buttons will be up by the time that we get
	 * to the character event.  This is needed to avoid strange things
	 * when the user pushes a buttons down on a Sun 160 (or other device
	 * with a non-Magic window package) and then releases it outside of
	 * the Magic windows.
	 */
	TxInputEvent *newEvent;
	int ourbuts;

	ourbuts = TxCurButtons;
	DQPushFront(&txInputEvents, event);
	while (ourbuts != 0) {
	    newEvent = TxNewEvent();
	    newEvent->txe_p = event->txe_p;
	    newEvent->txe_wid = event->txe_wid;
	    /* Get rightmost '1' bit of ourbuts. */
	    newEvent->txe_button = LAST_BIT_OF(ourbuts);
	    newEvent->txe_buttonAction = TX_BUTTON_UP;
	    newEvent->txe_ch = 0;
	    ourbuts &= ~newEvent->txe_button;
	    DQPushFront(&txInputEvents, newEvent);
	}
	/* Now, go around the loop and process these things just like
	 * the user had typed them.
	 */
    } else if (event->txe_button == TX_CHARACTER) {
	/* We have a text command, go into text collection mode */

	/* need to check for long command or macro, then assemble line */
	ch = event->txe_ch;
	TxFreeEvent(event);
	if ((ch == (int)TX_LONG_CMD) || (ch == (int)TX_LONG_CMD2))
	{
	    (void) TxGetLinePrompt(inputLine, TX_MAX_CMDLEN, TX_CMD_PROMPT);
	    if (inputLine[0] != '\0') MacroDefine(DBWclientID, (int)'.',
			inputLine, NULL, FALSE);
	    TxParseString_internal(inputLine, queue, (TxInputEvent* ) NULL);
	}
	else
	{
	    bool iMacro; /* Interactive macro */
	    char *macroDef;

	    macroDef = MacroRetrieve(DBWclientID, ch, &iMacro);
	    if (macroDef == NULL)
	    {
		/* Cases macroDef may be NULL:			*/
		/* 1) Return  2) Ctrl-C  3) Invalid macro	*/

		if (ch == (int)'\n')
		{
		    if (TxInteractive) TxPrintf("%c\n", TX_PROMPT);
		}
		else
		{
		    /* not a valid command */
		    char *vis = MacroName(ch);
		    TxError("Unknown macro or short command: '%s'\n", vis);
		    freeMagic(vis);
		}
	    }
	    else
	    {
		if (iMacro)
		{
		    (void) TxGetLineWPrompt(inputLine,
					TX_MAX_CMDLEN, TX_CMD_PROMPT, macroDef);
		    if (inputLine[0] != '\0') MacroDefine(DBWclientID, (int)'.',
				inputLine, NULL, FALSE);
		    TxParseString_internal(inputLine, queue, (TxInputEvent *) NULL);
		}
		else
		{
		    TxParseString_internal(macroDef, queue, (TxInputEvent *) NULL);
		}
		freeMagic(macroDef);
	    }
	}
    } else if ((event->txe_button & TX_LEFT_BUTTON) ||
	       (event->txe_button & TX_MIDDLE_BUTTON) ||
	       (event->txe_button & TX_RIGHT_BUTTON)) {
	/* We have a button command, but ignore double ups & downs. */
	int oldButtons;
	oldButtons = TxCurButtons;
	if (event->txe_buttonAction == TX_BUTTON_UP)
	    TxCurButtons &= ~(event->txe_button);
	else
	    TxCurButtons |= event->txe_button;
	if (oldButtons == TxCurButtons) {
	    TxFreeEvent(event);
	} else {
	    /* We have a valid button command. */
	    cmd = TxNewCommand();
	    cmd->tx_button = event->txe_button;
	    cmd->tx_buttonAction = event->txe_buttonAction;
	    cmd->tx_p = event->txe_p;
	    cmd->tx_wid = event->txe_wid;
	    cmd->tx_argc = 0;
	    cmd->tx_argv[0] = NULL;
	    DQPushRear(queue, (ClientData) cmd);
	    TxFreeEvent(event);
	}
    } else {	/* TX_BYPASS */
	/* Ignore this event */
	TxFreeEvent(event);
    }
}

/*
 * ----------------------------------------------------------------------------
 * txGetFileCommand --
 *
 *	Get a line from a file and stick the commands into the input queue.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Reads the file and puts a command into the queue.
 * ----------------------------------------------------------------------------
 */

void
txGetFileCommand(
    FILE *f,			/* File to read. */
    DQueue *queue)		/* Queue to receive the new commands. */
{
    char inputLine[TX_MAX_CMDLEN];
    char *linep;
    char *current;
    int spaceleft;

    /* Generate a line by patching together successive lines ending
     * in '\'.
     */

    do {
	current = inputLine;
	spaceleft = TX_MAX_CMDLEN - 1;

	while (TRUE)
	{
	    if (fgets(current, spaceleft, f) == NULL)
		return;

	    while (*current != 0)
	    {
		current++;
		spaceleft--;
	    }
	    if ((*(current-1) != '\n') || (*(current-2) != '\\')) goto gotline;
	    current--;
	    spaceleft++;
	}

	gotline: *current = 0;

	/* If the line is empty, or contains a hash mark as
	 * the first non-blank character, then skip over it.
	 */

	current = inputLine;
	while (isspace(*current)) current++;
    } while ((*current == 0) || (*current == '#'));

    linep = inputLine;

#ifdef MAGIC_WRAPPER
    /* Don't muck with Tcl's interpretation of semicolon as a	*/
    /* comment character.  Watch for single-colon entries for	*/
    /* backwards-compatibility with the old format, but watch	*/
    /* for the double-colon, which is a Tcl namespace marker.	*/

    if ((inputLine[0] == ':') && (inputLine[1] != ':')) linep++;
#else
    if ((inputLine[0] == ':') || (inputLine[1] == ';')) linep++;
#endif

    TxParseString_internal(linep, queue, (TxInputEvent *) NULL);
}

/*
 * ----------------------------------------------------------------------------
 * TxRebuildCommand:
 *
 *	Rebuild the arguments of a TxCommand structure.  This assumes
 *	that a routine has rewritten the tx_argstring record with a
 *	modified command.  Tokenize the tx_argstring and update the
 *	tx_argc count and tx_argv pointers.
 *
 *	The purpose of this routine is to allow some command callbacks
 *	to change the command from one implying the use of pointer
 *	coordinates to an equivalent command that uses database units,
 *	for the purpose of logging the command.
 *
 * ----------------------------------------------------------------------------
 */
void
TxRebuildCommand(
    TxCommand *cmd)
{
    char *cptr, *tptr, c;

    cmd->tx_argc = 0;
    tptr = cptr = cmd->tx_argstring;

    do
    {
	c = *cptr;
	if ((c == ' ') || (c == '\0'))
	{
	    cmd->tx_argv[cmd->tx_argc] = tptr;
	    cmd->tx_argc++;
	    *cptr = '\0';
	    tptr = cptr + 1;
	}
	cptr++;
    }	
    while (c != '\0');
}

#ifdef MAGIC_WRAPPER

/*
 * ----------------------------------------------------------------------------
 * TxTclDispatch:
 *
 *	Command dispatcher which relies on the Tcl interpreter for most
 *	of the work.  Replaces TxDispatch except for file input during
 *	startup.
 *
 * ----------------------------------------------------------------------------
 */

int
TxTclDispatch(
   ClientData clientData,
   int argc,
   char *argv[],
   bool quiet)
{
    int result;
    int n, asize;
    TxCommand *tclcmd;
    unsigned char lastdrc;

    if (argc > TX_MAXARGS)
    {
	TxError("Error: number of command arguments exceeds %d!\n", TX_MAXARGS);
	return -1;
    }

    SigIOReady = FALSE;
    if (SigInterruptOnSigIO >= 0) SigInterruptOnSigIO = 1;
    SigInterruptPending = FALSE;

    tclcmd = TxNewCommand();
    tclcmd->tx_argc = argc;

    asize = 0;
    for (n = 0; n < argc; n++)
    {
	if (asize + strlen(argv[n]) >= TX_MAX_CMDLEN)
	{
	    TxError("Error: command length exceeds %d characters!\n", TX_MAX_CMDLEN);
	    TxFreeCommand(tclcmd);
	    return -1;
	}
	strcpy(&tclcmd->tx_argstring[asize], argv[n]);
	tclcmd->tx_argv[n] = &tclcmd->tx_argstring[asize];
	asize += (1 + strlen(argv[n]));
    }
    tclcmd->tx_p = txCurrentPoint;
    if (txHaveCurrentPoint)
	tclcmd->tx_wid = txCurrentWindowID;
    else
	tclcmd->tx_wid = WIND_UNKNOWN_WINDOW;

    /* Prevent DRCContinuous from running in the middle of a command	*/
    /* (Note that Tcl_CancelIdleCall() does *not* work. . .)		*/

    lastdrc = DRCBackGround;
    if (DRCBackGround != DRC_SET_OFF) DRCBackGround = DRC_NOT_SET;

    result = WindSendCommand((MagWindow *)clientData, tclcmd, quiet);

    if (txLogFile != NULL) txLogCommand(tclcmd);

    TxFreeCommand(tclcmd);

    // Don't update the command number on bypass commands, or else
    // the selection mechanism gets broken by the background DRC.

    if (argc > 0 && strcmp(argv[0], "*bypass")) TxCommandNumber++;

    if (SigInterruptPending)
	TxPrintf("[Interrupted]\n");

    if (result == 0) WindUpdate();

    SigInterruptPending = FALSE;
    if (SigInterruptOnSigIO >= 0) SigInterruptOnSigIO = 0;
    SigIOReady = FALSE;

    /* Force a break of DRCContinuous() so we don't run DRC on an	*/
    /* invalid database.						*/

    /* Avoid breaking on "*bypass" commands.  Technically, one could	*/
    /* set up a command to *bypass that alters the database and crashes	*/
    /* magic.  This is a total hack.  We should flag an invalid		*/
    /* database instead.						*/

    if (DRCBackGround == DRC_NOT_SET) DRCBackGround = lastdrc;
    if (argc > 0 && strcmp(argv[0], "*bypass") && strcmp(argv[0], "windownames"))
	DRCBreak();

    /* Reinstate the idle call */
    if (result == 0) Tcl_DoWhenIdle(DRCContinuous, (ClientData)NULL);
    return result;
}
#else  /* !MAGIC_WRAPPER */

/*
 * ----------------------------------------------------------------------------
 * TxDispatch:
 *
 *	Read input from the interactive devices (keyboard or mouse) or from
 *	a file.  Send a command off to WindSendCommand for processing, and
 *	then repeat.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Commands are read and executed, one per cycle of this procedure's loop.
 * ----------------------------------------------------------------------------
 */

void
TxDispatch(
    FILE *f)		/* Read commands from this file instead of
			 * from the mouse or keyboard.
			 */
{
    bool inFile;
    DQueue inputCommands;	/* A queue of commands from our input
				 * sources.
				 */

    DQInit(&inputCommands, 4);

    inFile = (f != (FILE *) NULL);

    while (TRUE)
    {
	if (!inFile)
	{
	    /* Update the screen info of the window package and all of
	     * its clients.  If there's already input waiting, skip this...
	     * it can get done at the end, after all pending commands have
	     * been processed.
	     */
	    if (SigInterruptOnSigIO >= 0) SigInterruptOnSigIO = 0;
	    SigInterruptPending = FALSE;
	    txGetInteractiveCommand(FALSE, &inputCommands);
	    if (DQIsEmpty(&inputCommands))
	    {
		WindUpdate();
		if (SigInterruptPending)
		    TxPrintf("[Redisplay Interrupted]\n");
		txGetInteractiveCommand(FALSE, &inputCommands);
	    }

	    /* Give the DRC a chance to work. */
	    if (!SigGotSigWinch && DQIsEmpty(&inputCommands)) {
		if (DRCHasWork) {
		    TxSetPrompt(']');
		    TxPrompt();
		    (void) GrEnableTablet();
		    GrFlush();

		    /*  Call background DRC.
		     *  It will return if it is not enabled, there is no work to
		     *  be done, or it learns that the user has entered a
		     *  command.
		     */
		    SigIOReady = FALSE;
		    if (SigInterruptOnSigIO >= 0) SigInterruptOnSigIO = 1;
		    SigInterruptPending = FALSE;
		    DRCContinuous();
		    TxUnPrompt();

		    SigIOReady = FALSE;
		    if (SigInterruptOnSigIO >= 0) SigInterruptOnSigIO = 0;
		    SigInterruptPending = FALSE;
		    (void) GrDisableTablet();
		    WindUpdate();
		    if (SigInterruptPending)
			TxPrintf("[DRC Redisplay Interrupted]\n");
		}
		TxSetPrompt(TX_PROMPT);
		TxPrompt();
		(void) GrEnableTablet();
		GrFlush();

		txGetInteractiveCommand(TRUE, &inputCommands);

		TxUnPrompt();
		(void) GrDisableTablet();
		GrFlush();
		SigInterruptPending = FALSE;
		SigIOReady = FALSE;
	    }
	}
	else
	{
	    if (feof(f)) goto done;
	    if (SigInterruptPending && inFile)
	    {
		TxPrintf("[Read-in of command file aborted]\n");
		goto done;
	    }
	    if (!SigGotSigWinch) txGetFileCommand(f, &inputCommands);
	}

	if (SigGotSigWinch) {
	    /* WindUpdate will take care of SigWinch and set it to FALSE */
	    WindUpdate();
	}

	/* Now send off the commands in the queue. */
	while (!DQIsEmpty(&inputCommands))
	{
	    TxCommand *cmd;

	    cmd = (TxCommand *) DQPopFront(&inputCommands);
	    if (TxDebug) TxPrintCommand(cmd);
	    if (cmd->tx_button == TX_EOF)
	    {
		/* End of our input stream. */
		TxError("EOF encountered on input stream -- Magic exiting.\n");
		MainExit(1);
	    }

	    /****
	    ASSERT(cmd->tx_p.p_x > 0 && cmd->tx_p.p_x < 10000, "TxDispatch");
	    ASSERT(cmd->tx_p.p_y > 0 && cmd->tx_p.p_y < 10000, "TxDispatch");
	    ****/

	    /* force the point, if that was requested */
	    if (txHaveCurrentPoint) {
		cmd->tx_p = txCurrentPoint;
		cmd->tx_wid = txCurrentWindowID;
		if (!inFile) txHaveCurrentPoint = FALSE;
	    };

	    /****
	    ASSERT(cmd->tx_argc >= 0 && cmd->tx_argc <= TX_MAXARGS,
		"TxDispatch");
	    if (cmd->tx_argc != 0) {
		ASSERT(cmd->tx_button == TX_CHARACTER, "TxDispatch");
	    }
	    else {
		ASSERT((cmd->tx_buttonAction == TX_BUTTON_DOWN) || \
		    (cmd->tx_buttonAction == TX_BUTTON_UP),
		    "TxDispatch");
		ASSERT((cmd->tx_button == TX_LEFT_BUTTON) || \
		    (cmd->tx_button == TX_MIDDLE_BUTTON) || \
		    (cmd->tx_button == TX_RIGHT_BUTTON) || \
		    (cmd->tx_button == TX_CHARACTER),
		    "TxDispatch");
	    };
	    ****/
	    if (!inFile && (txLogFile != NULL)) txLogCommand(cmd);

	    /*
	     *  Add lisp interpreter here. We trap the character
	     *  commands and call the lisp interpreter that traps
	     *  any lisp-like behavior and returns a stream of "cmd"
	     *  functions which are then sent to the appropriate window
	     *  using the usual WindSendCommand() call.
	     *
	     *  (rajit@cs.caltech.edu)
	     */
#ifdef SCHEME_INTERPRETER
	    if (cmd->tx_button == TX_CHARACTER)
	      {
		lisp_cur_cmd = cmd;
		LispEvaluate (cmd->tx_argc, cmd->tx_argv, inFile);
		TxFreeCommand (cmd);
		lisp_cur_cmd = NULL;
	      }
	    else {
#endif
	      (void) WindSendCommand((MagWindow *) NULL, cmd, FALSE);
	      TxFreeCommand(cmd);
	      TxCommandNumber++;

#ifdef SCHEME_INTERPRETER
	    }
#endif
        }
	/* ?? */
	WindUpdate();

    } /* while */

done:
    while (!DQIsEmpty(&inputCommands))
	TxFreeCommand((TxCommand *) DQPopFront(&inputCommands));
    DQFree(&inputCommands);
}

int
TxDispatchString(
    const char *str,
    bool quiet)
{
    int result = 0;
    DQueue inputCommands;

    DQInit(&inputCommands, 4);
    TxParseString_internal(str, &inputCommands, (TxInputEvent *) NULL);

    while (!DQIsEmpty(&inputCommands))
    {
	TxCommand *cmd;

	cmd = (TxCommand *) DQPopFront(&inputCommands);

	if (txHaveCurrentPoint)
	{
	    cmd->tx_p = txCurrentPoint;
	    cmd->tx_wid = txCurrentWindowID;
	    txHaveCurrentPoint = FALSE;
	}

	result = WindSendCommand((MagWindow *) NULL, cmd, quiet);
	TxFreeCommand(cmd);
	TxCommandNumber++;

	if (result != 0)
	    break;
    }

    WindUpdate();

    while (!DQIsEmpty(&inputCommands))
	TxFreeCommand((TxCommand *) DQPopFront(&inputCommands));
    DQFree(&inputCommands);

    return result;
}

#endif	/* !MAGIC_WRAPPER */

/*
 * ----------------------------------------------------------------------------
 *
 * txCommandsInit --
 *
 *	Initialize the things in this file
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	Variables are set up.
 *
 * ----------------------------------------------------------------------------
 */

void
txCommandsInit(void)
{
    txZeroTime.tv_sec = 0;
    txZeroTime.tv_usec = 0;
    FD_ZERO(&txInputDescriptors);
    txInputDescriptors_nfds = 0;
    DQInit(&txInputEvents, 4);
    DQInit(&txFreeEvents, 4);
    DQInit(&txFreeCommands, 4);
}

#ifdef SCHEME_INTERPRETER

/*
 * ----------------------------------------------------------------------------
 *
 * txLispDispatch --
 *
 *	Send command to magic window.
 *
 * Results:
 *	Returns TRUE on success, FALSE on failure.
 *
 * Side Effects:
 *	whatever the command does.
 *
 * ----------------------------------------------------------------------------
 */

bool
TxLispDispatch(
    int argc,
    char **argv,
    int trace,
    int inFile)
{
  TxCommand *cmd = lisp_cur_cmd;
  int i,j,k;
  bool ret;
  Point tx_p;
  int wid;

  cmd->tx_argc = argc;
  for (i=0,j=0; i < argc; i++) {
    cmd->tx_argv[i] = cmd->tx_argstring+j;
    k=0;
    while (cmd->tx_argstring[j] = argv[i][k])
	j++,k++;
    j++;
  }
  tx_p = cmd->tx_p;
  wid = cmd->tx_wid;
  if (txHaveCurrentPoint) {
    cmd->tx_p = txCurrentPoint;
    cmd->tx_wid = txCurrentWindowID;
    if (!inFile) txHaveCurrentPoint = FALSE;
  }
  if (trace)
    TxPrintCommand (cmd);
  ret = WindSendCommand((MagWindow *) NULL, cmd, FALSE);
  /* restore the original values */
  cmd->tx_p = tx_p;
  cmd->tx_wid = wid;
  TxCommandNumber++;
  return ret;
}

#endif	/* SCHEME_INTERPRETER */
