/* simpleEvent.c
 *
 *	This file implements a trivial event manager for Tcl applications
 * that don't require all the baggage of Tk.
 *
 *	It provides file handlers, timers and whenIdle calls.
 */

/* $Source: /cluster21/kennykb/src/tclTCP.1.0beta/RCS/simpleEvent.c,v $
 * $Log: simpleEvent.c,v $
 * Revision 1.6  1992/06/23  15:39:25  kennykb
 * Fixed an off-by-one error in simpleEvent.c that could cause non-tk-based
 * servers to fail to process traffic on a socket.
 *
 * Revision 1.5  1992/03/04  20:03:10  kennykb
 * Modified source code to use the Tcl configurator and corresponding
 * include files.
 *
 * Revision 1.4  1992/03/04  18:24:21  kennykb
 * Added support for Xt event management
 *
 * Revision 1.3  1992/03/03  18:11:18  kennykb
 * Added RCS keywords in body of file.
 *
 */

#ifdef _AIX
#include <sys/select.h>
#endif
#include "tclInt.h"
#include "tclUnix.h"

#ifdef USE_TK
#include "tk.h"
#endif

#ifdef USE_XT
#include <X11/Intrinsic.h>
#endif

#include "simpleEvent.h"

/*
 ********************************************************************************
 * NOTE - should really set things up so that when an interpreter is deleted
 * it goes ahead and deletes all idle and timer handlers that are associated
 * with it. Some of these are not associated with any commands within the
 * interpreter and as a result they are not deleted when the interpreter is destroyed.
 ********************************************************************************
 */

extern int getdtablesize _ANSI_ARGS_((void));
extern int select _ANSI_ARGS_((int, fd_set *, fd_set *, fd_set *,
			       struct timeval *));

/* In the Xt environment, we may need to keep track of application context.
 * The following static variable does it.
 */

#ifdef USE_XT

XtAppContext simpleCurrentAppContext = NULL;

#endif /* USE_XT */

#ifndef USE_TK

/* If Tk is not in use, then each when-idle handler will have a record of the
 * following type:
 */

typedef struct simple_IdleHandler {
  Simple_IdleProc * 	proc;	/* Procedure to invoke */
  ClientData		clientData;
				/* Client data for the procedure */
  struct simple_IdleHandler *next, *prev;
				/* Next and previous idle handler on the */
				/* chain. */
} Simple_IdleHandler;

/* Head and tail of the list of idle handlers, which must be executed in
 * sequence.
 */

static Simple_IdleHandler * simpleFirstIdleHandler
  = (Simple_IdleHandler *) NULL;
static Simple_IdleHandler * simpleLastIdleHandler
  = (Simple_IdleHandler *) NULL;

#endif /* not USE_TK */

/* In an Xt environment, the idle calls are all handled by a single `work
 * process' that is created when the first idle call is established.
 */

#ifdef USE_XT

static XtWorkProcId simpleIdleCallPending = (XtWorkProcId) NULL;

#endif /* USE_XT */

#if !(defined( USE_TK ) || defined( USE_XT ))

/* If neither Xt nor Tk is not in use, then each file on which there
 * is a handler will have a record of the following type:
 */

typedef struct simple_FileHandler {
  int			fd;	/* Filedescriptor on which events are */
				/* expected */
  int			mask;	/* Event mask: some combination of */
				/* TCP_READABLE, TCP_WRITABLE, and */
				/* TCP_EXCEPTION.  A deleted handler */
				/* has the TCP_DELETE bit added. */
  
  Simple_FileProc *	proc;	/* Event handling procedure */
  ClientData		clientData;
				/* Additional data to pass to proc */

  struct simple_FileHandler * next;
  struct simple_FileHandler * prev;
				/* Next and previous handler in a list */
				/* of active handlers. */
} Simple_FileHandler;

/* All the file handlers will be on a chain originating from the following
 * point:
 */

static Simple_FileHandler * simpleFirstFileHandler
  = (Simple_FileHandler *) NULL;

#endif /* neither USE_TK nor USE_XT */

/* If Xt is in use, then there will be a table that maps filedescriptors to
 * Xt input IDs, so that file handlers can be deregistered by descriptor.
 */

#ifdef USE_XT

typedef struct simple_XtFileHandler {
  XtInputId id;			/* Xt identifier for the handler */
  Simple_FileProc * proc;	/* Event handling procedure */
  ClientData clientData;	/* Client data */
} Simple_XtFileHandler;

Simple_XtFileHandler * simpleXtFileTable;

#endif /* USE_XT */

/* Timer handlers */

#ifndef USE_XT

/* Use the Tk event handler stuff but keep some info for list/delete */
typedef struct simple_TimerHandler {
  struct timeval time;		/* When timer is to fire */
  Simple_TimerProc *proc;	/* Procedure to call */
  ClientData clientData;	/* Argument to pass to proc */
  struct simple_TimerHandler *nextPtr; /* Next Timer handler on the chain. */
#ifdef USE_TK
  Tk_TimerToken tkEventToken;	/* Used for deleting tk event handler only */
#endif
}  Simple_TimerHandler;
			   
static Simple_TimerHandler * simpleTimerQueue;

#endif /* !USE_XT */

/* Static procedures in this file */

#ifdef USE_XT

static void simpleInputCallbackProc _ANSI_ARGS_((XtPointer, int *,
						 XtInputId *));

#endif /* USE_XT */

#ifdef USE_TK

static void simpleDeleteFileHandler1 _ANSI_ARGS_((ClientData, int));

static void simpleDeleteFileHandler2 _ANSI_ARGS_((ClientData));

#endif /* USE_TK */

#ifdef USE_XT

static Boolean simpleWorkProc _ANSI_ARGS_((XtPointer));

#endif /* USE_XT */

#ifndef USE_TK

static int simpleProcessIdleCalls _ANSI_ARGS_((int));

static void simpleDeleteIdleHandler _ANSI_ARGS_((Simple_IdleHandler *));

#endif /* not USE_TK */

#if !(defined( USE_TK ) || defined( USE_XT ))

static int simpleFileSelect _ANSI_ARGS_((Tcl_Interp *interp, int));

#endif /* neither USE_TK nor USE_XT */

/*
 * simpleSetAppContext --
 *
 *	This procedure is used in the Xt environment to make the application
 * context something other than the Xt default.  In the non-Xt environment,
 * it does nothing.
 */

void
simpleSetAppContext (context)
     XtAppContext context;
{

#ifdef USE_XT

  simpleCurrentAppContext = context;

#endif /* USE_XT */

}

/*
 * simpleReportBackgroundError --
 *
 *	This procedure is invoked to report a Tcl error in the background,
 * when TCL_ERROR has been passed out to the outermost level.
 *
 *	It tries to run `bgerror' giving it  the error message.  If this
 * fails, it reports the problem on stderr.
 */

void
simpleReportBackgroundError (interp)
     Tcl_Interp * interp;
{

  char *argv[2];
  char *command;
  char *error;
  char *errorInfo, *tmp;
  int status;
  int unixStatus;

  /* Get the error message out of the interpreter. */

  error = (char *) ckalloc (strlen (interp -> result) + 1);
  strcpy (error, interp -> result);

  /* Get errorInfo, too */

  tmp = Tcl_GetVar (interp, "errorInfo", TCL_GLOBAL_ONLY);
  if (tmp == NULL) {
    errorInfo = error;
  } else {
    errorInfo = (char *) ckalloc (strlen (tmp) + 1);
    strcpy (errorInfo, tmp);
  }

  /* Build a `bgerror' command to report the error */

  argv[0] = "bgerror";
  argv[1] = error;
  command = Tcl_Merge (2, argv);

  /* Try to run the command */

  status = Tcl_Eval (interp, command);

  if (status != TCL_OK) {

    /* Command failed.  Report the problem to stderr. */

    tmp = Tcl_GetVar (interp, "errorInfo", TCL_GLOBAL_ONLY);
    if (tmp == NULL) {
      tmp = interp -> result;
    }
    unixStatus = fprintf (stderr, "\n\
------------------------------------------------------------------------\n\
Tcl interpreter detected a background error.\n\
Original error:\n\
%s\n\
\n\
User \"bgerror\" procedure failed to handle the background error.\n\
Error in bgerror:\n\
%s\n",
	     errorInfo, tmp);
    if (unixStatus < 0) {
      abort ();
    }
  }

  Tcl_ResetResult (interp);

  ckfree (command);

  ckfree (error);

  if (errorInfo != error) {
    ckfree (errorInfo);
  }
}

/*
 * simpleDoWhenIdle --
 *
 *	This procedure is invoked to cause a function to be called at a later
 * time, when the event processor is idle.
 */

void
simpleDoWhenIdle (proc, clientData)
     Simple_IdleProc * proc;
     ClientData clientData;
{

#ifdef USE_TK

  /* In the Tk environment, use Tk's event processing. */

  Tk_DoWhenIdle (proc, clientData);

#else
  
  /* In the non-Tk environment, use our own queueing. */

  Simple_IdleHandler * handler;

  handler = (Simple_IdleHandler *) ckalloc (sizeof (Simple_IdleHandler));

  handler -> proc = proc;
  handler -> clientData = clientData;
  handler -> next = (Simple_IdleHandler *) NULL;
  handler -> prev = simpleLastIdleHandler;
  if (handler -> prev == NULL) {
    simpleFirstIdleHandler = handler;
  } else {
    handler -> prev -> next = handler;
  }
  simpleLastIdleHandler = handler;

#endif

#ifdef USE_XT

  /* In the Xt environment, there must be a work process established to
   * handle all the idle calls.
   */

  if (simpleIdleCallPending == NULL) {
    if (simpleCurrentAppContext == NULL) {
      simpleIdleCallPending = XtAddWorkProc ((XtWorkProc) simpleWorkProc,
					     (XtPointer) NULL);
    } else {
      simpleIdleCallPending = XtAppAddWorkProc (simpleCurrentAppContext,
						(XtWorkProc) simpleWorkProc,
						(XtPointer) NULL);
    }
  }

#endif /* USE_XT */

}

/*
 * simpleCancelIdleCall -- 
 *
 *	This procedure cancels an earlier call to simpleDoWhenIdle
 */

void
simpleCancelIdleCall (proc, clientData)
     Simple_IdleProc * proc;
     ClientData clientData;
{

#ifdef USE_TK

  /* Tk -- Refer the call to Tk's event manager. */

  Tk_CancelIdleCall (proc, clientData);

#else

  /* Non-Tk -- Delete any handlers that match the conditions */

  Simple_IdleHandler * h;
  Simple_IdleHandler * nexth;

  for (h = simpleFirstIdleHandler;
       h != (Simple_IdleHandler *) NULL;
       h = nexth)
    {
      nexth = h -> next;
      if (h -> proc == proc && h -> clientData == clientData) {
	simpleDeleteIdleHandler (h);
      }
    }

#endif
}
#ifdef USE_TK

/*
 * simpleExecuteTimerHandler -- 
 *
 *	This procedure is called from the Tk Event handler because it is
 * necessary for me to clean out the simple timer list!
 *
 */

void
simpleExecuteTimerHandler (clientData)
  ClientData clientData;
{
  Simple_TimerHandler *timerPtr = (Simple_TimerHandler *) clientData;
  Simple_TimerHandler *prevPtr, *ptr;

  /*
   * Execute the thang first!
   */
  (*timerPtr->proc)(timerPtr->clientData);

  for (ptr = simpleTimerQueue, prevPtr = NULL; ptr != NULL;
       prevPtr = ptr, ptr = ptr->nextPtr)
    if (ptr == timerPtr)
      break;
  if (!prevPtr)
    simpleTimerQueue = timerPtr->nextPtr;
  else
    prevPtr->nextPtr = timerPtr->nextPtr;
  ckfree((char *) timerPtr);
}
#endif /* USE_TK */

/*
 * simpleCreateTimerHandler --
 *
 *	This procedure is invoked to cause a function to be called at a later
 * time, when the event triggers. Uses tk event handling if possible
 *
 * Not currently (ever?!) for XT
 */

void
simpleCreateTimerHandler (milliseconds, proc, clientData, targetTime)
     int milliseconds; /* How many milliseconds to wait before invoking proc (maybe 0) */
     Simple_TimerProc * proc;
     ClientData clientData;
     struct timeval *targetTime; /* If non-NULL saves us the trouble of recalc desired exec time */
{
#ifdef USE_XT
  fprintf(stderr, "simpleCreateTimerHandler does not currently support XT\n");
  abort();
#endif /* USE_XT */

  Simple_TimerHandler *timerPtr, *tPtr2, *prevPtr;

  /* Need to create our own queue even with tk for list/deletion */
  timerPtr = (Simple_TimerHandler *) ckalloc(sizeof (Simple_TimerHandler));

  timerPtr->proc = proc;
  timerPtr->clientData = clientData;

  if (targetTime)
    memcpy(&timerPtr->time, targetTime, sizeof (struct timeval));
  else {
    (void) gettimeofday(&timerPtr->time, (struct timezone *) NULL);
    timerPtr->time.tv_sec += milliseconds/1000;
    timerPtr->time.tv_usec += (milliseconds%1000)*1000;
    if (timerPtr->time.tv_usec > 1000000) {
      timerPtr->time.tv_usec -= 1000000;
      timerPtr->time.tv_sec += 1;
    }
  }

  /*
   * Add the event to the queue in the correct position
   * (ordered by event firing time).
   */

  for (tPtr2 = simpleTimerQueue, prevPtr = NULL; tPtr2 != NULL;
       prevPtr = tPtr2, tPtr2 = tPtr2->nextPtr) {
    if ((tPtr2->time.tv_sec > timerPtr->time.tv_sec)
	|| ((tPtr2->time.tv_sec == timerPtr->time.tv_sec)
	    && (tPtr2->time.tv_usec > timerPtr->time.tv_usec))) {
      break;
    }
  }
  if (!prevPtr) {
    timerPtr->nextPtr = simpleTimerQueue;
    simpleTimerQueue = timerPtr;
  } else {
    timerPtr->nextPtr = prevPtr->nextPtr;
    prevPtr->nextPtr = timerPtr;
  }

#ifdef USE_TK
  /* In the Tk environment, use Tk's event processing. */
  timerPtr->tkEventToken = Tk_CreateTimerHandler(milliseconds,
						 simpleExecuteTimerHandler,
						 (ClientData) timerPtr);
#endif /* USE_TK */
}

/*
 * simpleDeleteTimerHandler -- 
 *
 *	This procedure cancels an earlier call to simpleCreateTimerHandler
 *
 * Note we compare clientdata contents with specified compareproc rather than
 * do direct comparison because they are dyn alloced (that's why we have deleteProc)
 * so it's not possible to do a compare on the pointer.
 * This routine (unlike e.g. Tk_DeleteTimerHandler) can delete many TimerHandlers.
 *
 * Compare proc returns non-zero if it finds a match.
 */

void
simpleDeleteTimerHandler (proc, clientData, compareProc, deleteProc)
  Simple_TimerProc * proc;
  ClientData clientData;
  int (*compareProc) _ANSI_ARGS_((ClientData c1, ClientData c2));
  void (*deleteProc) _ANSI_ARGS_((ClientData clientData));
{
#ifdef USE_XT
  fprintf(stderr, "simpleDeleteTimerHandler does not currently support XT\n");
  abort();
#endif /* USE_XT */

  Simple_TimerHandler *timerPtr, *prevPtr, *nextPtr;

  for (timerPtr = simpleTimerQueue, prevPtr = NULL; timerPtr; timerPtr = nextPtr) {
    nextPtr = timerPtr->nextPtr;
    if (timerPtr->proc != proc ||
	(compareProc && !compareProc(timerPtr->clientData, clientData))) {
      /* Wrong proc and/or incorrect comparison with clientdata - need to update prevPtr*/
      prevPtr = timerPtr;
      continue;
    }

#ifdef USE_TK
  /* Tk -- Refer the call to Tk's event manager. */
  Tk_DeleteTimerHandler (timerPtr->tkEventToken);
#endif /* USE_TK */

    if (deleteProc)
      deleteProc(timerPtr->clientData);

    if (!prevPtr)
      simpleTimerQueue = timerPtr->nextPtr;
    else
      prevPtr->nextPtr = timerPtr->nextPtr;
    ckfree((char *) timerPtr);
  }
}

/*
 * simpleListTimerHandler -- 
 *
 *	This procedure lists TimerHandlers matching clientData
 * (if compareProc is non-NULL else all matching proc)
 *
 * Note printProc must alloc its own space using tclCkalloc
 */

char *
simpleListTimerHandler (proc, clientData, compareProc, printProc)
  Simple_TimerProc * proc;
  ClientData clientData;
  int (*compareProc) _ANSI_ARGS_((ClientData c1, ClientData c2));
  char *(*printProc) _ANSI_ARGS_((ClientData clientData));
{
#ifdef USE_XT
  fprintf(stderr, "simpleListTimerHandler does not currently support XT\n");
  abort();
#endif /* USE_XT */

  Simple_TimerHandler *timerPtr;
  int argc = 0;
  char **argv = NULL, *outp = NULL;

  for (timerPtr = simpleTimerQueue; timerPtr; timerPtr = timerPtr->nextPtr) {
    if (timerPtr->proc != proc ||
	(compareProc && !compareProc(timerPtr->clientData, clientData)))
      /* Wrong proc and/or incorrect comparison with clientdata */
      continue;

    if (argv)
      argv = (char **) ckrealloc((char *) argv, ++argc * sizeof(char *));
    else 
      argv = (char **) ckalloc(++argc * sizeof(char *));
    argv[argc-1] = printProc(timerPtr->clientData);
  }
  outp = Tcl_Merge(argc, argv);

  if (argc) {
    for ( ; --argc >= 0; )
      ckfree(argv[argc]);
    ckfree((char *) argv);
  }

  return outp;
}

/*
 * simpleSelect --
 *
 *	This is the user-level function that's called in a non-Tk,
 * non-Xt environment to multiplex events in the servers.  It makes all
 * necessary calls to simpleFileSelect.
 *
 * It blocks to await ready conditions on files
 * if the SIMPLE_WAIT bit is on in its argument, and processes
 * the events if the SIMPLE_DO_EVENTS bit is set.
 *
 *	Return value is the number of events pending/processed.  If the return
 * value is zero, no events are pending.  If the return value is -1, either
 * (a) the select failed for some reason, or
 * (b) no servers or clients are running and timer event queue is empty.
 *	These are distinguished by a zero/non-zero value in errno.
 */

#if defined( USE_TK ) || defined( USE_XT )

int simpleSelect (interp, flags)
     Tcl_Interp *interp;
     int flags;
{
  errno = 0;
  return -1;
}

#else

int
simpleSelect (interp, flags)
     Tcl_Interp *interp;
     int flags;
{
  int status;

  /* Step 1 -- Determine if files or timer events are ready,
   * and process their events if necessary.
   */

  status = simpleFileSelect (interp, flags & SIMPLE_DO_EVENTS);
  if (status != 0)
    return status;

  /* Step 2 -- No files were ready.  Process idle calls if there are any. */

  status = simpleProcessIdleCalls (flags & SIMPLE_DO_EVENTS);
  if (status != 0)
    return status;

  /* Step 3 -- No idle calls are waiting, either.  Now we can do a blocking
   * select to await the next thing to do.
   */

  status = simpleFileSelect (interp, flags);
  return status;
}

#endif /* not USE_TK */

/*
 * simpleWorkProc --
 *
 *	This procedure exists only in the Xt environment.  It receives
 * control from the Xt main loop, and dispatches any idle calls.  Following
 * all the idle calls, it returns `True' to the Xt main loop, which causes
 * it to be removed from the work procedures for the application
 */

#ifdef USE_XT

/* ARGSUSED */
static Boolean
simpleWorkProc (clientData)
     XtPointer clientData;
{

  (void) simpleProcessIdleCalls (SIMPLE_DO_EVENTS);

  simpleIdleCallPending = (XtWorkProcId) NULL;

  return True;
}

#endif /* USE_XT */

/*
 * simpleProcessIdleCalls --
 *
 *	This function is invoked when the event manger determines that no
 * files are ready for I/O.  It processes any DoWhenIdle requests that are
 * pending, and returns a count of the requests.
 */

#ifndef USE_TK

static int
simpleProcessIdleCalls (flags)
     int flags;
{
  Simple_IdleHandler * h;
  Simple_IdleHandler * nexth;
  int count = 0;
  Simple_IdleProc * proc;
  ClientData clientData;

  for (h = simpleFirstIdleHandler;
       h != (Simple_IdleHandler *) NULL;
       h = nexth)
    {
      ++count;

      if (flags & SIMPLE_DO_EVENTS) {

	/* Careful here.  Running one idle handler can cancel it, or cancel
	 * another.
         */

	proc = h -> proc;
	clientData = h -> clientData;
	simpleDeleteIdleHandler (h);
	(*proc) (clientData);
	nexth = simpleFirstIdleHandler;

      } else {

	nexth = h -> next;

      }
    }

  return count;
}

#endif /* not USE_TK */

/*
 * simpleDeleteIdleHandler --
 * 
 *	This procedure is invoked to delete an idle handler.  The handler
 * is still linked on the chain of active handlers.
 */

#ifndef USE_TK

static void
simpleDeleteIdleHandler (h)
     Simple_IdleHandler * h;
{
  if (h -> prev == NULL) {
    simpleFirstIdleHandler = h -> next;
  } else {
    h -> prev -> next = h -> next;
  }

  if (h -> next == NULL) {
    simpleLastIdleHandler = h -> prev;
  } else {
    h -> next -> prev = h -> prev;
  }
  
  (void) ckfree ((char *) h);
}

#endif /* not USE_TK */

/*
 * simpleCreateFileHandler --
 *
 *	This procedure is invoked to create a handle to cause a callback
 *	whenever a condition (readable, writable, exception) is
 *	present on a given file.
 *
 *	In the Tk environment, the file handler is created using Tk's
 *	Tk_CreateFileHandler procedure, and the callback takes place
 *	from the Tk main loop.  In a non-Tk environment, a
 *	Simple_FileHandler structure is created to describe the file, and
 *	this structure is linked to a chain of such structures
 *	processed by the server main loop.
 */

void
simpleCreateFileHandler (fd, mask, proc, clientData)
     int fd;
     int mask;
     Simple_FileProc * proc;
     ClientData clientData;
{

#ifdef USE_TK

  /* Tk -- Refer the call to Tk */
  /* NOTE -- Assumes that Tk's condition bits are the same as ours. */

  Tk_CreateFileHandler (fd, mask, (Tk_FileProc *) proc, clientData);

  /* It is possible that we have a file handler scheduled for deletion.
   * This deletion has to be cancelled if we've requested creation of
   * another one.
   */

  Tk_CancelIdleCall ((Tk_IdleProc *) simpleDeleteFileHandler2,
		     (ClientData) fd);

#endif

#ifdef USE_XT

  /* Xt -- Refer the call to Xt */

  /* Create the handler table if necessary */

  if (simpleXtFileTable == NULL) {

    int tsize = getdtablesize ();
    int i;

    simpleXtFileTable =
      (Simple_XtFileHandler *) ckalloc (tsize * sizeof (Simple_XtFileHandler));

    for (i = 0; i < tsize; ++i)
      simpleXtFileTable [i] . id = (XtInputId) NULL;
  }

  /* Cancel any existing handler on the file */

  if (simpleXtFileTable [fd] . id != (XtInputId) NULL) {
    XtRemoveInput (simpleXtFileTable [fd] . id);
  }

  /* Record and establish the new handler */
  /* Note -- Assumes that Xt's condition bits are the same as ours. */

  simpleXtFileTable [fd] . proc = proc;
  simpleXtFileTable [fd] . clientData = clientData;
  if (simpleCurrentAppContext != NULL) {
    simpleXtFileTable [fd] . id =
      XtAppAddInput (simpleCurrentAppContext, fd, (XtPointer) mask,
		     (XtInputCallbackProc) simpleInputCallbackProc,
		     (XtPointer) (simpleXtFileTable + fd));
  } else {
    simpleXtFileTable [fd] . id =
      XtAddInput (fd, (XtPointer) mask,
		  (XtInputCallbackProc) simpleInputCallbackProc,
		  (XtPointer) (simpleXtFileTable + fd));
  }

#endif /* USE_XT */

#if !(defined( USE_TK ) || defined( USE_XT ))

  /* Neither Tk nor Xt -- Create the file handler for the server main loop */

  Simple_FileHandler * h;

  /* Find an existing handler for the same file, or create a new one */

  for (h = simpleFirstFileHandler; h; h = h -> next) {
    if (h -> fd == fd) break;
  }
  if (!h) {
    h = (Simple_FileHandler *) ckalloc (sizeof (Simple_FileHandler));
    h -> fd = fd;
    h -> next = simpleFirstFileHandler;
    simpleFirstFileHandler = h;
    if (h -> next) {
      h -> next -> prev = h;
    }
    h -> prev = (Simple_FileHandler *) NULL;
  }

  /* Fill in the mask, the handler procedure, and the client data */

  h -> mask = mask;
  h -> proc = proc;
  h -> clientData = clientData;

#endif /* USE_TK */

}

/*
 * simpleInputCallbackProc --
 *
 *	This function is defined only in an Xt environment.  It is invoked
 * from the Xt main event loop when a `ready' condition is detected
 * on a file.  It analyzes the condition, and calls the appropriate
 * event handler.
 */

#ifdef USE_XT

static void
simpleInputCallbackProc (client_data, source, id)
     XtPointer client_data;
     int * source;
     XtInputId * id;
{
  Simple_XtFileHandler * handler = (Simple_XtFileHandler *) client_data;

  fd_set infds, outfds, exceptfds;
  int mask;

  /* Our event handlers need to know which condition invoked them.  Determine
   * the condition. */

  FD_ZERO( &infds );
  FD_ZERO( &outfds );
  FD_ZERO( &exceptfds );
  FD_SET( *source, & infds );
  FD_SET( *source, & outfds );
  FD_SET( *source, & exceptfds );
  (void) select (*source + 1, & infds, & outfds, & exceptfds,
		 (struct timeval *) NULL);
  if (FD_ISSET( *source, & infds )) mask |= SIMPLE_READABLE;
  if (FD_ISSET( *source, & outfds )) mask |= SIMPLE_WRITABLE;
  if (FD_ISSET( *source, & exceptfds )) mask |= SIMPLE_EXCEPTION;

  /* Invoke the callback */

  (* (handler -> proc)) (handler -> clientData, mask);

}

#endif /* USE_XT */

/*
 * simpleDeleteFileHandler --
 *
 *	This function is invoked when the program is no longer interested in
 * handling events on a file.  It removes any outstanding handler on the file.
 *
 *	The function is a little tricky because a file handler on the file may
 * be active.  In a non-Tk environment, this is simple; the SIMPLE_DELETE flag
 * is set in the handler's mask, and the main loop deletes the handler once
 * it is quiescent.  In Tk, the event loop won't do that, so what we do
 * is set a DoWhenIdle to delete the handler and return.  The DoWhenIdle
 * gets called back from the Tk event loop at a time that the handler is
 * quiescent, and deletes the handler.
 */

void
simpleDeleteFileHandler (fd)
     int fd;
{

#ifdef USE_TK

  /* First of all, we have to zero the file's mask to avoid calling the same
     handler over again if the file is still ready. */
  Tk_CreateFileHandler (fd, 0, (Tk_FileProc *) simpleDeleteFileHandler1,
			(ClientData) NULL);
  Tk_DoWhenIdle ((Tk_IdleProc *) simpleDeleteFileHandler2,
		 (ClientData) fd);

#endif /* USE_TK */

#ifdef USE_XT

  if (simpleXtFileTable != NULL) {
    if (simpleXtFileTable [fd] . id != (XtInputId) NULL) {
      XtRemoveInput (simpleXtFileTable [fd] . id);
      simpleXtFileTable [fd] . id = (XtInputId) NULL;
    }
  }

#endif /* USE_XT */

#if !(defined( USE_TK ) || defined( USE_XT ))

  Simple_FileHandler * h;

  for (h = simpleFirstFileHandler; h != NULL; h = h -> next) {

    if (h -> fd == fd) {
      h -> mask |= SIMPLE_DELETE;
    }
  }
#endif /* neither USE_TK nor USE_XT */
}

#ifdef USE_TK

/* ARGSUSED */
static void
simpleDeleteFileHandler1 (clientData, mask)
     ClientData clientData;
     int mask;
{
  (void) fprintf (stderr, "in simpleDeleteFileHandler1: bug in tkEvent.c");
  abort ();
}

static void
simpleDeleteFileHandler2 (clientData)
     ClientData clientData;
{
  int fd = (int) clientData;

  Tk_DeleteFileHandler (fd);
}

#endif /* USE_TK */

/*
 * simpleFileSelect --
 *
 *	This is the function used in a non-Tk, non-Xt environment to multiplex
 * events in the servers.  It issues a select () that determines whether
 * I/O is pending on any of the file descriptors.  It blocks to await
 * such requests if the SIMPLE_WAIT bit is on in its argument, and processes
 * the requests if the SIMPLE_DO_EVENTS bit is set.
 *
 *	Return value is the number of events pending/processed.  If the return
 * value is zero, no events are pending.  If the return value is -1, either
 * (a) the select failed for some reason, or
 * (b) no servers or clients are running and the timer queue is empty.
 *	These are distinguished by a zero/non-zero value in errno.
 *
 * Modified to also look for timer events. In POLL mode, we simply check to see
 * if any of the timer due times have arrived. In WAIT mode, we wait, but instead of
 * forever, until the closest timer due time.
 *
 * NOTE - interp is just used for signal events; each file etc. handler
 * specifies it's own interpreter
 */

#if !(defined( USE_TK ) || defined( USE_XT ))

static int
simpleFileSelect (interp, flags)
     Tcl_Interp *interp;
     int flags;
{

  fd_set readfds, writefds, exceptfds;
  int width;
  Simple_FileHandler * h, * nexth;
  int fd;
  int mask;
  int unixStatus;
  static struct timeval notime = { 0 , 0 };
  struct timeval * waittime, curTime, timeUntilEvent;

  /* The following loop gets restarted if a signal interrupts the select
   * operation.
   */

  do {

    if (tcl_AsyncReady)
      /*
       * First check for any signals that may have been received
       * This is also done by standard TCL in Tcl_Eval but we need
       * to do it here also, since that might not be called if there
       * is currently on reason to process Tcl commands.
       * We do this at the top since the signal handlers could
       * remove file and timer handlers
       */
      if (Tcl_AsyncInvoke(interp, TCL_OK) != TCL_OK)
	simpleReportBackgroundError(interp);

    /* First off, destroy any file handlers that are marked for deletion. */

    for (h = simpleFirstFileHandler; h != NULL; h = nexth) {
      nexth = h -> next;

      if (h -> mask & SIMPLE_DELETE) {
	    
	if (h -> prev != NULL) {
	  h -> prev -> next = nexth;
	} else {
	  simpleFirstFileHandler = nexth;
	}
	if (nexth != NULL) {
	  nexth -> prev = h -> prev;
	}
	    
	ckfree ((char *) h);
      }
    }

    /* Return a negative number when all handlers have been destroyed */

    errno = 0;
    if (simpleFirstFileHandler == NULL && simpleTimerQueue == NULL)
      return -1;
  
    /* Set up the select() masks */

    width = 0;
    FD_ZERO( &readfds );
    FD_ZERO( &writefds );
    FD_ZERO( &exceptfds );

    for (h = simpleFirstFileHandler; h != NULL; h = nexth) {
      nexth = h -> next;
      if ((h -> mask & SIMPLE_DELETE) == 0) {
	fd = h -> fd;
	if (h -> mask & SIMPLE_READABLE) {
	  FD_SET( fd, &readfds );
	}
	if (h -> mask & SIMPLE_WRITABLE) {
	  FD_SET( fd, &writefds );
	}
	if (h -> mask & SIMPLE_EXCEPTION) {
	  FD_SET ( fd, &exceptfds );
	}
	if (fd >= width) {
	  width = fd + 1;
	}
      }
    }

    /* Do the select call. If it gets interrupted, try it again. */
    
    if (flags & SIMPLE_WAIT) {
      if (simpleTimerQueue) {
	/* We have some timer events - wait for most urgent */
	gettimeofday(&curTime, NULL);
	if (timercmp(&simpleTimerQueue->time, &curTime, <))
	  /* It's already overdue so just do poll */
	  waittime = &notime;
	else {
	  memcpy(&timeUntilEvent, &simpleTimerQueue->time, sizeof(struct timeval));
	  TIMEVAL_SUB(timeUntilEvent, curTime);
	  waittime = &timeUntilEvent;
	}
      } else
	waittime = (struct timeval *) NULL;
    } else {
      waittime = &notime;
    }
    unixStatus = select (width, &readfds, &writefds, &exceptfds, waittime);

    if (unixStatus >= 0 && (flags & SIMPLE_DO_EVENTS)) {

      /* Select was successful.  Invoke any handlers that may have */
      /* fired.  Note that invoking a handler might delete */
      /* it, or delete another, so we have to check for liveness. */
      /* May not have any filehandlers, just timers, but they take precedence if they fired */
      
      for (h = simpleFirstFileHandler; h != NULL; h = nexth) {
	nexth = h -> next;
	if ((h -> mask & SIMPLE_DELETE) == 0) {
	  mask = 0;
	  fd = h -> fd;
	  if (FD_ISSET( fd, &readfds )) {
	    mask |= SIMPLE_READABLE;
	  }
	  if (FD_ISSET( fd, &writefds )) {
	    mask |= SIMPLE_WRITABLE;
	  }
	  if (FD_ISSET( fd, &exceptfds )) {
	    mask |= SIMPLE_EXCEPTION;
	  }
	  if (mask != 0) {
	    (*(h -> proc)) (h -> clientData, mask);
	  }
	}
      }

      /*
       * Just grab one event at a time to avoid any infinite loop problems
       */
      if (simpleTimerQueue) {
	Simple_TimerHandler *timerPtr = simpleTimerQueue;

	gettimeofday(&curTime, NULL);

	if (timerPtr->time.tv_sec < curTime.tv_sec ||
	    (timerPtr->time.tv_sec == curTime.tv_sec &&
	     timerPtr->time.tv_usec <= curTime.tv_usec)) {
	  simpleTimerQueue = timerPtr->nextPtr;
	  (*timerPtr->proc)(timerPtr->clientData);
	  ckfree((char *) timerPtr);
	  unixStatus++;
	}
      }
    }
    
    /* Restart the loop if the select call was interrupted */

  } while (unixStatus < 0 && errno == EINTR);

  return unixStatus;
}

#endif /* neither USE_TK nor USE_XT */
