/* 
 * tkXEvent.c --
 *
 *	This file provides basic low-level facilities for managing
 *	X events.  It builds on the facilities provided in tkEvent.c.
 *
 * Copyright (c) 1990-1994 The Regents of the University of California.
 * Copyright (c) 1994 Sun Microsystems, Inc.
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */

/* 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.
 */

static char sccsid[] = "@(#) tkXEvent.c 1.4 94/12/17 16:32:21";

#include "tkPort.h"
#include "tkInt.h"
#include <signal.h>

#ifdef TCL_MSDOS_PORT
#include <winsock.h>
int quitValue;		/* wParam value from final WM_QUIT message */
Tcl_Interp *quitInterp = NULL;
#endif
/*
 * 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 */
};

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

#ifdef TCL_MSDOS_PORT
static Bool (*restrictProc)  _ANSI_ARGS_((XEvent *eventPtr,
    char *arg));		/* Procedure to call.  NULL means no
#else
static Bool (*restrictProc)  _ANSI_ARGS_((Display *display, XEvent *eventPtr,
    char *arg));		/* Procedure to call.  NULL means no
				 * restrictProc is currently in effect. */
#endif
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.
 */

#ifndef TCL_MSDOS_PORT
#define TK_NEVENTS 32
static XEvent eventTrace[TK_NEVENTS];
static int traceIndex = 0;
#endif

/*
 * A mouse motion event whose processing is delayed, in order to
 * merge it with subsequent mouse motion events, if possible:
 */

#ifndef TCL_MSDOS_PORT
static XEvent delayedMotionEvent;
#endif

/*
 * Prototypes for procedures referenced only in this file:
 */

#ifndef TCL_MSDOS_PORT
static void		DelayedEventProc _ANSI_ARGS_((void));
#endif

/*
 *--------------------------------------------------------------
 *
 * 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.
 *
 *--------------------------------------------------------------
 */

#ifdef TCL_MSDOS_PORT
void _export
#else
void
#endif
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.
 *
 *--------------------------------------------------------------
 */

#ifdef TCL_MSDOS_PORT
void _export
#else
void
#endif
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.
 *
 *--------------------------------------------------------------
 */

#ifdef TCL_MSDOS_PORT
void _export
#else
void
#endif
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.
 *
 *--------------------------------------------------------------
 */

#ifdef TCL_MSDOS_PORT
void _export
#else
void
#endif
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.
 *
 *--------------------------------------------------------------
 */

#ifdef TCL_MSDOS_PORT
void _export
#else
void
#endif
Tk_HandleEvent(eventPtr)
    XEvent *eventPtr;		/* Event to dispatch. */
{
#ifdef TCL_MSDOS_PORT
    TkEventHandler *handlerPtr;
    GenericHandler *genericPtr;
    GenericHandler *genPrevPtr;
#else
    register TkEventHandler *handlerPtr;
    register GenericHandler *genericPtr;
    register GenericHandler *genPrevPtr;
#endif
    TkWindow *winPtr;
    register unsigned long mask;
    InProgress ip;
    Window handlerWindow;
#ifdef TCL_MSDOS_PORT
    char className[32];
#endif

    /*
     * 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 TCL_MSDOS_PORT
    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

    /*
     * Events selected by StructureNotify require two forms of
     * special handling:
     *
     * 1. They look the same as those selected by SubstructureNotify.
     *    The only difference is whether the "event" and "window"
     *    fields are the same.  Compare the two fields and convert
     *    StructureNotify to SubstructureNotify if necessary.
     * 2. If the event is a DestroyNotify event (and isn't being
     *    delivered as a SubstructureNotify event), complete the
     *    deletion of the window by freeing the resource id for
     *    the window and freeing its context.
     */

    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;
	}
#ifndef TCL_MSDOS_PORT
	else if (eventPtr->type == DestroyNotify) {
	    if (XFindContext(eventPtr->xany.display, handlerWindow,
		    tkWindowContext, (caddr_t *) &winPtr) != 0) {
		return;
	    }
	    if (winPtr == 0) {
		XDeleteContext(eventPtr->xany.display, handlerWindow,
			tkWindowContext);
		Tk_FreeXId(eventPtr->xany.display, handlerWindow);
		return;
	    }
	    goto gotContext;
	}
#endif
    }
#ifdef TCL_MSDOS_PORT
    if(!GetClassName(handlerWindow, className, sizeof(className)) ||
	strcmp(className, "Tk_Window"))
#else 
    if ((XFindContext(eventPtr->xany.display, handlerWindow,
	    tkWindowContext, (caddr_t *) &winPtr) != 0)
	    || (winPtr == 0))
#endif
    {

	/*
	 * 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).
	 */

#ifndef TCL_MSDOS_PORT
	if (eventPtr->type == PropertyNotify) {
	    TkSelPropProc(eventPtr);
	}
#endif
	return;
    }
#ifdef TCL_MSDOS_PORT
    else if((winPtr = (TkWindow *)GetWindowLong(handlerWindow, 0)) == NULL){
	return;
    }
#endif

#ifndef TCL_MSDOS_PORT
    gotContext:
#endif
    if (winPtr->mainPtr != NULL) {
	/*
	 * 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;
    
#ifndef TCL_MSDOS_PORT
	    winPtr->dispPtr->lastEventTime = eventPtr->xkey.time;
#endif
	    focusPtr = TkGetFocus(winPtr);
	    if (focusPtr == NULL) {
		return;
	    }
#ifdef TCL_MSDOS_PORT
	    Tk_GetRootCoords((Tk_Window) winPtr, &winX, &winY);
	    Tk_GetRootCoords((Tk_Window) focusPtr, &focusX, &focusY);
	    eventPtr->xkey.x -= focusX - winX;
	    eventPtr->xkey.y -= focusY - winY;
#else
	    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;
	    }
#endif
	    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)) {
#ifndef TCL_MSDOS_PORT
	    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;
	    }
#endif
	    if (TkPointerEvent(eventPtr, winPtr) == 0) {
		return;
	    }
	}
    }

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

#ifndef TCL_MSDOS_PORT
    if (eventPtr->type == PropertyNotify) {
	winPtr->dispPtr->lastEventTime = eventPtr->xproperty.time;
    }
#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;
}

/*
 *----------------------------------------------------------------------
 *
 * TkXFileProc --
 *
 *	This procedure is a file handler created by calling
 *	Tk_CreateFileHandler2.  It is invoked by Tk_DoOneEvent to see
 *	whether an X server connection is ready and, if so, handle
 *	an incoming event on it.
 *
 * Results:
 *	Returns TK_FILE_HANDLED if an event was found and processed.
 *	Otherwise returns TK_READABLE is X events are enabled, or 0 if
 *	flags doesn't contain TK_X_EVENTS, which means that we shouldn't
 *	even look for X events.
 *
 * Side effects:
 *	Could be arbitrary, depending on what happens as a consequence
 *	of invoking an event handler.
 *
 *----------------------------------------------------------------------
 */

#ifndef TCL_MSDOS_PORT
int
TkXFileProc(clientData, mask, flags)
    ClientData clientData;	/* Pointer to Xlib Display structure
				 * for display. */
    int mask;			/* OR-ed combination of the bits TK_READABLE,
				 * TK_WRITABLE, and TK_EXCEPTION, indicating
				 * current state of file. */
    int flags;			/* Flag bits passed to Tk_DoOneEvent;
				 * contains bits such as TK_DONT_WAIT,
				 * TK_X_EVENTS, Tk_FILE_EVENTS, etc. */
{
    Display *display = (Display *) clientData;
    XEvent event;

    if (!(flags & TK_X_EVENTS)) {
	return 0;
    }
    XFlush(display);
    if (mask & TK_READABLE) {
	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 request, 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 TK_FILE_HANDLED;
	}
	if (restrictProc != NULL) {
	    if (!XCheckIfEvent(display, &event, restrictProc, restrictArg)) {
		return TK_FILE_HANDLED;
	    }
	} else {
	    XNextEvent(display, &event);
	}
    } else {
	if (QLength(display) == 0) {
	    return TK_READABLE;
	}
	if (restrictProc != NULL) {
	    if (!XCheckIfEvent(display, &event, restrictProc, restrictArg)) {
		return TK_READABLE;
	    }
	} 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 (tkDelayedEventProc != NULL) {
	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;
	    tkDelayedEventProc = NULL;
	    Tk_HandleEvent(&copy);
	}
    }
    if (event.type == MotionNotify) {
	delayedMotionEvent = event;
	tkDelayedEventProc = DelayedEventProc;
    } else {
	Tk_HandleEvent(&event);
    }
    return TK_FILE_HANDLED;
}
#endif

/*
 *----------------------------------------------------------------------
 *
 * 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.
 *
 *----------------------------------------------------------------------
 */

#ifdef TCL_MSDOS_PORT
Tk_RestrictProc * _export
#else
Tk_RestrictProc *
#endif
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. */
{
#ifdef TCL_MSDOS_PORT
    Bool (*prev)  _ANSI_ARGS_((XEvent *eventPtr, char *arg));
#else
    Bool (*prev)  _ANSI_ARGS_((Display *display, XEvent *eventPtr, char *arg));
#endif

    prev = restrictProc;
    *prevArgPtr = restrictArg;
    restrictProc = proc;
    restrictArg = arg;
    return prev;
}

/*
 *--------------------------------------------------------------
 *
 * 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.
 *
 *--------------------------------------------------------------
 */

#ifdef TCL_MSDOS_PORT
int _export
Tk_MainLoop(interp)
    Tcl_Interp *interp;
#else
void
Tk_MainLoop()
#endif
{
#ifdef TCL_MSDOS_PORT
    while (tk_NumMainWindows > 0){
	if(quitInterp != NULL){
	    if(quitInterp == interp){
		quitInterp = NULL;
		break;
	    } else {
		Yield();
		if(Tk_DoOneEvent(0) == 0) break;
		continue;
	    }
	} else if(Tk_DoOneEvent(0) == 0) break;
    }
/* 
    WinsockCleanup();
*/
    return quitValue;
#else
    while (tk_NumMainWindows > 0) {
	Tk_DoOneEvent(0);
    }
#endif
}

/*
 *--------------------------------------------------------------
 *
 * 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.
 *
 *----------------------------------------------------------------------
 */

#ifndef TCL_MSDOS_PORT
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;
}
#endif

/*
 *----------------------------------------------------------------------
 *
 * DelayedEventProc --
 *
 *	This procedure is a bit of a kludge;  it is called by
 *	Tk_DoOneEvent just before calling idle handlers, if there
 *	is a delayed mouse motion event.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The mouse motion event gets processed.
 *
 *----------------------------------------------------------------------
 */

#ifndef TCL_MSDOS_PORT
static void
DelayedEventProc()
{
    XEvent copy;

    /*
     * Special trick:  must free delayedMotionEvent *before* calling
     * Tk_HandleEvent, so that the event handler can call Tk_DoOneEvent
     * recursively without infinite looping.
     */

    copy = delayedMotionEvent;
    tkDelayedEventProc = NULL;
    Tk_HandleEvent(&copy);
}
#endif
