/* 
 * tkEvent.c --
 *
 *	This file provides basic event-managing facilities,
 *	whereby procedure callbacks may be attached to
 *	certain events.
 *
 * Copyright (c) 1990-1993 The Regents of the University of California.
 * All rights reserved.
 *
 * Permission is hereby granted, without written agreement and without
 * license or royalty fees, to use, copy, modify, and distribute this
 * software and its documentation for any purpose, provided that the
 * above copyright notice and the following two paragraphs appear in
 * all copies of this software.
 * 
 * IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY FOR
 * DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
 * OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
 * CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS FOR A PARTICULAR PURPOSE.  THE SOFTWARE PROVIDED HEREUNDER IS
 * ON AN "AS IS" BASIS, AND THE UNIVERSITY OF CALIFORNIA HAS NO OBLIGATION TO
 * PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
 */
/*
 * Copyright (c) 1994 Software Research Associates, Inc.
 *
 * 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 and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Software Research Associates not be
 * used in advertising or publicity pertaining to distribution of the
 * software without specific, written prior permission.  Software Research
 * Associates makes no representations about the suitability of this software
 * for any purpose.  It is provided "as is" without express or implied
 * warranty.
 */

#ifndef lint
static char rcsid[] = "$Header: /user6/ouster/wish/RCS/tkEvent.c,v 1.77 93/10/07 09:59:04 ouster Exp $ SPRITE (Berkeley)";
#endif

#ifdef _WINDOWS
#  define WIN32_LEAN_AND_MEAN
#  include "wndproc.h"
#  include <winsock.h>
#  undef WIN32_LEAN_AND_MEAN
#endif /* _WINDOWS */
#include "tkConfig.h"
#include "tkInt.h"
#include <errno.h>
#include <signal.h>
#include "util.h"

/*
 * For each timer callback that's pending, there is one record
 * of the following type, chained together in a list sorted by
 * time (earliest event first).
 */

typedef struct TimerEvent {
    struct timeval time;	/* When timer is to fire. */
    void (*proc)  _ANSI_ARGS_((ClientData clientData));
				/* Procedure to call. */
    ClientData clientData;	/* Argument to pass to proc. */
    Tk_TimerToken token;	/* Identifies event so it can be
				 * deleted. */
    struct TimerEvent *nextPtr;	/* Next event in queue, or NULL for
				 * end of queue. */
    unsigned char set;		/* Has a timer been set for this event */
} TimerEvent;

static TimerEvent *timerQueue;	/* First event in queue. */

/*
 * The information below is used to provide read, write, and
 * exception masks to select during calls to Tk_DoOneEvent.
 */

static int readCount;		/* Number of files for which we */
static int writeCount;		/* care about each event type. */
static int exceptCount;

static fd_set masks[3];
				/* Integer array containing official
				 * copies of the three sets of
				 * masks. */
static fd_set ready[3];
				/* Temporary copy of masks, passed
				 * to select and modified by kernel
				 * to indicate which files are
				 * actually ready. */
static fd_set *readPtr;		/* Pointers to the portions of */
static fd_set *writePtr;	/* *readyPtr for reading, writing, */
static fd_set *exceptPtr;	/* and excepting.  Will be NULL if
				 * corresponding count (e.g. readCount
				 * is zero. */

static int numFds = 0;		/* Number of valid bits in mask
				 * arrays (this value is passed
				 * to select). */

/*
 * For each file registered in a call to Tk_CreateFileHandler,
 * and for each display that's currently active, there is one
 * record of the following type.  All of these records are
 * chained together into a single list.
 */

typedef struct FileEvent {
    SOCKET fd;			/* Descriptor number for this file. */
    int isDisplay;		/* Non-zero means that this file descriptor
				 * corresponds to a display and should be
				 * treated specially. */
#ifndef _WINDOWS
    fd_mask *readPtr;		/* Pointer to word in ready array
				 * for this file's read mask bit. */
    fd_mask *writePtr;		/* Same for write mask bit. */
    fd_mask *exceptPtr;		/* Same for except mask bit. */
#endif /* !_WINDOWS */
    int mask;			/* Value to AND with mask word to
				 * select just this file's bit. */
    int lEvent;
    void (*proc)  _ANSI_ARGS_((ClientData clientData, int mask));
				/* Procedure to call.  NULL means
				 * this is a display. */
    ClientData clientData;	/* Argument to pass to proc.  For
				 * displays, this is a (Display *). */
    struct FileEvent *nextPtr;	/* Next in list of all files we
				 * care about (NULL for end of
				 * list). */
} FileEvent;

static FileEvent *fileList;	/* List of all file events. */

/*
 * There is one of the following structures for each of the
 * handlers declared in a call to Tk_DoWhenIdle.  All of the
 * currently-active handlers are linked together into a list.
 */

typedef struct IdleHandler {
    void (*proc)  _ANSI_ARGS_((ClientData clientData));
				/* Procedure to call. */
    ClientData clientData;	/* Value to pass to proc. */
    int generation;		/* Used to distinguish older handlers from
				 * recently-created ones. */
    struct IdleHandler *nextPtr;/* Next in list of active handlers. */
} IdleHandler;

static IdleHandler *idleList = NULL;
				/* First in list of all idle handlers. */
static IdleHandler *lastIdlePtr = NULL;
				/* Last in list (or NULL for empty list). */
static int idleGeneration = 0;	/* Used to fill in the "generation" fields
				 * of IdleHandler structures.  Increments
				 * each time Tk_DoOneEvent starts calling
				 * idle handlers, so that all old handlers
				 * can be called without calling any of the
				 * new ones created by old ones. */

/*
 * There's a potential problem if a handler is deleted while it's
 * current (i.e. its procedure is executing), since Tk_HandleEvent
 * will need to read the handler's "nextPtr" field when the procedure
 * returns.  To handle this problem, structures of the type below
 * indicate the next handler to be processed for any (recursively
 * nested) dispatches in progress.  The nextHandler fields get
 * updated if the handlers pointed to are deleted.  Tk_HandleEvent
 * also needs to know if the entire window gets deleted;  the winPtr
 * field is set to zero if that particular window gets deleted.
 */

typedef struct InProgress {
    XEvent *eventPtr;		 /* Event currently being handled. */
    TkWindow *winPtr;		 /* Window for event.  Gets set to None if
				  * window is deleted while event is being
				  * handled. */
    TkEventHandler *nextHandler; /* Next handler in search. */
    struct InProgress *nextPtr;	 /* Next higher nested search. */
} InProgress;

static InProgress *pendingPtr = NULL;
				/* Topmost search in progress, or
				 * NULL if none. */

/*
 * For each call to Tk_CreateGenericHandler, an instance of the following
 * structure will be created.  All of the active handlers are linked into a
 * list.
 */

typedef struct GenericHandler {
    Tk_GenericProc *proc;	/* Procedure to dispatch on all X events. */
    ClientData clientData;	/* Client data to pass to procedure. */
    int deleteFlag;		/* Flag to set when this handler is deleted. */
    struct GenericHandler *nextPtr;
				/* Next handler in list of all generic
				 * handlers, or NULL for end of list. */
} GenericHandler;

static GenericHandler *genericList = NULL;
				/* First handler in the list, or NULL. */
static GenericHandler *lastGenericPtr = NULL;
				/* Last handler in list. */

/*
 * There's a potential problem if Tk_HandleEvent is entered recursively.
 * A handler cannot be deleted physically until we have returned from
 * calling it.  Otherwise, we're looking at unallocated memory in advancing to
 * its `next' entry.  We deal with the problem by using the `delete flag' and
 * deleting handlers only when it's known that there's no handler active.
 *
 * The following variable has a non-zero value when a handler is active.
 */

static int genericHandlersActive = 0;

/*
 * Array of event masks corresponding to each X event:
 */

static unsigned long eventMasks[] = {
    0,
    0,
    KeyPressMask,			/* KeyPress */
    KeyReleaseMask,			/* KeyRelease */
    ButtonPressMask,			/* ButtonPress */
    ButtonReleaseMask,			/* ButtonRelease */
    PointerMotionMask|PointerMotionHintMask|ButtonMotionMask
	    |Button1MotionMask|Button2MotionMask|Button3MotionMask
	    |Button4MotionMask|Button5MotionMask,
					/* MotionNotify */
    EnterWindowMask,			/* EnterNotify */
    LeaveWindowMask,			/* LeaveNotify */
    FocusChangeMask,			/* FocusIn */
    FocusChangeMask,			/* FocusOut */
    KeymapStateMask,			/* KeymapNotify */
    ExposureMask,			/* Expose */
    ExposureMask,			/* GraphicsExpose */
    ExposureMask,			/* NoExpose */
    VisibilityChangeMask,		/* VisibilityNotify */
    SubstructureNotifyMask,		/* CreateNotify */
    StructureNotifyMask,		/* DestroyNotify */
    StructureNotifyMask,		/* UnmapNotify */
    StructureNotifyMask,		/* MapNotify */
    SubstructureRedirectMask,		/* MapRequest */
    StructureNotifyMask,		/* ReparentNotify */
    StructureNotifyMask,		/* ConfigureNotify */
    SubstructureRedirectMask,		/* ConfigureRequest */
    StructureNotifyMask,		/* GravityNotify */
    ResizeRedirectMask,			/* ResizeRequest */
    StructureNotifyMask,		/* CirculateNotify */
    SubstructureRedirectMask,		/* CirculateRequest */
    PropertyChangeMask,			/* PropertyNotify */
    0,					/* SelectionClear */
    0,					/* SelectionRequest */
    0,					/* SelectionNotify */
    ColormapChangeMask,			/* ColormapNotify */
    0,					/* ClientMessage */
    0,					/* Mapping Notify */
#ifdef _WINDOWS
    0,					/* Activate App */
    ColormapChangeMask,			/* System color change */
#endif
};

/*
 * If someone has called Tk_RestrictEvents, the information below
 * keeps track of it.
 */

static Bool (*restrictProc)  _ANSI_ARGS_((Display *display, XEvent *eventPtr,
    char *arg));		/* Procedure to call.  NULL means no
				 * restrictProc is currently in effect. */
static char *restrictArg;	/* Argument to pass to restrictProc. */

/*
 * The following array keeps track of the last TK_NEVENTS X events, for
 * memory dump analysis.  The tracing is only done if tkEventDebug is set
 * to 1.
 */

#define TK_NEVENTS 32
static XEvent eventTrace[TK_NEVENTS];
static int traceIndex = 0;
int tkEventDebug = 0;

/*
 *--------------------------------------------------------------
 *
 * Tk_CreateEventHandler --
 *
 *	Arrange for a given procedure to be invoked whenever
 *	events from a given class occur in a given window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	From now on, whenever an event of the type given by
 *	mask occurs for token and is processed by Tk_HandleEvent,
 *	proc will be called.  See the manual entry for details
 *	of the calling sequence and return value for proc.
 *
 *--------------------------------------------------------------
 */

void
Tk_CreateEventHandler(token, mask, proc, clientData)
    Tk_Window token;		/* Token for window in which to
				 * create handler. */
    unsigned long mask;		/* Events for which proc should
				 * be called. */
    Tk_EventProc *proc;		/* Procedure to call for each
				 * selected event */
    ClientData clientData;	/* Arbitrary data to pass to proc. */
{
    register TkEventHandler *handlerPtr;
    register TkWindow *winPtr = (TkWindow *) token;
    int found;

    /*
     * Skim through the list of existing handlers to (a) compute the
     * overall event mask for the window (so we can pass this new
     * value to the X system) and (b) see if there's already a handler
     * declared with the same callback and clientData (if so, just
     * change the mask).  If no existing handler matches, then create
     * a new handler.
     */

    found = 0;
    if (winPtr->handlerList == NULL) {
	handlerPtr = (TkEventHandler *) ckalloc(
		(unsigned) sizeof(TkEventHandler));
	winPtr->handlerList = handlerPtr;
	goto initHandler;
    } else {
	for (handlerPtr = winPtr->handlerList; ;
		handlerPtr = handlerPtr->nextPtr) {
	    if ((handlerPtr->proc == proc)
		    && (handlerPtr->clientData == clientData)) {
		handlerPtr->mask = mask;
		found = 1;
	    }
	    if (handlerPtr->nextPtr == NULL) {
		break;
	    }
	}
    }

    /*
     * Create a new handler if no matching old handler was found.
     */

    if (!found) {
	handlerPtr->nextPtr = (TkEventHandler *)
		ckalloc(sizeof(TkEventHandler));
	handlerPtr = handlerPtr->nextPtr;
	initHandler:
	handlerPtr->mask = mask;
	handlerPtr->proc = proc;
	handlerPtr->clientData = clientData;
	handlerPtr->nextPtr = NULL;
    }

    /*
     * No need to call XSelectInput:  Tk always selects on all events
     * for all windows (needed to support bindings on classes and "all").
     */
}

/*
 *--------------------------------------------------------------
 *
 * Tk_DeleteEventHandler --
 *
 *	Delete a previously-created handler.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	If there existed a handler as described by the
 *	parameters, the handler is deleted so that proc
 *	will not be invoked again.
 *
 *--------------------------------------------------------------
 */

void
Tk_DeleteEventHandler(token, mask, proc, clientData)
    Tk_Window token;		/* Same as corresponding arguments passed */
    unsigned long mask;		/* previously to Tk_CreateEventHandler. */
    Tk_EventProc *proc;
    ClientData clientData;
{
    register TkEventHandler *handlerPtr;
    register InProgress *ipPtr;
    TkEventHandler *prevPtr;
    register TkWindow *winPtr = (TkWindow *) token;

    /*
     * Find the event handler to be deleted, or return
     * immediately if it doesn't exist.
     */

    for (handlerPtr = winPtr->handlerList, prevPtr = NULL; ;
	    prevPtr = handlerPtr, handlerPtr = handlerPtr->nextPtr) {
	if (handlerPtr == NULL) {
	    return;
	}
	if ((handlerPtr->mask == mask) && (handlerPtr->proc == proc)
		&& (handlerPtr->clientData == clientData)) {
	    break;
	}
    }

    /*
     * If Tk_HandleEvent is about to process this handler, tell it to
     * process the next one instead.
     */

    for (ipPtr = pendingPtr; ipPtr != NULL; ipPtr = ipPtr->nextPtr) {
	if (ipPtr->nextHandler == handlerPtr) {
	    ipPtr->nextHandler = handlerPtr->nextPtr;
	}
    }

    /*
     * Free resources associated with the handler.
     */

    if (prevPtr == NULL) {
	winPtr->handlerList = handlerPtr->nextPtr;
    } else {
	prevPtr->nextPtr = handlerPtr->nextPtr;
    }
    ckfree((char *) handlerPtr);


    /*
     * No need to call XSelectInput:  Tk always selects on all events
     * for all windows (needed to support bindings on classes and "all").
     */
}

/*--------------------------------------------------------------
 *
 * Tk_CreateGenericHandler --
 *
 *	Register a procedure to be called on each X event, regardless
 *	of display or window.  Generic handlers are useful for capturing
 *	events that aren't associated with windows, or events for windows
 *	not managed by Tk.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	From now on, whenever an X event is given to Tk_HandleEvent,
 *	invoke proc, giving it clientData and the event as arguments.
 *
 *--------------------------------------------------------------
 */

void
Tk_CreateGenericHandler(proc, clientData)
     Tk_GenericProc *proc;	/* Procedure to call on every event. */
     ClientData clientData;	/* One-word value to pass to proc. */
{
    GenericHandler *handlerPtr;
    
    handlerPtr = (GenericHandler *) ckalloc (sizeof (GenericHandler));
    
    handlerPtr->proc = proc;
    handlerPtr->clientData = clientData;
    handlerPtr->deleteFlag = 0;
    handlerPtr->nextPtr = NULL;
    if (genericList == NULL) {
	genericList = handlerPtr;
    } else {
	lastGenericPtr->nextPtr = handlerPtr;
    }
    lastGenericPtr = handlerPtr;
}

/*
 *--------------------------------------------------------------
 *
 * Tk_DeleteGenericHandler --
 *
 *	Delete a previously-created generic handler.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	If there existed a handler as described by the parameters,
 *	that handler is logically deleted so that proc will not be
 *	invoked again.  The physical deletion happens in the event
 *	loop in Tk_HandleEvent.
 *
 *--------------------------------------------------------------
 */

void
Tk_DeleteGenericHandler(proc, clientData)
     Tk_GenericProc *proc;
     ClientData clientData;
{
    GenericHandler * handler;
    
    for (handler = genericList; handler; handler = handler->nextPtr) {
	if ((handler->proc == proc) && (handler->clientData == clientData)) {
	    handler->deleteFlag = 1;
	}
    }
}

/*
 *--------------------------------------------------------------
 *
 * Tk_HandleEvent --
 *
 *	Given an event, invoke all the handlers that have
 *	been registered for the event.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Depends on the handlers.
 *
 *--------------------------------------------------------------
 */

void
Tk_HandleEvent(eventPtr)
    XEvent *eventPtr;		/* Event to dispatch. */
{
    register TkEventHandler *handlerPtr;
    register GenericHandler *genericPtr;
    register GenericHandler *genPrevPtr;
    TkWindow *winPtr;
    register unsigned long mask;
    InProgress ip;
    Window handlerWindow;

    /*
     * First off, look for a special trigger event left around by the
     * grab module.  If it's found, call the grab module and discard
     * the event.
     */

    if ((eventPtr->xany.type == -1) && (eventPtr->xany.window == None)) {
	TkGrabTriggerProc(eventPtr);
	return;
    }

    /* 
     * Next, invoke all the generic event handlers (those that are
     * invoked for all events).  If a generic event handler reports that
     * an event is fully processed, go no further.
     */

    for (genPrevPtr = NULL, genericPtr = genericList;  genericPtr != NULL; ) {
	if (genericPtr->deleteFlag) {
	    if (!genericHandlersActive) {
		GenericHandler *tmpPtr;

		/*
		 * This handler needs to be deleted and there are no
		 * calls pending through the handler, so now is a safe
		 * time to delete it.
		 */

		tmpPtr = genericPtr->nextPtr;
		if (genPrevPtr == NULL) {
		    genericList = tmpPtr;
		} else {
		    genPrevPtr->nextPtr = tmpPtr;
		}
		if (tmpPtr == NULL) {
		    lastGenericPtr = genPrevPtr;
		}
		(void) ckfree((char *) genericPtr);
		genericPtr = tmpPtr;
		continue;
	    }
	} else {
	    int done;

	    genericHandlersActive++;
	    done = (*genericPtr->proc)(genericPtr->clientData, eventPtr);
	    genericHandlersActive--;
	    if (done) {
		return;
	    }
	}
	genPrevPtr = genericPtr;
	genericPtr = genPrevPtr->nextPtr;
    }

    /*
     * If the event is a MappingNotify event, find its display and
     * refresh the keyboard mapping information for the display.
     * After that there's nothing else to do with the event, so just
     * quit.
     */

#ifndef _WINDOWS
    if (eventPtr->type == MappingNotify) {
	TkDisplay *dispPtr;

	for (dispPtr = tkDisplayList; dispPtr != NULL;
		dispPtr = dispPtr->nextPtr) {
	    if (dispPtr->display != eventPtr->xmapping.display) {
		continue;
	    }
	    XRefreshKeyboardMapping(&eventPtr->xmapping);
	    dispPtr->bindInfoStale = 1;
	    break;
	}
	return;
    }
#endif /* !_WINDOWS */

    /*
     * Events selected by StructureNotify look the same as those
     * selected by SubstructureNotify;  the only difference is
     * whether the "event" and "window" fields are the same.
     * Check it out and convert StructureNotify to
     * SubstructureNotify if necessary.
     */

    handlerWindow = eventPtr->xany.window;
    mask = eventMasks[eventPtr->xany.type];
    if (mask == StructureNotifyMask) {
	if (eventPtr->xmap.event != eventPtr->xmap.window) {
	    mask = SubstructureNotifyMask;
	    handlerWindow = eventPtr->xmap.event;
	}
    }
    if (XFindContext(eventPtr->xany.display, handlerWindow,
	    tkWindowContext, (caddr_t *) &winPtr) != 0) {

#ifndef _WINDOWS
	/*
	 * There isn't a TkWindow structure for this window.
	 * However, if the event is a PropertyNotify event then call
	 * the selection manager (it deals beneath-the-table with
	 * certain properties).
	 */

	if (eventPtr->type == PropertyNotify) {
	    TkSelPropProc(eventPtr);
	}
#endif /* !_WINDOWS */
	return;
    }

    /*
     * Call focus-related code to look at FocusIn, FocusOut, Enter,
     * and Leave events;  depending on its return value, ignore the
     * event.
     */

    if ((mask & (FocusChangeMask|EnterWindowMask|LeaveWindowMask))
	    && !TkFocusFilterEvent(winPtr, eventPtr)) {
	return;
    }

    /*
     * Redirect KeyPress and KeyRelease events to the focus window,
     * or ignore them entirely if there is no focus window.  Map the
     * x and y coordinates to make sense in the context of the focus
     * window, if possible (make both -1 if the map-from and map-to
     * windows don't share the same screen).
     */

    if (mask & (KeyPressMask|KeyReleaseMask)) {
	TkWindow *focusPtr;
	int winX, winY, focusX, focusY;

	winPtr->dispPtr->lastEventTime = eventPtr->xkey.time;
	if (winPtr->mainPtr->focusPtr == NULL) {
	    return;
	}
	focusPtr = winPtr->mainPtr->focusPtr;
	if ((focusPtr->display != winPtr->display)
		|| (focusPtr->screenNum != winPtr->screenNum)) {
	    eventPtr->xkey.x = -1;
	    eventPtr->xkey.y = -1;
	} else {
	    Tk_GetRootCoords((Tk_Window) winPtr, &winX, &winY);
	    Tk_GetRootCoords((Tk_Window) focusPtr, &focusX, &focusY);
	    eventPtr->xkey.x -= focusX - winX;
	    eventPtr->xkey.y -= focusY - winY;
	}
	eventPtr->xkey.window = focusPtr->window;
	winPtr = focusPtr;
    }

    /*
     * Call a grab-related procedure to do special processing on
     * pointer events.
     */

    if (mask & (ButtonPressMask|ButtonReleaseMask|PointerMotionMask
	    |EnterWindowMask|LeaveWindowMask)) {
	if (mask & (ButtonPressMask|ButtonReleaseMask)) {
	    winPtr->dispPtr->lastEventTime = eventPtr->xbutton.time;
	} else if (mask & PointerMotionMask) {
	    winPtr->dispPtr->lastEventTime = eventPtr->xmotion.time;
	} else {
	    winPtr->dispPtr->lastEventTime = eventPtr->xcrossing.time;
	}
	if (TkPointerEvent(eventPtr, winPtr) == 0) {
	    return;
	}
    }

    /*
     * For events where it hasn't already been done, update the current
     * time in the display.
     */

    if (eventPtr->type == PropertyNotify) {
	winPtr->dispPtr->lastEventTime = eventPtr->xproperty.time;
    }

#if 0
#ifdef _WINDOWS
    if (eventPtr->type == SyscolorNotify) {
	TkWindow *topPtr;
	if (XFindContext(eventPtr->xany.display, handlerWindow,
			 tkWindowContext, (caddr_t *) &topPtr) == 0) {
	    if (Tk_Parent(topPtr) == NULL) {
		TkUpdateSystemColors(eventPtr->xany.display);
	    }
	}
    }
#endif /* !_WINDOWS */
#endif

    /*
     * There's a potential interaction here with Tk_DeleteEventHandler.
     * Read the documentation for pendingPtr.
     */

    ip.eventPtr = eventPtr;
    ip.winPtr = winPtr;
    ip.nextHandler = NULL;
    ip.nextPtr = pendingPtr;
    pendingPtr = &ip;
    if (mask == 0) {
	if ((eventPtr->type == SelectionClear)
		|| (eventPtr->type == SelectionRequest)
		|| (eventPtr->type == SelectionNotify)) {
	    TkSelEventProc((Tk_Window) winPtr, eventPtr);
	} else if ((eventPtr->type == ClientMessage)
		&& (eventPtr->xclient.message_type ==
		    Tk_InternAtom((Tk_Window) winPtr, "WM_PROTOCOLS"))) {
	    TkWmProtocolEventProc(winPtr, eventPtr);
	}
    } else {
	for (handlerPtr = winPtr->handlerList; handlerPtr != NULL; ) {
	    if ((handlerPtr->mask & mask) != 0) {
		ip.nextHandler = handlerPtr->nextPtr;
		(*(handlerPtr->proc))(handlerPtr->clientData, eventPtr);
		handlerPtr = ip.nextHandler;
	    } else {
		handlerPtr = handlerPtr->nextPtr;
	    }
	}

	/*
	 * Pass the event to the "bind" command mechanism.  But, don't
	 * do this for SubstructureNotify events.  The "bind" command
	 * doesn't support them anyway, and it's easier to filter out
	 * these events here than in the lower-level procedures.
	 */

	if ((ip.winPtr != None) && (mask != SubstructureNotifyMask)) {
	    TkBindEventProc(winPtr, eventPtr);
	}
    }
    pendingPtr = ip.nextPtr;
}

/*
 *--------------------------------------------------------------
 *
 * Tk_CreateFileHandler --
 *
 *	Arrange for a given procedure to be invoked whenever
 *	a given file becomes readable or writable.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	From now on, whenever the I/O channel given by fd becomes
 *	ready in the way indicated by mask, proc will be invoked.
 *	See the manual entry for details on the calling sequence
 *	to proc.  If fd is already registered then the old mask
 *	and proc and clientData values will be replaced with
 *	new ones.
 *
 *--------------------------------------------------------------
 */

void
Tk_CreateFileHandler(fd, mask, proc, clientData)
    SOCKET fd;			/* Integer identifier for stream. */
    int mask;			/* OR'ed combination of TK_READABLE,
				 * TK_WRITABLE, and TK_EXCEPTION:
				 * indicates conditions under which
				 * proc should be called.  TK_IS_DISPLAY
				 * indicates that this is a display and that
				 * clientData is the (Display *) for it,
				 * and that events should be handled
				 * automatically.*/
    Tk_FileProc *proc;		/* Procedure to call for each
				 * selected event. */
    ClientData clientData;	/* Arbitrary data to pass to proc. */
{
    register FileEvent *filePtr;
    long lEvent = 0;

#ifndef _WINDOWS
    if (fd >= OPEN_MAX) {
	panic("Tk_CreatefileHandler can't handle file id %d", fd);
    }
#endif /* !_WINDOWS */

    /*
     * Make sure the file isn't already registered.  Create a
     * new record in the normal case where there's no existing
     * record.
     */

    for (filePtr = fileList; filePtr != NULL;
	    filePtr = filePtr->nextPtr) {
	if (filePtr->fd == fd) {
	    break;
	}
    }
    if (filePtr == NULL) {
	filePtr = (FileEvent *) ckalloc(sizeof(FileEvent));
	filePtr->fd = fd;
	filePtr->isDisplay = 0;
	filePtr->nextPtr = fileList;
	fileList = filePtr;
    } else if (fd != INVALID_SOCKET) {
	if (FD_ISSET(fd, &masks[0])) {
	    FD_CLR(fd, &masks[0]);
	    readCount--;
	    readPtr = (readCount == 0) ? (fd_set *) NULL : &ready[0];
	}
	if (FD_ISSET(fd, &masks[1])) {
	    FD_CLR(fd, &masks[1]);
	    writeCount--;
	    writePtr = (writeCount == 0) ? (fd_set *) NULL : &ready[1];
	}
	if (FD_ISSET(fd, &masks[2])) {
	    FD_CLR(fd, &masks[2]);
	    exceptCount--;
	    exceptPtr = (exceptCount == 0) ? (fd_set *) NULL : &ready[2];
	}
    }

    if (mask & TK_IS_DISPLAY) {
	filePtr->isDisplay = 1;
	return;
    } else {
	filePtr->isDisplay = 0;
    }

    /*
     * The remainder of the initialization below is done
     * regardless of whether or not this is a new record
     * or a modification of an old one.
     */

    if (fd != INVALID_SOCKET) {
	if (mask & TK_READABLE) {
	    FD_SET(fd, &masks[0]);
	    readCount++;
	    lEvent |= FD_READ;
	    lEvent |= FD_ACCEPT;
	}
	readPtr = (readCount == 0) ? (fd_set *) NULL : &ready[0];

	if (mask & TK_WRITABLE) {
	    FD_SET(fd, &masks[1]);
	    writeCount++;
	    lEvent |= FD_WRITE;
	}
	writePtr = (writeCount == 0) ? (fd_set *) NULL : &ready[1];

	if (mask & TK_EXCEPTION) {
	    FD_SET(fd, &masks[2]);
	    exceptCount++;
	    lEvent |= FD_OOB;
	}
	exceptPtr = (exceptCount == 0) ? (fd_set *) NULL : &ready[2];

	filePtr->lEvent = lEvent;

	if (get_useSocket()) {
	    if (WSAAsyncSelect(fd, GetPostWindow(), SOCKETNOTIFY, lEvent)) {
		panic("Unable to do WSAAsyncSelect\n");
	    }
	}
    }
    filePtr->mask = mask;
    filePtr->proc = proc;
    filePtr->clientData = clientData;

    numFds++;
}

/*
 *--------------------------------------------------------------
 *
 * Tk_DeleteFileHandler --
 *
 *	Cancel a previously-arranged callback arrangement for
 *	a file.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	If a callback was previously registered on fd, remove it.
 *
 *--------------------------------------------------------------
 */

void
Tk_DeleteFileHandler(fd)
    SOCKET fd;			/* Stream id for which to remove
				 * callback procedure. */
{
    register FileEvent *filePtr;
    FileEvent *prevPtr;

    /*
     * Find the entry for the given file (and return if there
     * isn't one).
     */

    for (prevPtr = NULL, filePtr = fileList; ;
	    prevPtr = filePtr, filePtr = filePtr->nextPtr) {
	if (filePtr == NULL) {
	    return;
	}
	if (filePtr->fd == fd) {
	    break;
	}
    }

    /*
     * Clean up information in the callback record.
     */

    if (fd != INVALID_SOCKET) {
	if (FD_ISSET(fd, &masks[0])) {
	    FD_CLR(fd, &masks[0]);
	    readCount--;
	    readPtr = (readCount == 0) ? (fd_set *) NULL : &ready[0];
	}
	if (FD_ISSET(fd, &masks[1])) {
	    FD_CLR(fd, &masks[1]);
	    writeCount--;
	    writePtr = (writeCount == 0) ? (fd_set *) NULL : &ready[1];
	}
	if (FD_ISSET(fd, &masks[2])) {
	    FD_CLR(fd, &masks[2]);
	    exceptCount--;
	    exceptPtr = (exceptCount == 0) ? (fd_set *) NULL : &ready[2];
	}

	if (get_useSocket()) {
	    if (! filePtr->isDisplay) {
		WSAAsyncSelect(fd, GetPostWindow(), 0, 0);
	    }
	}
    }

    if (prevPtr == NULL) {
	fileList = filePtr->nextPtr;
    } else {
	prevPtr->nextPtr = filePtr->nextPtr;
    }
    ckfree((char *) filePtr);

    /*
     * Recompute numFds.
     */

    numFds--;
}

/*
 *--------------------------------------------------------------
 *
 * Tk_CreateTimerHandler --
 *
 *	Arrange for a given procedure to be invoked at a particular
 *	time in the future.
 *
 * Results:
 *	The return value is a token for the timer event, which
 *	may be used to delete the event before it fires.
 *
 * Side effects:
 *	When milliseconds have elapsed, proc will be invoked
 *	exactly once.
 *
 *--------------------------------------------------------------
 */

Tk_TimerToken
Tk_CreateTimerHandler(milliseconds, proc, clientData)
    int milliseconds;		/* How many milliseconds to wait
				 * before invoking proc. */
    Tk_TimerProc *proc;		/* Procedure to invoke. */
    ClientData clientData;	/* Arbitrary data to pass to proc. */
{
    register TimerEvent *timerPtr, *tPtr2, *prevPtr;
    static int id = 0;

    timerPtr = (TimerEvent *) ckalloc(sizeof(TimerEvent));

    /*
     * Compute when the event should fire.
     */

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

    /*
     * Fill in other fields for the event.
     */

    timerPtr->proc = proc;
    timerPtr->clientData = clientData;
    id++;
    timerPtr->token = (Tk_TimerToken) id;
    timerPtr->set = 0;

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

    for (tPtr2 = timerQueue, 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 == NULL) {
	if (timerQueue) {
	    timerQueue->set = 0;
	}
	timerPtr->nextPtr = timerQueue;
	timerQueue = timerPtr;
    } else {
	timerPtr->nextPtr = prevPtr->nextPtr;
	prevPtr->nextPtr = timerPtr;
    }
    return timerPtr->token;
}

/*
 *--------------------------------------------------------------
 *
 * Tk_DeleteTimerHandler --
 *
 *	Delete a previously-registered timer handler.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Destroy the timer callback identified by TimerToken,
 *	so that its associated procedure will not be called.
 *	If the callback has already fired, or if the given
 *	token doesn't exist, then nothing happens.
 *
 *--------------------------------------------------------------
 */

void
Tk_DeleteTimerHandler(token)
    Tk_TimerToken token;	/* Result previously returned by
				 * Tk_DeleteTimerHandler. */
{
    register TimerEvent *timerPtr, *prevPtr;

    for (timerPtr = timerQueue, prevPtr = NULL; timerPtr != NULL;
	    prevPtr = timerPtr, timerPtr = timerPtr->nextPtr) {
	if (timerPtr->token != token) {
	    continue;
	}
	if (prevPtr == NULL) {
	    timerQueue = timerPtr->nextPtr;
	} else {
	    prevPtr->nextPtr = timerPtr->nextPtr;
	}
	ckfree((char *) timerPtr);
	return;
    }
}

/*
 *--------------------------------------------------------------
 *
 * Tk_DoWhenIdle --
 *
 *	Arrange for proc to be invoked the next time the
 *	system is idle (i.e., just before the next time
 *	that Tk_DoOneEvent would have to wait for something
 *	to happen).
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Proc will eventually be called, with clientData
 *	as argument.  See the manual entry for details.
 *
 *--------------------------------------------------------------
 */

void
Tk_DoWhenIdle(proc, clientData)
    Tk_IdleProc *proc;		/* Procedure to invoke. */
    ClientData clientData;	/* Arbitrary value to pass to proc. */
{
    register IdleHandler *idlePtr;

    idlePtr = (IdleHandler *) ckalloc(sizeof(IdleHandler));
    idlePtr->proc = proc;
    idlePtr->clientData = clientData;
    idlePtr->generation = idleGeneration;
    idlePtr->nextPtr = NULL;
    if (lastIdlePtr == NULL) {
	idleList = idlePtr;
    } else {
	lastIdlePtr->nextPtr = idlePtr;
    }
    lastIdlePtr = idlePtr;
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_CancelIdleCall --
 *
 *	If there are any when-idle calls requested to a given procedure
 *	with given clientData, cancel all of them.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	If the proc/clientData combination were on the when-idle list,
 *	they are removed so that they will never be called.
 *
 *----------------------------------------------------------------------
 */

void
Tk_CancelIdleCall(proc, clientData)
    Tk_IdleProc *proc;		/* Procedure that was previously registered. */
    ClientData clientData;	/* Arbitrary value to pass to proc. */
{
    register IdleHandler *idlePtr, *prevPtr;
    IdleHandler *nextPtr;

    for (prevPtr = NULL, idlePtr = idleList; idlePtr != NULL;
	    prevPtr = idlePtr, idlePtr = idlePtr->nextPtr) {
	while ((idlePtr->proc == proc)
		&& (idlePtr->clientData == clientData)) {
	    nextPtr = idlePtr->nextPtr;
	    ckfree((char *) idlePtr);
	    idlePtr = nextPtr;
	    if (prevPtr == NULL) {
		idleList = idlePtr;
	    } else {
		prevPtr->nextPtr = idlePtr;
	    }
	    if (idlePtr == NULL) {
		lastIdlePtr = prevPtr;
		return;
	    }
	}
    }
}

/*
 *--------------------------------------------------------------
 *
 * Tk_DoOneEvent --
 *
 *	Process a single event of some sort.  If there's no
 *	work to do, wait for an event to occur, then process
 *	it.
 *
 * Results:
 *	The return value is 1 if the procedure actually found
 *	an event to process.  If no event was found then 0 is
 *	returned.
 *
 * Side effects:
 *	May delay execution of process while waiting for an
 *	X event, X error, file-ready event, or timer event.
 *	The handling of the event could cause additional
 *	side effects.  Collapses sequences of mouse-motion
 *	events for the same window into a single event by
 *	delaying motion event processing.
 *
 *--------------------------------------------------------------
 */

int
Tk_DoOneEvent(flags)
    int flags;			/* Miscellaneous flag values:  may be any
				 * combination of TK_DONT_WAIT, TK_X_EVENTS,
				 * TK_FILE_EVENTS, TK_TIMER_EVENTS, and
				 * TK_IDLE_EVENTS. */
#ifdef _WINDOWS
{
    XEvent event;
    Display *display;
    FileEvent * filePtr, * inFile = NULL;
    struct timeval curTime, timeout;
    int queues;
    int numFound;
    int error;
    SOCKET fd;
    static XEvent delayedMotionEvent;	/* Used to hold motion events that
					 * are being saved until later. */
    static int eventDelayed = 0;	/* Non-zero means there is an event
					 * in delayedMotionEvent. */
    if ((flags & TK_ALL_EVENTS) == 0) {
	flags |= TK_ALL_EVENTS;
    }

    /*
     * Phase One: see if there's already something ready
     * (either a file or a display) that was left over
     * from before (i.e don't do a select, just check the
     * bits from the last select).
     */

    checkFiles:
    if (tcl_AsyncReady) {
	(void) Tcl_AsyncInvoke((Tcl_Interp *) NULL, 0);
	return 1;
    }

    if (flags & TK_FILE_EVENTS) {
	for (filePtr = fileList; filePtr != NULL;
	     filePtr = filePtr->nextPtr) {
	    int mask;

	    if (filePtr->isDisplay) {
		continue;
	    }
	    /*
	     * Not a display:  if the file is ready, call the
	     * appropriate handler.
	     */

	    if (! (FD_ISSET(filePtr->fd, &ready[0]) ||
		   FD_ISSET(filePtr->fd, &ready[1]) ||
		   FD_ISSET(filePtr->fd, &ready[2])))
	    {
		continue;
	    }

	    mask = 0;
	    if (FD_ISSET(filePtr->fd, &ready[0])) {
		mask |= TK_READABLE;
		FD_CLR(filePtr->fd, &ready[0]);
	    }
	    if (FD_ISSET(filePtr->fd, &ready[1])) {
		mask |= TK_WRITABLE;
		FD_CLR(filePtr->fd, &ready[1]);
	    }	    
	    if (FD_ISSET(filePtr->fd, &ready[2])) {
		mask |= TK_EXCEPTION;
		FD_CLR(filePtr->fd, &ready[2]);
	    }	    
	    (*filePtr->proc)(filePtr->clientData, mask);
	    return 1;
	}
    }

    queues = 0;
    if (flags & TK_X_EVENTS) {
	queues |= X_Queue;
    }
    if (flags & TK_FILE_EVENTS) {
	queues |= File_Queue;
    }
    if (flags & TK_TIMER_EVENTS) {
	queues |= Timer_Queue;
    }
    
    if (flags & (TK_FILE_EVENTS|TK_X_EVENTS|TK_TIMER_EVENTS)) {
	checkStreem:
	display = XOpenDisplay(NULL);

	if (XFileEventsQueued(display, QueuedAfterReading, queues) != 0) {
        nextevent:
	    XNextFileEvent(display, &event, queues);

	    switch (event.type) {
	    case TimerNotify:
		if (timerQueue) {
		    timerQueue->set = 0;
		}
		goto checkTime;

	    case InputNotify:
		/* Maintained for compatability when not using sockets */
		for (filePtr = fileList; filePtr != NULL;
		     filePtr = filePtr->nextPtr)
		{
		    if (!filePtr->isDisplay) {
			inFile = filePtr;
		    }
		}

		if (!(flags & TK_FILE_EVENTS) || inFile == NULL) {
		    return 0;
		} else if (inFile->mask == TK_READABLE && inFile->proc != NULL) {
		    (inFile->proc)(inFile->clientData, 0);
		}
		return 1;

	    case SocketNotify:
		fd = event.xsocket.socket;
		for (filePtr = fileList; filePtr != NULL;
		     filePtr = filePtr->nextPtr)
		{
		    if (filePtr->fd == fd) {
			/*--- Reenable the asynchronous callbacks ---*/
			WSAAsyncSelect(fd, GetPostWindow(), 0, 0);
			if (WSAAsyncSelect(fd, GetPostWindow(), SOCKETNOTIFY,
					   filePtr->lEvent))
			{
			    panic("Unable to do WSAAsyncSelect\n");
			}
			break;
		    }
		}

		memcpy((VOID *) ready, (VOID *) masks,
		       3*sizeof(fd_set));
		timeout.tv_sec = timeout.tv_usec = 0;
		numFound = select(numFds, readPtr,
				  writePtr, exceptPtr,
				  &timeout);
		error = WSAGetLastError();
		if (numFound == -1 || errno == WSAEINTR) {
		    /*
		     * Some systems don't clear the masks after an error, so
		     * we have to do it here.
		     */

		    FD_ZERO(&ready[0]);
		    FD_ZERO(&ready[1]);
		    FD_ZERO(&ready[2]);
		}
		if (numFound > 0) {
		    goto checkFiles;
		}
		break;

	    default:
		if (tkEventDebug) {
		    eventTrace[traceIndex] = event;
		    traceIndex = (traceIndex+1) % TK_NEVENTS;
		}

		if (eventDelayed) {
		    if (((event.type != MotionNotify)
			 && (event.type != GraphicsExpose)
			 && (event.type != NoExpose)
			 && (event.type != Expose))
			|| (event.xmotion.display
			    != delayedMotionEvent.xmotion.display)
			|| (event.xmotion.window
			    != delayedMotionEvent.xmotion.window))
		    {
			XEvent copy;

			copy = delayedMotionEvent;
			eventDelayed = 0;
			Tk_HandleEvent(&copy);
		    }
		}
		if (event.type == MotionNotify) {
		    delayedMotionEvent = event;
		    eventDelayed = 1;
		} else {
		    Tk_HandleEvent(&event);
		}
		return 1;
	    }
	}
    }

    /*
     * Phase Two: get the current time and see if any timer
     * events are ready to fire.  If so, fire one and return.
     */

    checkTime:
    if ((timerQueue != NULL) && (flags & TK_TIMER_EVENTS)) {
	register TimerEvent *timerPtr = timerQueue;

	(void) gettimeofday(&curTime, (struct timezone *) NULL);
	if ((timerPtr->time.tv_sec < curTime.tv_sec)
		|| ((timerPtr->time.tv_sec == curTime.tv_sec)
		&&  (timerPtr->time.tv_usec <= curTime.tv_usec))) {
	    timerQueue = timerPtr->nextPtr;
	    (*timerPtr->proc)(timerPtr->clientData);
	    ckfree((char *) timerPtr);
	    return 1;
	}
    }

    /*
     * Phase Three: if there is a delayed motion event, process it
     * now, before any DoWhenIdle handlers.  Better to process before
     * idle handlers than after, because the goal of idle handlers is
     * to delay until after all pending events have been processed.
     * Must free up delayedMotionEvent *before* calling Tk_HandleEvent,
     * so that the event handler can call Tk_DoOneEvent recursively
     * without infinite looping.
     */

    if ((eventDelayed) && (flags & TK_X_EVENTS)) {
	XEvent copy;

	copy = delayedMotionEvent;
	eventDelayed = 0;
	Tk_HandleEvent(&copy);
	return 1;
    }

    /*
     * Phase Four: if there are DoWhenIdle requests pending (or
     * if we're not allowed to block), then do a select with an
     * instantaneous timeout.  If a ready file is found, then go
     * back to process it.
     */

    if (flags & (TK_FILE_EVENTS|TK_X_EVENTS|TK_TIMER_EVENTS)) {
	if (XFileEventsQueued(display, QueuedAfterReading, queues) != 0) {
	    goto checkStreem;
	}
    }
    if (((idleList != NULL) && (flags & TK_IDLE_EVENTS))
	    || (flags & TK_DONT_WAIT)) {
	if (flags & (TK_X_EVENTS|TK_FILE_EVENTS)) {
	    memcpy((VOID *) ready, (VOID *) masks,
		    3*sizeof(fd_set));
	    timeout.tv_sec = timeout.tv_usec = 0;
	    numFound = select(numFds, readPtr,
			      writePtr, exceptPtr,
			      &timeout);
	    error = WSAGetLastError();
	    if (numFound == -1 || errno == WSAEINTR) {
		/*
		 * Some systems don't clear the masks after an error, so
		 * we have to do it here.
		 */

		FD_ZERO(&ready[0]);
		FD_ZERO(&ready[1]);
		FD_ZERO(&ready[2]);
	    }
	    if ((numFound > 0) || ((numFound == -1) && (errno == WSAEINTR))) {
		goto checkFiles;
	    }
	}
    }

    /*
     * Phase Five:  process all pending DoWhenIdle requests.
     */

    if ((idleList != NULL) && (flags & TK_IDLE_EVENTS)) {
	register IdleHandler *idlePtr;
	int oldGeneration;

	oldGeneration = idleList->generation;
	idleGeneration++;

	/*
	 * The code below is trickier than it may look, for the following
	 * reasons:
	 *
	 * 1. New handlers can get added to the list while the current
	 *    one is being processed.  If new ones get added, we don't
	 *    want to process them during this pass through the list (want
	 *    to check for other work to do first).  This is implemented
	 *    using the generation number in the handler:  new handlers
	 *    will have a different generation than any of the ones currently
	 *    on the list.
	 * 2. The handler can call Tk_DoOneEvent, so we have to remove
	 *    the hander from the list before calling it. Otherwise an
	 *    infinite loop could result.
	 * 3. Tk_CancelIdleCall can be called to remove an element from
	 *    the list while a handler is executing, so the list could
	 *    change structure during the call.
	 */

	for (idlePtr = idleList;
		((idlePtr != NULL) && (idlePtr->generation == oldGeneration));
		idlePtr = idleList) {
	    idleList = idlePtr->nextPtr;
	    if (idleList == NULL) {
		lastIdlePtr = NULL;
	    }
	    (*idlePtr->proc)(idlePtr->clientData);
	    ckfree((char *) idlePtr);
	}
	return 1;
    }

    /*
     * Phase Six: do a select to wait for either one of the
     * files to become ready or for the first timer event to
     * fire.  Then go back to process the event.
     */

    if ((flags & TK_DONT_WAIT)
	    || !(flags & (TK_TIMER_EVENTS|TK_FILE_EVENTS|TK_X_EVENTS))) {
	return 0;
    }
    if ((timerQueue != NULL) && (flags & TK_TIMER_EVENTS)) {
        DWORD waitTime;

	if (! timerQueue->set) {
	    waitTime = (timerQueue->time.tv_sec - curTime.tv_sec)*1000;
	    waitTime += (timerQueue->time.tv_usec - curTime.tv_usec + 999) / 1000;
#if 0
	    if (waitTime > 65535) {
		waitTime = 65535; 
	    }
#endif
	    if (!SetTimer(GetPostWindow(), 1, (UINT) waitTime, NULL)) {
		return 0;
	    }
	    timerQueue->set = 1;
	}
    }

    goto nextevent;
}
#else
{
    register FileEvent *filePtr;
    struct timeval curTime, timeout, *timeoutPtr;
    int numFound;
    static XEvent delayedMotionEvent;	/* Used to hold motion events that
					 * are being saved until later. */
    static int eventDelayed = 0;	/* Non-zero means there is an event
					 * in delayedMotionEvent. */

    if ((flags & TK_ALL_EVENTS) == 0) {
	flags |= TK_ALL_EVENTS;
    }

    /*
     * Phase One: see if there's already something ready
     * (either a file or a display) that was left over
     * from before (i.e don't do a select, just check the
     * bits from the last select).
     */

    checkFiles:
    if (tcl_AsyncReady) {
	(void) Tcl_AsyncInvoke((Tcl_Interp *) NULL, 0);
	return 1;
    }
    for (filePtr = fileList; filePtr != NULL;
	    filePtr = filePtr->nextPtr) {
	int mask;

	/*
	 * Displays:  flush output, check for queued events,
	 * and read events from the server if display is ready.
	 * If there are any events, process one and then
	 * return.
	 */

	if (filePtr->isDisplay) {
	    Display *display = (Display *) filePtr->clientData;
	    XEvent event;

	    if (!(flags & TK_X_EVENTS)) {
		continue;
	    }
	    XFlush(display);
	    if ((*filePtr->readPtr) & filePtr->mask) {
		*filePtr->readPtr &= ~filePtr->mask;
		if (XEventsQueued(display, QueuedAfterReading) == 0) {

		    /*
		     * Things are very tricky if there aren't any events
		     * readable at this point (after all, there was
		     * supposedly data available on the connection).
		     * A couple of things could have occurred:
		     * 
		     * One possibility is that there were only error events
		     * in the input from the server.  If this happens,
		     * we should return (we don't want to go to sleep
		     * in XNextEvent below, since this would block out
		     * other sources of input to the process).
		     *
		     * Another possibility is that our connection to the
		     * server has been closed.  This will not necessarily
		     * be detected in XEventsQueued (!!), so if we just
		     * return then there will be an infinite loop.  To
		     * detect such an error, generate a NoOp protocol
		     * request to exercise the connection to the server,
		     * then return.  However, must disable SIGPIPE while
		     * sending the event, or else the process will die
		     * from the signal and won't invoke the X error
		     * function to print a nice message.
		     */

		    void (*oldHandler)();

		    oldHandler = (void (*)()) signal(SIGPIPE, SIG_IGN);
		    XNoOp(display);
		    XFlush(display);
		    (void) signal(SIGPIPE, oldHandler);
		    return 1;
		}
		if (restrictProc != NULL) {
		    if (!XCheckIfEvent(display, &event, restrictProc,
			    restrictArg)) {
			return 1;
		    }
		} else {
		    XNextEvent(display, &event);
		}
	    } else {
		if (QLength(display) == 0) {
		    continue;
		}
		if (restrictProc != NULL) {
		    if (!XCheckIfEvent(display, &event, restrictProc,
			    restrictArg)) {
			continue;
		    }
		} else {
		    XNextEvent(display, &event);
		}
	    }

	    /*
	     * Got an event.  Deal with mouse-motion-collapsing and
	     * event-delaying here.  If there's already an event delayed,
	     * then process that event if it's incompatible with the new
	     * event (new event not mouse motion, or window changed, or
	     * state changed).  If the new event is mouse motion, then
	     * don't process it now;  delay it until later in the hopes
	     * that it can be merged with other mouse motion events
	     * immediately following.
	     */

	    if (tkEventDebug) {
		eventTrace[traceIndex] = event;
		traceIndex = (traceIndex+1) % TK_NEVENTS;
	    }

	    if (eventDelayed) {
		if (((event.type != MotionNotify)
			    && (event.type != GraphicsExpose)
			    && (event.type != NoExpose)
			    && (event.type != Expose))
			|| (event.xmotion.display
			    != delayedMotionEvent.xmotion.display)
			|| (event.xmotion.window
			    != delayedMotionEvent.xmotion.window)) {
		    XEvent copy;

		    /*
		     * Must copy the event out of delayedMotionEvent before
		     * processing it, in order to allow recursive calls to
		     * Tk_DoOneEvent as part of the handler.
		     */

		    copy = delayedMotionEvent;
		    eventDelayed = 0;
		    Tk_HandleEvent(&copy);
		}
	    }
	    if (event.type == MotionNotify) {
		delayedMotionEvent = event;
		eventDelayed = 1;
	    } else {
		Tk_HandleEvent(&event);
	    }
	    return 1;
	}

	/*
	 * Not a display:  if the file is ready, call the
	 * appropriate handler.
	 */

	if (((*filePtr->readPtr | *filePtr->writePtr
		| *filePtr->exceptPtr) & filePtr->mask) == 0) {
	    continue;
	}
	if (!(flags & TK_FILE_EVENTS)) {
	    continue;
	}
	mask = 0;
	if (*filePtr->readPtr & filePtr->mask) {
	    mask |= TK_READABLE;
	    *filePtr->readPtr &= ~filePtr->mask;
	}
	if (*filePtr->writePtr & filePtr->mask) {
	    mask |= TK_WRITABLE;
	    *filePtr->writePtr &= ~filePtr->mask;
	}
	if (*filePtr->exceptPtr & filePtr->mask) {
	    mask |= TK_EXCEPTION;
	    *filePtr->exceptPtr &= ~filePtr->mask;
	}
	(*filePtr->proc)(filePtr->clientData, mask);
	return 1;
    }

    /*
     * Phase Two: get the current time and see if any timer
     * events are ready to fire.  If so, fire one and return.
     */

    checkTime:
    if ((timerQueue != NULL) && (flags & TK_TIMER_EVENTS)) {
	register TimerEvent *timerPtr = timerQueue;

	(void) gettimeofday(&curTime, (struct timezone *) NULL);
	if ((timerPtr->time.tv_sec < curTime.tv_sec)
		|| ((timerPtr->time.tv_sec == curTime.tv_sec)
		&&  (timerPtr->time.tv_usec < curTime.tv_usec))) {
	    timerQueue = timerPtr->nextPtr;
	    (*timerPtr->proc)(timerPtr->clientData);
	    ckfree((char *) timerPtr);
	    return 1;
	}
    }


    /*
     * Phase Three: if there is a delayed motion event, process it
     * now, before any DoWhenIdle handlers.  Better to process before
     * idle handlers than after, because the goal of idle handlers is
     * to delay until after all pending events have been processed.
     * Must free up delayedMotionEvent *before* calling Tk_HandleEvent,
     * so that the event handler can call Tk_DoOneEvent recursively
     * without infinite looping.
     */

    if ((eventDelayed) && (flags & TK_X_EVENTS)) {
	XEvent copy;

	copy = delayedMotionEvent;
	eventDelayed = 0;
	Tk_HandleEvent(&copy);
	return 1;
    }

    /*
     * Phase Four: if there are DoWhenIdle requests pending (or
     * if we're not allowed to block), then do a select with an
     * instantaneous timeout.  If a ready file is found, then go
     * back to process it.
     */

    if (((idleList != NULL) && (flags & TK_IDLE_EVENTS))
	    || (flags & TK_DONT_WAIT)) {
	if (flags & (TK_X_EVENTS|TK_FILE_EVENTS)) {
	    memcpy((VOID *) ready, (VOID *) masks,
		    3*MASK_SIZE*sizeof(fd_mask));
	    timeout.tv_sec = timeout.tv_usec = 0;
	    numFound = select(numFds, (SELECT_MASK *) readPtr,
		    (SELECT_MASK *) writePtr, (SELECT_MASK *) exceptPtr,
		    &timeout);
	    if (numFound == -1) {
		/*
		 * Some systems don't clear the masks after an error, so
		 * we have to do it here.
		 */

		memset((VOID *) ready, 0, 3*MASK_SIZE*sizeof(fd_mask));
	    }
	    if ((numFound > 0) || ((numFound == -1) && (errno == EINTR))) {
		goto checkFiles;
	    }
	}
    }

    /*
     * Phase Five:  process all pending DoWhenIdle requests.
     */

    if ((idleList != NULL) && (flags & TK_IDLE_EVENTS)) {
	register IdleHandler *idlePtr;
	int oldGeneration;

	oldGeneration = idleList->generation;
	idleGeneration++;

	/*
	 * The code below is trickier than it may look, for the following
	 * reasons:
	 *
	 * 1. New handlers can get added to the list while the current
	 *    one is being processed.  If new ones get added, we don't
	 *    want to process them during this pass through the list (want
	 *    to check for other work to do first).  This is implemented
	 *    using the generation number in the handler:  new handlers
	 *    will have a different generation than any of the ones currently
	 *    on the list.
	 * 2. The handler can call Tk_DoOneEvent, so we have to remove
	 *    the hander from the list before calling it. Otherwise an
	 *    infinite loop could result.
	 * 3. Tk_CancelIdleCall can be called to remove an element from
	 *    the list while a handler is executing, so the list could
	 *    change structure during the call.
	 */

	for (idlePtr = idleList;
		((idlePtr != NULL) && (idlePtr->generation == oldGeneration));
		idlePtr = idleList) {
	    idleList = idlePtr->nextPtr;
	    if (idleList == NULL) {
		lastIdlePtr = NULL;
	    }
	    (*idlePtr->proc)(idlePtr->clientData);
	    ckfree((char *) idlePtr);
	}
	return 1;
    }

    /*
     * Phase Six: do a select to wait for either one of the
     * files to become ready or for the first timer event to
     * fire.  Then go back to process the event.
     */

    if ((flags & TK_DONT_WAIT)
	    || !(flags & (TK_TIMER_EVENTS|TK_FILE_EVENTS|TK_X_EVENTS))) {
	return 0;
    }
    if ((timerQueue == NULL) || !(flags & TK_TIMER_EVENTS)) {
	timeoutPtr = NULL;
    } else {
	timeoutPtr = &timeout;
	timeout.tv_sec = timerQueue->time.tv_sec - curTime.tv_sec;
	timeout.tv_usec = timerQueue->time.tv_usec - curTime.tv_usec;
	if (timeout.tv_usec < 0) {
	    timeout.tv_sec -= 1;
	    timeout.tv_usec += 1000000;
	}
    }

    memcpy((VOID *) ready, (VOID *) masks, 3*MASK_SIZE*sizeof(fd_mask));
    numFound = select(numFds, (SELECT_MASK *) readPtr,
	    (SELECT_MASK *) writePtr, (SELECT_MASK *) exceptPtr,
	    timeoutPtr);
    if (numFound == -1) {
	/*
	 * Some systems don't clear the masks after an error, so
	 * we have to do it here.
	 */

	memset((VOID *) ready, 0, 3*MASK_SIZE*sizeof(fd_mask));
    }
    if (numFound == 0) {
	goto checkTime;
    }
    goto checkFiles;
}
#endif /* !_WINDOWS */
/*
 *--------------------------------------------------------------
 *
 * Tk_MainLoop --
 *
 *	Call Tk_DoOneEvent over and over again in an infinite
 *	loop as long as there exist any main windows.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Arbitrary;  depends on handlers for events.
 *
 *--------------------------------------------------------------
 */

void
Tk_MainLoop()
{
    while (tk_NumMainWindows > 0) {
	Tk_DoOneEvent(0);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_Sleep --
 *
 *	Delay execution for the specified number of milliseconds.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Time passes.
 *
 *----------------------------------------------------------------------
 */

void
Tk_Sleep(ms)
    int ms;			/* Number of milliseconds to sleep. */
#ifdef _WINDOWS
{
    Sleep(ms);
}
#else
{
    static struct timeval delay;

    delay.tv_sec = ms/1000;
    delay.tv_usec = (ms%1000)*1000;
    (void) select(0, (SELECT_MASK *) 0, (SELECT_MASK *) 0,
	    (SELECT_MASK *) 0, &delay);
}
#endif /* _WINDOWS */
/*
 *----------------------------------------------------------------------
 *
 * Tk_RestrictEvents --
 *
 *	This procedure is used to globally restrict the set of events
 *	that will be dispatched.  The restriction is done by filtering
 *	all incoming X events through a procedure that determines
 *	whether they are to be processed immediately or deferred.
 *
 * Results:
 *	The return value is the previous restriction procedure in effect,
 *	if there was one, or NULL if there wasn't.
 *
 * Side effects:
 *	From now on, proc will be called to determine whether to process
 *	or defer each incoming X event.
 *
 *----------------------------------------------------------------------
 */

Tk_RestrictProc *
Tk_RestrictEvents(proc, arg, prevArgPtr)
    Tk_RestrictProc *proc;	/* X "if" procedure to call for each
				 * incoming event.  See "XIfEvent" doc.
				 * for details. */
    char *arg;			/* Arbitrary argument to pass to proc. */
    char **prevArgPtr;		/* Place to store information about previous
				 * argument. */
{
    Bool (*prev)  _ANSI_ARGS_((Display *display, XEvent *eventPtr, char *arg));

    prev = restrictProc;
    *prevArgPtr = restrictArg;
    restrictProc = proc;
    restrictArg = arg;
    return prev;
}

/*
 *--------------------------------------------------------------
 *
 * TkEventDeadWindow --
 *
 *	This procedure is invoked when it is determined that
 *	a window is dead.  It cleans up event-related information
 *	about the window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Various things get cleaned up and recycled.
 *
 *--------------------------------------------------------------
 */

void
TkEventDeadWindow(winPtr)
    TkWindow *winPtr;		/* Information about the window
				 * that is being deleted. */
{
    register TkEventHandler *handlerPtr;
    register InProgress *ipPtr;

    /*
     * While deleting all the handlers, be careful to check for
     * Tk_HandleEvent being about to process one of the deleted
     * handlers.  If it is, tell it to quit (all of the handlers
     * are being deleted).
     */

    while (winPtr->handlerList != NULL) {
	handlerPtr = winPtr->handlerList;
	winPtr->handlerList = handlerPtr->nextPtr;
	for (ipPtr = pendingPtr; ipPtr != NULL; ipPtr = ipPtr->nextPtr) {
	    if (ipPtr->nextHandler == handlerPtr) {
		ipPtr->nextHandler = NULL;
	    }
	    if (ipPtr->winPtr == winPtr) {
		ipPtr->winPtr = None;
	    }
	}
	ckfree((char *) handlerPtr);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkCurrentTime --
 *
 *	Try to deduce the current time.  "Current time" means the time
 *	of the event that led to the current code being executed, which
 *	means the time in the most recently-nested invocation of
 *	Tk_HandleEvent.
 *
 * Results:
 *	The return value is the time from the current event, or
 *	CurrentTime if there is no current event or if the current
 *	event contains no time.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

Time
TkCurrentTime(dispPtr)
    TkDisplay *dispPtr;		/* Display for which the time is desired. */
{
    register XEvent *eventPtr;

    if (pendingPtr == NULL) {
	return dispPtr->lastEventTime;
    }
    eventPtr = pendingPtr->eventPtr;
    switch (eventPtr->type) {
	case ButtonPress:
	case ButtonRelease:
	    return eventPtr->xbutton.time;
	case KeyPress:
	case KeyRelease:
	    return eventPtr->xkey.time;
	case MotionNotify:
	    return eventPtr->xmotion.time;
	case EnterNotify:
	case LeaveNotify:
	    return eventPtr->xcrossing.time;
	case PropertyNotify:
	    return eventPtr->xproperty.time;
    }
    return dispPtr->lastEventTime;
}
