/*
 * tkEvent.c --
 *
 *	This file provides basic low-level facilities for managing X events in
 *	Tk.
 *
 * Copyright (c) 1990-1994 The Regents of the University of California.
 * Copyright (c) 1994-1995 Sun Microsystems, Inc.
 * Copyright (c) 1998-2000 Ajuba Solutions.
 * Copyright (c) 2004 George Peter Staplin
 *
 * See the file "license.terms" for information on usage and redistribution of
 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
 */

#include "tkInt.h"

/*
 * There's a potential problem if a handler is deleted while it's current
 * (i.e. its function is executing), since Tk_HandleEvent will need to read
 * the handler's "nextPtr" field when the function 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;

/*
 * For each call to Tk_CreateGenericHandler or Tk_CreateClientMessageHandler,
 * 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;	/* Function to dispatch on all X events. */
    ClientData clientData;	/* Client data to pass to function. */
    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;

/*
 * 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 structure is used for queueing X-style events on the Tcl
 * event queue.
 */

typedef struct TkWindowEvent {
    Tcl_Event header;		/* Standard information for all events. */
    XEvent event;		/* The X event. */
} TkWindowEvent;

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

static const unsigned long eventMasks[TK_LASTEVENT] = {
    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 */
    VirtualEventMask,			/* VirtualEvents */
    ActivateMask,			/* ActivateNotify */
    ActivateMask,			/* DeactivateNotify */
    MouseWheelMask			/* MouseWheelEvent */
};

/*
 * For each exit handler created with a call to TkCreateExitHandler or
 * TkCreateThreadExitHandler there is a structure of the following type:
 */

typedef struct ExitHandler {
    Tcl_ExitProc *proc;		/* Function to call when process exits. */
    ClientData clientData;	/* One word of information to pass to proc. */
    struct ExitHandler *nextPtr;/* Next in list of all exit handlers for this
				 * application, or NULL for end of list. */
} ExitHandler;

/*
 * The structure below is used to store Data for the Event module that must be
 * kept thread-local. The "dataKey" is used to fetch the thread-specific
 * storage for the current thread.
 */

typedef struct {
    int handlersActive;		/* The following variable has a non-zero value
				 * when a handler is active. */
    InProgress *pendingPtr;	/* Topmost search in progress, or NULL if
				 * none. */

    /*
     * List of generic handler records.
     */

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

    /*
     * List of client message handler records.
     */

    GenericHandler *cmList;	/* First handler in the list, or NULL. */
    GenericHandler *lastCmPtr;	/* Last handler in list. */

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

    Tk_RestrictProc *restrictProc;
				/* Function to call. NULL means no
				 * restrictProc is currently in effect. */
    ClientData restrictArg;	/* Argument to pass to restrictProc. */
    ExitHandler *firstExitPtr;	/* First in list of all exit handlers for this
				 * thread. */
    int inExit;			/* True when this thread is exiting. This is
				 * used as a hack to decide to close the
				 * standard channels. */
} ThreadSpecificData;
static Tcl_ThreadDataKey dataKey;

/*
 * There are both per-process and per-thread exit handlers. The first list is
 * controlled by a mutex. The other is in thread local storage.
 */

static ExitHandler *firstExitPtr = NULL;
				/* First in list of all exit handlers for
				 * application. */
TCL_DECLARE_MUTEX(exitMutex)

/*
 * Prototypes for functions that are only referenced locally within this file.
 */

static void		CleanUpTkEvent(XEvent *eventPtr);
static void		DelayedMotionProc(ClientData clientData);
static unsigned long    GetEventMaskFromXEvent(XEvent *eventPtr);
static TkWindow *	GetTkWindowFromXEvent(XEvent *eventPtr);
static void		InvokeClientMessageHandlers(ThreadSpecificData *tsdPtr,
			    Tk_Window tkwin, XEvent *eventPtr);
static int		InvokeFocusHandlers(TkWindow **winPtrPtr,
			    unsigned long mask, XEvent *eventPtr);
static int		InvokeGenericHandlers(ThreadSpecificData *tsdPtr,
			    XEvent *eventPtr);
static int		InvokeMouseHandlers(TkWindow *winPtr,
			    unsigned long mask, XEvent *eventPtr);
static Window		ParentXId(Display *display, Window w);
static int		RefreshKeyboardMappingIfNeeded(XEvent *eventPtr);
static int		TkXErrorHandler(ClientData clientData,
			    XErrorEvent *errEventPtr);
static int		WindowEventProc(Tcl_Event *evPtr, int flags);
#ifdef TK_USE_INPUT_METHODS
static void		CreateXIC(TkWindow *winPtr);
#endif /* TK_USE_INPUT_METHODS */

/*
 *----------------------------------------------------------------------
 *
 * InvokeFocusHandlers --
 *
 *	Call focus-related code to look at FocusIn, FocusOut, Enter, and Leave
 *	events; depending on its return value, ignore the event.
 *
 * Results:
 *	0 further processing can be done on the event.
 *	1 we are done with the event passed.
 *
 * Side effects:
 *	The *winPtrPtr in the caller may be changed to the TkWindow for the
 *	window with focus.
 *
 *----------------------------------------------------------------------
 */

static int
InvokeFocusHandlers(
    TkWindow **winPtrPtr,
    unsigned long mask,
    XEvent *eventPtr)
{
    if ((mask & (FocusChangeMask|EnterWindowMask|LeaveWindowMask))
	    && (TkFocusFilterEvent(*winPtrPtr, eventPtr) == 0)) {
	return 1;
    }

    /*
     * Only key-related events are directed according to the focus.
     */

    if (mask & (KeyPressMask|KeyReleaseMask)) {
	(*winPtrPtr)->dispPtr->lastEventTime = eventPtr->xkey.time;
	*winPtrPtr = TkFocusKeyEvent(*winPtrPtr, eventPtr);
	if (*winPtrPtr == NULL) {
	    return 1;
	}
    }

    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * InvokeMouseHandlers --
 *
 *	Call a grab-related function to do special processing on pointer
 *	events.
 *
 * Results:
 *	0 further processing can be done on the event.
 *	1 we are done with the event passed.
 *
 * Side effects:
 *	New events may be queued from TkPointerEvent and grabs may be added
 *	and/or removed. The eventPtr may be changed by TkPointerEvent in some
 *	cases.
 *
 *----------------------------------------------------------------------
 */

static int
InvokeMouseHandlers(
    TkWindow *winPtr,
    unsigned long mask,
    XEvent *eventPtr)
{
    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) {
	    /*
	     * The event should be ignored to make grab work correctly (as the
	     * comment for TkPointerEvent states).
	     */

	    return 1;
	}
    }

    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * CreateXIC --
 *
 *	Create the X input context for our winPtr.
 *	XIM is only ever enabled on Unix.
 *
 *----------------------------------------------------------------------
 */

#ifdef TK_USE_INPUT_METHODS
static void
CreateXIC(
    TkWindow *winPtr)
{
    TkDisplay *dispPtr = winPtr->dispPtr;
    long im_event_mask = 0L;
    const char *preedit_attname = NULL;
    XVaNestedList preedit_attlist = NULL;

    if (dispPtr->inputStyle & XIMPreeditPosition) {
	XPoint spot = {0, 0};

	preedit_attname = XNPreeditAttributes;
	preedit_attlist = XVaCreateNestedList(0,
		XNSpotLocation, &spot,
		XNFontSet, dispPtr->inputXfs,
		NULL);
    }

    winPtr->inputContext = XCreateIC(dispPtr->inputMethod,
	    XNInputStyle, dispPtr->inputStyle,
	    XNClientWindow, winPtr->window,
	    XNFocusWindow, winPtr->window,
	    preedit_attname, preedit_attlist,
	    NULL);

    if (preedit_attlist) {
	XFree(preedit_attlist);
    }


    if (winPtr->inputContext == NULL) {
	/* XCreateIC failed. */
	return;
    }
    winPtr->ximGeneration = dispPtr->ximGeneration;

    /*
     * Adjust the window's event mask if the IM requires it.
     */
    XGetICValues(winPtr->inputContext, XNFilterEvents, &im_event_mask, NULL);
    if ((winPtr->atts.event_mask & im_event_mask) != im_event_mask) {
	winPtr->atts.event_mask |= im_event_mask;
	XSelectInput(winPtr->display, winPtr->window, winPtr->atts.event_mask);
    }
}
#endif

/*
 *----------------------------------------------------------------------
 *
 * GetTkWindowFromXEvent --
 *
 *	Attempt to find which TkWindow is associated with an event. If it
 *	fails we attempt to get the TkWindow from the parent for a property
 *	notification.
 *
 * Results:
 *	The TkWindow associated with the event or NULL.
 *
 * Side effects:
 *	TkSelPropProc may influence selection on windows not known to Tk.
 *
 *----------------------------------------------------------------------
 */

static TkWindow *
GetTkWindowFromXEvent(
    XEvent *eventPtr)
{
    TkWindow *winPtr;
    Window parentXId, handlerWindow = eventPtr->xany.window;

    if ((eventPtr->xany.type == StructureNotifyMask)
	    && (eventPtr->xmap.event != eventPtr->xmap.window)) {
	handlerWindow = eventPtr->xmap.event;
    }

    winPtr = (TkWindow *) Tk_IdToWindow(eventPtr->xany.display, handlerWindow);

    if (winPtr == NULL) {
	/*
	 * 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). Also, if the
	 * window's parent is a Tk window that has the TK_PROP_PROPCHANGE flag
	 * set, then we must propagate the PropertyNotify event up to the
	 * parent.
	 */

	if (eventPtr->type != PropertyNotify) {
	    return NULL;
	}
	TkSelPropProc(eventPtr);
	parentXId = ParentXId(eventPtr->xany.display, handlerWindow);
	if (parentXId == None) {
	    return NULL;
	}
	winPtr = (TkWindow *) Tk_IdToWindow(eventPtr->xany.display, parentXId);
	if (winPtr == NULL) {
	    return NULL;
	}
	if (!(winPtr->flags & TK_PROP_PROPCHANGE)) {
	    return NULL;
	}
    }
    return winPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * GetEventMaskFromXEvent --
 *
 *	The event type is looked up in our eventMasks tables, and may be
 *	changed to a different mask depending on the state of the event and
 *	window members.
 *
 * Results:
 *	The mask for the event.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static unsigned long
GetEventMaskFromXEvent(
    XEvent *eventPtr)
{
    unsigned long mask;

    if (eventPtr->xany.type <TK_LASTEVENT) {
	mask = eventMasks[eventPtr->xany.type];
    } else {
	mask = 0;
    }

    /*
     * Events selected by StructureNotify require special handling. 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.
     */

    if (mask == StructureNotifyMask) {
	if (eventPtr->xmap.event != eventPtr->xmap.window) {
	    mask = SubstructureNotifyMask;
	}
    }
    return mask;
}

/*
 *----------------------------------------------------------------------
 *
 * RefreshKeyboardMappingIfNeeded --
 *
 *	If the event is a MappingNotify event, find its display and refresh
 *	the keyboard mapping information for the display.
 *
 * Results:
 *	0 if the event was not a MappingNotify event
 *	1 if the event was a MappingNotify event
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
RefreshKeyboardMappingIfNeeded(
    XEvent *eventPtr)
{
    TkDisplay *dispPtr;

    if (eventPtr->type == MappingNotify) {
	dispPtr = TkGetDisplay(eventPtr->xmapping.display);
	if (dispPtr != NULL) {
	    XRefreshKeyboardMapping(&eventPtr->xmapping);
	    dispPtr->bindInfoStale = 1;
	}
	return 1;
    }
    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * TkGetButtonMask --
 *
 *	Return the proper Button${n}Mask for the button.
 *
 * Results:
 *	A button mask.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static const unsigned buttonMasks[] = {
    0, Button1Mask, Button2Mask, Button3Mask, Button4Mask, Button5Mask
};

unsigned
TkGetButtonMask(
    unsigned button)
{
    return (button > Button5) ? 0 : buttonMasks[button];
}

/*
 *----------------------------------------------------------------------
 *
 * InvokeClientMessageHandlers --
 *
 *	Iterate the list of handlers and invoke the function pointer for each.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Handlers may be deleted and events may be sent to handlers.
 *
 *----------------------------------------------------------------------
 */

static void
InvokeClientMessageHandlers(
    ThreadSpecificData *tsdPtr,
    Tk_Window tkwin,
    XEvent *eventPtr)
{
    GenericHandler *prevPtr, *tmpPtr, *curPtr = tsdPtr->cmList;

    for (prevPtr = NULL; curPtr != NULL; ) {
	if (curPtr->deleteFlag) {
	    if (!tsdPtr->handlersActive) {
		/*
		 * This handler needs to be deleted and there are no calls
		 * pending through any handlers, so now is a safe time to
		 * delete it.
		 */

		tmpPtr = curPtr->nextPtr;
		if (prevPtr == NULL) {
		    tsdPtr->cmList = tmpPtr;
		} else {
		    prevPtr->nextPtr = tmpPtr;
		}
		if (tmpPtr == NULL) {
		    tsdPtr->lastCmPtr = prevPtr;
		}
		ckfree(curPtr);
		curPtr = tmpPtr;
		continue;
	    }
	} else {
	    int done;

	    tsdPtr->handlersActive++;
	    done = (*(Tk_ClientMessageProc *)curPtr->proc)(tkwin, eventPtr);
	    tsdPtr->handlersActive--;
	    if (done) {
		break;
	    }
	}
	prevPtr = curPtr;
	curPtr = curPtr->nextPtr;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * InvokeGenericHandlers --
 *
 *	Iterate the list of handlers and invoke the function pointer for each.
 *	If the handler invoked returns a non-zero value then we are done.
 *
 * Results:
 *	0 when the event wasn't handled by a handler. Non-zero when it was
 *	processed and handled by a handler.
 *
 * Side effects:
 *	Handlers may be deleted and events may be sent to handlers.
 *
 *----------------------------------------------------------------------
 */

static int
InvokeGenericHandlers(
    ThreadSpecificData *tsdPtr,
    XEvent *eventPtr)
{
    GenericHandler *prevPtr, *tmpPtr, *curPtr = tsdPtr->genericList;

    for (prevPtr = NULL; curPtr != NULL; ) {
	if (curPtr->deleteFlag) {
	    if (!tsdPtr->handlersActive) {
		/*
		 * 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 = curPtr->nextPtr;
		if (prevPtr == NULL) {
		    tsdPtr->genericList = tmpPtr;
		} else {
		    prevPtr->nextPtr = tmpPtr;
		}
		if (tmpPtr == NULL) {
		    tsdPtr->lastGenericPtr = prevPtr;
		}
		ckfree(curPtr);
		curPtr = tmpPtr;
		continue;
	    }
	} else {
	    int done;

	    tsdPtr->handlersActive++;
	    done = curPtr->proc(curPtr->clientData, eventPtr);
	    tsdPtr->handlersActive--;
	    if (done) {
		return done;
	    }
	}
	prevPtr = curPtr;
	curPtr = curPtr->nextPtr;
    }
    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_CreateEventHandler --
 *
 *	Arrange for a given function 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(
    Tk_Window token,		/* Token for window in which to create
				 * handler. */
    unsigned long mask,		/* Events for which proc should be called. */
    Tk_EventProc *proc,		/* Function to call for each selected event */
    ClientData clientData)	/* Arbitrary data to pass to proc. */
{
    TkEventHandler *handlerPtr;
    TkWindow *winPtr = (TkWindow *)token;

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

    if (winPtr->handlerList == NULL) {
	/*
	 * No event handlers defined at all, so must create.
	 */

	handlerPtr = (TkEventHandler *)ckalloc(sizeof(TkEventHandler));
	winPtr->handlerList = handlerPtr;
    } else {
	int found = 0;

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

	/*
	 * If we found anything, we're done because we do not need to use
	 * XSelectInput; Tk always selects on all events anyway in order to
	 * support binding on classes, 'all' and other bind-tags.
	 */

	if (found) {
	    return;
	}

	/*
	 * No event handler matched, so create a new one.
	 */

	handlerPtr->nextPtr = (TkEventHandler *)ckalloc(sizeof(TkEventHandler));
	handlerPtr = handlerPtr->nextPtr;
    }

    /*
     * Initialize the new event handler.
     */

    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(
    Tk_Window token,		/* Same as corresponding arguments passed */
    unsigned long mask,		/* previously to Tk_CreateEventHandler. */
    Tk_EventProc *proc,
    ClientData clientData)
{
    TkEventHandler *handlerPtr;
    InProgress *ipPtr;
    TkEventHandler *prevPtr;
    TkWindow *winPtr = (TkWindow *) token;
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));

    /*
     * 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 = tsdPtr->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(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 function 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(
    Tk_GenericProc *proc,	/* Function to call on every event. */
    ClientData clientData)	/* One-word value to pass to proc. */
{
    GenericHandler *handlerPtr;
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));

    handlerPtr = (GenericHandler *)ckalloc(sizeof(GenericHandler));

    handlerPtr->proc		= proc;
    handlerPtr->clientData	= clientData;
    handlerPtr->deleteFlag	= 0;
    handlerPtr->nextPtr		= NULL;
    if (tsdPtr->genericList == NULL) {
	tsdPtr->genericList	= handlerPtr;
    } else {
	tsdPtr->lastGenericPtr->nextPtr = handlerPtr;
    }
    tsdPtr->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(
    Tk_GenericProc *proc,
    ClientData clientData)
{
    GenericHandler * handler;
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));

    for (handler=tsdPtr->genericList ; handler ; handler=handler->nextPtr) {
	if ((handler->proc == proc) && (handler->clientData == clientData)) {
	    handler->deleteFlag = 1;
	}
    }
}

/*----------------------------------------------------------------------
 *
 * Tk_CreateClientMessageHandler --
 *
 *	Register a function to be called on each ClientMessage event.
 *	ClientMessage handlers are useful for Drag&Drop extensions.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	From now on, whenever a ClientMessage event is received that isn't a
 *	WM_PROTOCOL event or SelectionEvent, invoke proc, giving it tkwin and
 *	the event as arguments.
 *
 *----------------------------------------------------------------------
 */

void
Tk_CreateClientMessageHandler(
    Tk_ClientMessageProc *proc)	/* Function to call on event. */
{
    GenericHandler *handlerPtr;
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));

    /*
     * We use a GenericHandler struct, because it's basically the same, except
     * with an extra clientData field we'll never use.
     */

    handlerPtr = (GenericHandler *)ckalloc(sizeof(GenericHandler));

    handlerPtr->proc = (Tk_GenericProc *) proc;
    handlerPtr->clientData = NULL;	/* never used */
    handlerPtr->deleteFlag = 0;
    handlerPtr->nextPtr = NULL;
    if (tsdPtr->cmList == NULL) {
	tsdPtr->cmList = handlerPtr;
    } else {
	tsdPtr->lastCmPtr->nextPtr = handlerPtr;
    }
    tsdPtr->lastCmPtr = handlerPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_DeleteClientMessageHandler --
 *
 *	Delete a previously-created ClientMessage 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
 *	TkClientMessageEventProc.
 *
 *----------------------------------------------------------------------
 */

void
Tk_DeleteClientMessageHandler(
    Tk_ClientMessageProc *proc)
{
    GenericHandler * handler;
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));

    for (handler=tsdPtr->cmList ; handler!=NULL ; handler=handler->nextPtr) {
	if (handler->proc == (Tk_GenericProc *) proc) {
	    handler->deleteFlag = 1;
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkEventInit --
 *
 *	This functions initializes all the event module structures used by the
 *	current thread. It must be called before any other function in this
 *	file is called.
 *
 * Results:
 *	None.
 *
 * Side Effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
TkEventInit(void)
{
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));

    tsdPtr->handlersActive	= 0;
    tsdPtr->pendingPtr		= NULL;
    tsdPtr->genericList		= NULL;
    tsdPtr->lastGenericPtr	= NULL;
    tsdPtr->cmList		= NULL;
    tsdPtr->lastCmPtr		= NULL;
    tsdPtr->restrictProc	= NULL;
    tsdPtr->restrictArg		= NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * TkXErrorHandler --
 *
 *	TkXErrorHandler is an error handler, to be installed via
 *	Tk_CreateErrorHandler, that will set a flag if an X error occurred.
 *
 * Results:
 *	Always returns 0, indicating that the X error was handled.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static int
TkXErrorHandler(
    ClientData clientData,	/* Pointer to flag we set. */
    TCL_UNUSED(XErrorEvent *))	/* X error info. */
{
    int *error = (int *)clientData;

    *error = 1;
    return 0;
}

/*
 *----------------------------------------------------------------------
 *
 * ParentXId --
 *
 *	Returns the parent of the given window, or "None" if the window
 *	doesn't exist.
 *
 * Results:
 *	Returns an X window ID.
 *
 * Side effects:
 *	None.
 *
 *----------------------------------------------------------------------
 */

static Window
ParentXId(
    Display *display,
    Window w)
{
    Tk_ErrorHandler handler;
    int gotXError;
    Status status;
    Window parent;
    Window root;
    Window *childList;
    unsigned int nChildren;

    /*
     * Handle errors ourselves.
     */

    gotXError = 0;
    handler = Tk_CreateErrorHandler(display, -1, -1, -1,
	    TkXErrorHandler, &gotXError);

    /*
     * Get the parent window.
     */

    status = XQueryTree(display, w, &root, &parent, &childList, &nChildren);

    /*
     * Do some cleanup; gotta return "None" if we got an error.
     */

    Tk_DeleteErrorHandler(handler);
    XSync(display, False);
    if (status != 0 && childList != NULL) {
	XFree(childList);
    }
    if (status == 0) {
	parent = None;
    }

    return parent;
}

/*
 *----------------------------------------------------------------------
 *
 * 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(
    XEvent *eventPtr)	/* Event to dispatch. */
{
    TkEventHandler *handlerPtr;
    TkWindow *winPtr;
    unsigned long mask;
    InProgress ip;
    Tcl_Interp *interp = NULL;
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));

#if !defined(MAC_OSX_TK) && !defined(_WIN32)
    if (((eventPtr->type == ButtonPress) || (eventPtr->type == ButtonRelease))
	    && ((eventPtr->xbutton.button - 6) < 2)) {
	eventPtr->xbutton.button -= 2;
	eventPtr->xbutton.state ^= ShiftMask;
    }
#endif

    /*
     * If the generic handler processed this event we are done and can return.
     */

    if (InvokeGenericHandlers(tsdPtr, eventPtr)) {
	goto releaseEventResources;
    }

    if (RefreshKeyboardMappingIfNeeded(eventPtr)) {
	/*
	 * We are done with a MappingNotify event.
	 */

	goto releaseEventResources;
    }

    mask = GetEventMaskFromXEvent(eventPtr);
    winPtr = GetTkWindowFromXEvent(eventPtr);

    if (winPtr == NULL) {
	goto releaseEventResources;
    }

    /*
     * Once a window has started getting deleted, don't process any more
     * events for it except for the DestroyNotify event. This check is needed
     * because a DestroyNotify handler could re-invoke the event loop, causing
     * other pending events to be handled for the window (the window doesn't
     * get totally expunged from our tables until after the DestroyNotify
     * event has been completely handled).
     */

    if ((winPtr->flags & TK_ALREADY_DEAD)
	    && (eventPtr->type != DestroyNotify)) {
	goto releaseEventResources;
    }

    if (winPtr->mainPtr != NULL) {
	int result;

	interp = winPtr->mainPtr->interp;

	/*
	 * Protect interpreter for this window from possible deletion while we
	 * are dealing with the event for this window. Thus, widget writers do
	 * not have to worry about protecting the interpreter in their own
	 * code.
	 */

	Tcl_Preserve(interp);

	result = ((InvokeFocusHandlers(&winPtr, mask, eventPtr))
		|| (InvokeMouseHandlers(winPtr, mask, eventPtr)));

	if (result) {
	    goto releaseInterpreter;
	}
    }

    /*
     * Create the input context for the window if it hasn't already been done
     * (XFilterEvent needs this context). When the event is a FocusIn event,
     * set the input context focus to the receiving window. This code is only
     * ever active for X11.
     */

#ifdef TK_USE_INPUT_METHODS
    /*
     * If the XIC has been invalidated, it must be recreated.
     */
    if (winPtr->dispPtr->ximGeneration != winPtr->ximGeneration) {
	winPtr->flags &= ~TK_CHECKED_IC;
	winPtr->inputContext = NULL;
    }

    if ((winPtr->dispPtr->flags & TK_DISPLAY_USE_IM)) {
	if (!(winPtr->flags & (TK_CHECKED_IC|TK_ALREADY_DEAD))) {
	    winPtr->flags |= TK_CHECKED_IC;
	    if (winPtr->dispPtr->inputMethod != NULL) {
		CreateXIC(winPtr);
	    }
	}
	if ((eventPtr->type == FocusIn) &&
		(winPtr->dispPtr->inputMethod != NULL) &&
		(winPtr->inputContext != NULL)) {
	    XSetICFocus(winPtr->inputContext);
	}
    }
#endif /*TK_USE_INPUT_METHODS*/

    /*
     * 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;
    }

    /*
     * 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 = tsdPtr->pendingPtr;
    tsdPtr->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) {
	    if (eventPtr->xclient.message_type ==
		    Tk_InternAtom((Tk_Window) winPtr, "WM_PROTOCOLS")) {
		TkWmProtocolEventProc(winPtr, eventPtr);
	    } else {
		InvokeClientMessageHandlers(tsdPtr, (Tk_Window) winPtr,
			eventPtr);
	    }
	}
    } else {
	for (handlerPtr = winPtr->handlerList; handlerPtr != NULL; ) {
	    if (handlerPtr->mask & mask) {
		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 functions.
	 */

	/*
	 * ...well, except when we use the tkwm patches, in which case we DO
	 * handle CreateNotify events, so we gotta pass 'em through.
	 */

	if ((ip.winPtr != NULL)
		&& ((mask != SubstructureNotifyMask)
		|| (eventPtr->type == CreateNotify))) {
	    TkBindEventProc(winPtr, eventPtr);
	}
    }
    tsdPtr->pendingPtr = ip.nextPtr;

    /*
     * Release the interpreter for this window so that it can be potentially
     * deleted if requested.
     */

  releaseInterpreter:
    if (interp != NULL) {
	Tcl_Release(interp);
    }

    /*
     * Release the user_data from the event (if it is a virtual event and the
     * field was non-NULL in the first place.) Note that this is done using a
     * Tcl_Obj interface, and we set the field back to NULL afterwards out of
     * paranoia. Also clean up any cached %A substitutions from key events.
     */

  releaseEventResources:
    CleanUpTkEvent(eventPtr);
}

/*
 *----------------------------------------------------------------------
 *
 * TkEventDeadWindow --
 *
 *	This function 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(
    TkWindow *winPtr)		/* Information about the window that is being
				 * deleted. */
{
    TkEventHandler *handlerPtr;
    InProgress *ipPtr;
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));

    /*
     * 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 = tsdPtr->pendingPtr; ipPtr != NULL;
		ipPtr = ipPtr->nextPtr) {
	    if (ipPtr->nextHandler == handlerPtr) {
		ipPtr->nextHandler = NULL;
	    }
	    if (ipPtr->winPtr == winPtr) {
		ipPtr->winPtr = NULL;
	    }
	}
	ckfree(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(
    TkDisplay *dispPtr)		/* Display for which the time is desired. */
{
    XEvent *eventPtr;
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));

    if (tsdPtr->pendingPtr == NULL) {
	return dispPtr->lastEventTime;
    }
    eventPtr = tsdPtr->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;
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_RestrictEvents --
 *
 *	This function 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 function that determines whether they are to be
 *	processed immediately, deferred, or discarded.
 *
 * Results:
 *	The return value is the previous restriction function 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,
 *	defer or discard each incoming X event.
 *
 *----------------------------------------------------------------------
 */

Tk_RestrictProc *
Tk_RestrictEvents(
    Tk_RestrictProc *proc,	/* Function to call for each incoming event */
    ClientData arg,		/* Arbitrary argument to pass to proc. */
    ClientData *prevArgPtr)	/* Place to store information about previous
				 * argument. */
{
    Tk_RestrictProc *prev;
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));

    prev = tsdPtr->restrictProc;
    *prevArgPtr = tsdPtr->restrictArg;
    tsdPtr->restrictProc = proc;
    tsdPtr->restrictArg = arg;
    return prev;
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_CollapseMotionEvents --
 *
 *	This function controls whether we collapse motion events in a
 *	particular display or not.
 *
 * Results:
 *	The return value is the previous collapse value in effect.
 *
 * Side effects:
 *	Filtering of motion events may be changed after calling this.
 *
 *----------------------------------------------------------------------
 */

int
Tk_CollapseMotionEvents(
    Display *display,		/* Display handling these events. */
    int collapse)		/* Boolean value that specifies whether motion
				 * events should be collapsed. */
{
    TkDisplay *dispPtr = (TkDisplay *) display;
    int prev = (dispPtr->flags & TK_DISPLAY_COLLAPSE_MOTION_EVENTS);

    if (collapse) {
	dispPtr->flags |= TK_DISPLAY_COLLAPSE_MOTION_EVENTS;
    } else {
	dispPtr->flags &= ~TK_DISPLAY_COLLAPSE_MOTION_EVENTS;
    }
    return prev;
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_QueueWindowEvent --
 *
 *	Given an X-style window event, this function adds it to the Tcl event
 *	queue at the given position. This function also performs mouse motion
 *	event collapsing if possible.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Adds stuff to the event queue, which will eventually be processed.
 *
 *----------------------------------------------------------------------
 */

void
Tk_QueueWindowEvent(
    XEvent *eventPtr,		/* Event to add to queue. This function copies
				 * it before adding it to the queue. */
    Tcl_QueuePosition position)	/* Where to put it on the queue:
				 * TCL_QUEUE_TAIL, TCL_QUEUE_HEAD, or
				 * TCL_QUEUE_MARK. */
{
    TkWindowEvent *wevPtr;
    TkDisplay *dispPtr;

    /*
     * Find our display structure for the event's display.
     */

    for (dispPtr = TkGetDisplayList(); ; dispPtr = dispPtr->nextPtr) {
	if (dispPtr == NULL) {
	    return;
	}
	if (dispPtr->display == eventPtr->xany.display) {
	    break;
	}
    }

    /*
     * Don't filter motion events if the user defaulting to true (1), which
     * could be set to false (0) when the user wishes to receive all the
     * motion data)
     */

    if (!(dispPtr->flags & TK_DISPLAY_COLLAPSE_MOTION_EVENTS)) {
	wevPtr = (TkWindowEvent *)ckalloc(sizeof(TkWindowEvent));
	wevPtr->header.proc = WindowEventProc;
	wevPtr->event = *eventPtr;
	Tcl_QueueEvent(&wevPtr->header, position);
	return;
    }

    if ((dispPtr->delayedMotionPtr != NULL) && (position == TCL_QUEUE_TAIL)) {
	if ((eventPtr->type == MotionNotify) && (eventPtr->xmotion.window
		== dispPtr->delayedMotionPtr->event.xmotion.window)) {
	    /*
	     * The new event is a motion event in the same window as the saved
	     * motion event. Just replace the saved event with the new one.
	     */

	    dispPtr->delayedMotionPtr->event = *eventPtr;
	    return;
	} else if ((eventPtr->type != GraphicsExpose)
		&& (eventPtr->type != NoExpose)
		&& (eventPtr->type != Expose)) {
	    /*
	     * The new event may conflict with the saved motion event. Queue
	     * the saved motion event now so that it will be processed before
	     * the new event.
	     */

	    Tcl_QueueEvent(&dispPtr->delayedMotionPtr->header, position);
	    dispPtr->delayedMotionPtr = NULL;
	    Tcl_CancelIdleCall(DelayedMotionProc, dispPtr);
	}
    }

    wevPtr = (TkWindowEvent *)ckalloc(sizeof(TkWindowEvent));
    wevPtr->header.proc = WindowEventProc;
    wevPtr->event = *eventPtr;
    if ((eventPtr->type == MotionNotify) && (position == TCL_QUEUE_TAIL)) {
	/*
	 * The new event is a motion event so don't queue it immediately; save
	 * it around in case another motion event arrives that it can be
	 * collapsed with.
	 */

	if (dispPtr->delayedMotionPtr != NULL) {
	    Tcl_Panic("Tk_QueueWindowEvent found unexpected delayed motion event");
	}
	dispPtr->delayedMotionPtr = wevPtr;
	Tcl_DoWhenIdle(DelayedMotionProc, dispPtr);
    } else {
	Tcl_QueueEvent(&wevPtr->header, position);
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkQueueEventForAllChildren --
 *
 *	Given an XEvent, recursively queue the event for this window and all
 *	non-toplevel children of the given window.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Events queued.
 *
 *----------------------------------------------------------------------
 */

void
TkQueueEventForAllChildren(
    TkWindow *winPtr,	    /* Window to which event is sent. */
    XEvent *eventPtr)	    /* The event to be sent. */
{
    TkWindow *childPtr;

    if (!Tk_IsMapped(winPtr)) {
	return;
    }

    eventPtr->xany.window = winPtr->window;
    Tk_QueueWindowEvent(eventPtr, TCL_QUEUE_TAIL);

    childPtr = winPtr->childList;
    while (childPtr != NULL) {
	if (!Tk_TopWinHierarchy(childPtr)) {
	    TkQueueEventForAllChildren(childPtr, eventPtr);
	}
	childPtr = childPtr->nextPtr;
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkGenerateActivateEvents --
 *
 *	This function is called by the Mac and Windows window manager routines
 *	when a toplevel window is activated or deactivated.
 *	Activate/Deactivate events will be sent to every subwindow of the
 *	toplevel followed by a FocusIn/FocusOut message.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Generates X events.
 *
 *----------------------------------------------------------------------
 */

void
TkGenerateActivateEvents(
    TkWindow *winPtr,		/* Toplevel to activate. */
    int active)			/* Non-zero if the window is being activated,
				 * else 0.*/
{
    XEvent event;

    /*
     * Generate Activate and Deactivate events. This event is sent to every
     * subwindow in a toplevel window.
     */

    event.xany.serial = NextRequest(winPtr->display);
    event.xany.send_event = False;
    event.xany.display = winPtr->display;
    event.xany.window = winPtr->window;

    event.xany.type = active ? ActivateNotify : DeactivateNotify;
    TkQueueEventForAllChildren(winPtr, &event);
}

/*
 *----------------------------------------------------------------------
 *
 * WindowEventProc --
 *
 *	This function is called by Tcl_DoOneEvent when a window event reaches
 *	the front of the event queue. This function is responsible for
 *	actually handling the event.
 *
 * Results:
 *	Returns 1 if the event was handled, meaning it should be removed from
 *	the queue. Returns 0 if the event was not handled, meaning it should
 *	stay on the queue. The event isn't handled if the TCL_WINDOW_EVENTS
 *	bit isn't set in flags, if a restrict proc prevents the event from
 *	being handled.
 *
 * Side effects:
 *	Whatever the event handlers for the event do.
 *
 *----------------------------------------------------------------------
 */

static int
WindowEventProc(
    Tcl_Event *evPtr,		/* Event to service. */
    int flags)			/* Flags that indicate what events to handle,
				 * such as TCL_WINDOW_EVENTS. */
{
    TkWindowEvent *wevPtr = (TkWindowEvent *) evPtr;
    Tk_RestrictAction result;
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));

    if (!(flags & TCL_WINDOW_EVENTS)) {
	return 0;
    }
    if (tsdPtr->restrictProc != NULL) {
	result = tsdPtr->restrictProc(tsdPtr->restrictArg, &wevPtr->event);
	if (result != TK_PROCESS_EVENT) {
	    if (result == TK_DEFER_EVENT) {
		return 0;
	    } else {
		/*
		 * TK_DELETE_EVENT: return and say we processed the event,
		 * even though we didn't do anything at all.
		 */

		CleanUpTkEvent(&wevPtr->event);
		return 1;
	    }
	}
    }
    Tk_HandleEvent(&wevPtr->event);
    CleanUpTkEvent(&wevPtr->event);
    return 1;
}

/*
 *----------------------------------------------------------------------
 *
 * CleanUpTkEvent --
 *
 *	This function is called to remove and deallocate any information in
 *	the event which is not directly in the event structure itself. It may
 *	be called multiple times per event, so it takes care to set the
 *	cleared pointer fields to NULL afterwards.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Makes the event no longer have any external resources.
 *
 *----------------------------------------------------------------------
 */

static void
CleanUpTkEvent(
    XEvent *eventPtr)
{
    switch (eventPtr->type) {
    case KeyPress:
    case KeyRelease: {

#if !defined(_WIN32) && !defined(MAC_OSX_TK)
	TkKeyEvent *kePtr = (TkKeyEvent *) eventPtr;
	if (kePtr->charValuePtr != NULL) {
	    ckfree(kePtr->charValuePtr);
	    kePtr->charValuePtr = NULL;
	    kePtr->charValueLen = 0;
	}
#endif
	break;
    }

    case VirtualEvent: {
	XVirtualEvent *vePtr = (XVirtualEvent *) eventPtr;

	if (vePtr->user_data != NULL) {
	    Tcl_DecrRefCount(vePtr->user_data);
	    vePtr->user_data = NULL;
	}
	break;
    }
    }
}

/*
 *----------------------------------------------------------------------
 *
 * DelayedMotionProc --
 *
 *	This function is invoked as an idle handler when a mouse motion event
 *	has been delayed. It queues the delayed event so that it will finally
 *	be serviced.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	The delayed mouse motion event gets added to the Tcl event queue for
 *	servicing.
 *
 *----------------------------------------------------------------------
 */

static void
DelayedMotionProc(
    ClientData clientData)	/* Pointer to display containing a delayed
				 * motion event to be serviced. */
{
    TkDisplay *dispPtr = (TkDisplay *)clientData;

    if (dispPtr->delayedMotionPtr == NULL) {
	Tcl_Panic("DelayedMotionProc found no delayed mouse motion event");
    }
    Tcl_QueueEvent(&dispPtr->delayedMotionPtr->header, TCL_QUEUE_TAIL);
    dispPtr->delayedMotionPtr = NULL;
}

/*
 *----------------------------------------------------------------------
 *
 * TkCreateExitHandler --
 *
 *	Same as Tcl_CreateExitHandler, but private to Tk.
 *
 * Results:
 *	None.
 *
 * Side effects.
 *	Sets a handler with Tcl_CreateExitHandler if this is the first call.
 *
 *----------------------------------------------------------------------
 */

void
TkCreateExitHandler(
    Tcl_ExitProc *proc,		/* Function to invoke. */
    ClientData clientData)	/* Arbitrary value to pass to proc. */
{
    ExitHandler *exitPtr;

    exitPtr = (ExitHandler *)ckalloc(sizeof(ExitHandler));
    exitPtr->proc = proc;
    exitPtr->clientData = clientData;
    Tcl_MutexLock(&exitMutex);

    /*
     * The call to TclInExit() is disabled here. That's a private Tcl routine,
     * and calling it is causing some trouble with portability of building Tk.
     * We should avoid private Tcl routines generally.
     *
     * In this case, the TclInExit() call is being used only to prevent a
     * Tcl_CreateExitHandler() call when Tcl finalization is in progress.
     * That's a situation that shouldn't happen anyway. Recent changes within
     * Tcl_Finalize now cause a Tcl_Panic() to happen if exit handlers get
     * added after exit handling is complete. By disabling the guard here,
     * that panic will serve to help us find the buggy conditions and correct
     * them.
     *
     * We can restore this guard if we find we must (hopefully getting public
     * access to TclInExit() if we discover extensions really do need this),
     * but during alpha development, this is a good time to dig in and find
     * the root causes of finalization bugs.
     */

    if (firstExitPtr == NULL/* && !TclInExit()*/) {
	Tcl_CreateExitHandler(TkFinalize, NULL);
    }
    exitPtr->nextPtr = firstExitPtr;
    firstExitPtr = exitPtr;
    Tcl_MutexUnlock(&exitMutex);
}

/*
 *----------------------------------------------------------------------
 *
 * TkDeleteExitHandler --
 *
 *	Same as Tcl_DeleteExitHandler, but private to Tk.
 *
 * Results:
 *	None.
 *
 * Side effects.
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
TkDeleteExitHandler(
    Tcl_ExitProc *proc,		/* Function that was previously registered. */
    ClientData clientData)	/* Arbitrary value to pass to proc. */
{
    ExitHandler *exitPtr, *prevPtr;

    Tcl_MutexLock(&exitMutex);
    for (prevPtr = NULL, exitPtr = firstExitPtr; exitPtr != NULL;
	    prevPtr = exitPtr, exitPtr = exitPtr->nextPtr) {
	if ((exitPtr->proc == proc)
		&& (exitPtr->clientData == clientData)) {
	    if (prevPtr == NULL) {
		firstExitPtr = exitPtr->nextPtr;
	    } else {
		prevPtr->nextPtr = exitPtr->nextPtr;
	    }
	    ckfree(exitPtr);
	    break;
	}
    }
    Tcl_MutexUnlock(&exitMutex);
    return;
}

/*
 *----------------------------------------------------------------------
 *
 * TkCreateThreadExitHandler --
 *
 *	Same as Tcl_CreateThreadExitHandler, but private to Tk.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	Proc will be invoked with clientData as argument when the application
 *	exits.
 *
 *----------------------------------------------------------------------
 */

void
TkCreateThreadExitHandler(
    Tcl_ExitProc *proc,		/* Function to invoke. */
    ClientData clientData)	/* Arbitrary value to pass to proc. */
{
    ExitHandler *exitPtr;
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));

    exitPtr = (ExitHandler *)ckalloc(sizeof(ExitHandler));
    exitPtr->proc = proc;
    exitPtr->clientData = clientData;

    /*
     * See comments in TkCreateExitHandler().
     */

    if (tsdPtr->firstExitPtr == NULL/* && !TclInExit()*/) {
	Tcl_CreateThreadExitHandler(TkFinalizeThread, NULL);
    }
    exitPtr->nextPtr = tsdPtr->firstExitPtr;
    tsdPtr->firstExitPtr = exitPtr;
}

/*
 *----------------------------------------------------------------------
 *
 * TkDeleteThreadExitHandler --
 *
 *	Same as Tcl_DeleteThreadExitHandler, but private to Tk.
 *
 * Results:
 *	None.
 *
 * Side effects:
 *	If there is an exit handler corresponding to proc and clientData then
 *	it is cancelled; if no such handler exists then nothing happens.
 *
 *----------------------------------------------------------------------
 */

void
TkDeleteThreadExitHandler(
    Tcl_ExitProc *proc,		/* Function that was previously registered. */
    ClientData clientData)	/* Arbitrary value to pass to proc. */
{
    ExitHandler *exitPtr, *prevPtr;
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));

    for (prevPtr = NULL, exitPtr = tsdPtr->firstExitPtr; exitPtr != NULL;
	    prevPtr = exitPtr, exitPtr = exitPtr->nextPtr) {
	if ((exitPtr->proc == proc)
		&& (exitPtr->clientData == clientData)) {
	    if (prevPtr == NULL) {
		tsdPtr->firstExitPtr = exitPtr->nextPtr;
	    } else {
		prevPtr->nextPtr = exitPtr->nextPtr;
	    }
	    ckfree(exitPtr);
	    return;
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * TkFinalize --
 *
 *	Runs our private exit handlers and removes itself from Tcl. This is
 *	benificial should we want to protect from dangling pointers should the
 *	Tk shared library be unloaded prior to Tcl which can happen on windows
 *	should the process be forcefully exiting from an exception handler.
 *
 * Results:
 *	None.
 *
 * Side effects.
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
TkFinalize(
    TCL_UNUSED(void *))	/* Arbitrary value to pass to proc. */
{
    ExitHandler *exitPtr;

#if defined(_WIN32) && !defined(STATIC_BUILD)
    if (!tclStubsPtr) {
	return;
    }
#endif

    Tcl_DeleteExitHandler(TkFinalize, NULL);

    Tcl_MutexLock(&exitMutex);
    for (exitPtr = firstExitPtr; exitPtr != NULL; exitPtr = firstExitPtr) {
	/*
	 * Be careful to remove the handler from the list before invoking its
	 * callback. This protects us against double-freeing if the callback
	 * should call TkDeleteExitHandler on itself.
	 */

	firstExitPtr = exitPtr->nextPtr;
	Tcl_MutexUnlock(&exitMutex);
	exitPtr->proc(exitPtr->clientData);
	ckfree(exitPtr);
	Tcl_MutexLock(&exitMutex);
    }
    firstExitPtr = NULL;
    Tcl_MutexUnlock(&exitMutex);
}

/*
 *----------------------------------------------------------------------
 *
 * TkFinalizeThread --
 *
 *	Runs our private thread exit handlers and removes itself from Tcl.
 *	This is beneficial should we want to protect from dangling pointers
 *	should the Tk shared library be unloaded prior to Tcl which can happen
 *	on Windows should the process be forcefully exiting from an exception
 *	handler.
 *
 * Results:
 *	None.
 *
 * Side effects.
 *	None.
 *
 *----------------------------------------------------------------------
 */

void
TkFinalizeThread(
    TCL_UNUSED(void *))	/* Arbitrary value to pass to proc. */
{
    ExitHandler *exitPtr;
    ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
	    Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));

    Tcl_DeleteThreadExitHandler(TkFinalizeThread, NULL);

    if (tsdPtr != NULL) {
	tsdPtr->inExit = 1;

	for (exitPtr = tsdPtr->firstExitPtr; exitPtr != NULL;
		exitPtr = tsdPtr->firstExitPtr) {
	    /*
	     * Be careful to remove the handler from the list before invoking
	     * its callback. This protects us against double-freeing if the
	     * callback should call TkDeleteThreadExitHandler on itself.
	     */

	    tsdPtr->firstExitPtr = exitPtr->nextPtr;
	    exitPtr->proc(exitPtr->clientData);
	    ckfree(exitPtr);
	}
    }
}

/*
 *----------------------------------------------------------------------
 *
 * Tk_MainLoop --
 *
 *	Call Tcl_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(void)
{
    while (Tk_GetNumMainWindows() > 0) {
	Tcl_DoOneEvent(0);
    }
}

/*
 * Local Variables:
 * mode: c
 * c-basic-offset: 4
 * fill-column: 78
 * End:
 */
