/* 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 and whenIdle calls.  If you need timers,
 * you're on your own.
 */

/* TkWin version 0.3 -- Tk for Windows
 * Copyright (C) 1994 K. K. Kubota
 * Mathematical Sciences Computing Facility, University of Kentucky,
 * Lexington, KY 40506, USA (e-mail: ken@ms.uky.edu)
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose is granted without fee, provided that the above copyright notice
 * and the following paragraph appear in all copies of this software
 * and supporting documentation.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY, even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  In no event shall the author be
 * liable to any party for direct, indirect, special, incidental, or
 * consequential damages arising out of the use of this software and its
 * documentation.
 */

char RCSid_simpleEvent [] =
  "$Header: /net/src3/btcl/tclTCP2.1/src/RCS/simpleEvent.c,v 1.2 1994/08/04 13:31:26 vanandel Exp $";
/* $Source: /net/src3/btcl/tclTCP2.1/src/RCS/simpleEvent.c,v $
 * $Log: simpleEvent.c,v $
 * Revision 1.2  1994/08/04  13:31:26  vanandel
 * change to allow sharing source with VxWorks version
 *
 * Revision 1.1  1994/03/09  18:46:39  vanandel
 * Initial revision
 *
 * Revision 1.1  1992/10/28  18:51:36  vanandel
 * Initial revision
 *
 * 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 TCL_MSDOS_PORT
#include "tkint.h"
#include "tkPort.h"
#include "simpleEvent.h"
#define USE_TK
#define TASK(t) t
#else
#include <errno.h>
#include <tclInt.h>
#include <msdos.h>

#ifdef TCL_VW
#include <vxWorks.h>
#include <systime.h>
#include <errnoLib.h>
#include <taskVarLib.h>
#include <logLib.h>
#include <string.h>
#include <selectLib.h>
#define TASK(t) Tcl_ctxt->t
#else
#define TASK(t) t

#endif

#include "tcl.h"


#ifdef USE_TK
#include <tk.h>
#endif

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

#include "simpleEvent.h"
#endif /* TCL_MSDOS_PORT */

/* Kernel calls not defined in any of the .h files */

#ifdef NOTDEF
extern void abort _ANSI_ARGS_((void));
#endif
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:
 */

#ifndef TCL_VW
static Simple_FileHandler * simpleFirstFileHandler
  = (Simple_FileHandler *) NULL;
#endif

#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 */

/* 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_((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 = TASK(simpleLastIdleHandler);
  if (handler -> prev == NULL) {
    TASK(simpleFirstIdleHandler) = handler;
  } else {
    handler -> prev -> next = handler;
  }
  TASK(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 = TASK(simpleFirstIdleHandler);

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

#endif
}

/*
 * 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.
 *	These are distinguished by a zero/non-zero value in errno.
 */

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

int simpleSelect (flags)
     int flags;
{
  errno = 0;
  return -1;
}

#else

int
simpleSelect (flags)
     int flags;
{
  int status;

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

  status = simpleFileSelect (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 (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 = TASK(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 = TASK(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) {
    TASK(simpleFirstIdleHandler) = h -> next;
  } else {
    h -> prev -> next = h -> next;
  }

  if (h -> next == NULL) {
    TASK(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
 *	Tcp_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 = TASK(simpleFirstFileHandler); h; h = h -> next) {
    if (h -> fd == fd) break;
  }
  if (!h) {
    h = (Simple_FileHandler *) ckalloc (sizeof (Simple_FileHandler));
    h -> fd = fd;
    h -> next = TASK(simpleFirstFileHandler);
    TASK(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 = TASK(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.
 *	These are distinguished by a zero/non-zero value in errno.
 */

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

static int
simpleFileSelect (flags)
     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;

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

  do {

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

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

      if (h -> mask & SIMPLE_DELETE) {
	    
	if (h -> prev != NULL) {
	  h -> prev -> next = nexth;
	} else {
	  TASK(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 (TASK(simpleFirstFileHandler) == NULL) return -1;
  
    /* Set up the select() masks */

    width = -1;
    FD_ZERO( &readfds );
    FD_ZERO( &writefds );
    FD_ZERO( &exceptfds );

    for (h = TASK(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) {
      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. */
      
      for (h = TASK(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);
	  }
	}
      }
    }
    
    /* Restart the loop if the select call was interrupted */

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

  return unixStatus;
}

#endif /* neither USE_TK nor USE_XT */
