/* * 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. * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * * RCS: @(#) $Id: tkEvent.c,v 1.16 2002/06/19 19:37:54 mdejong Exp $ */ #include "tkPort.h" #include "tkInt.h" #include /* * 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; /* * 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; /* * 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 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 */ }; /* * 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 ThreadSpecificData { 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. */ GenericHandler *genericList; /* First handler in the list, or NULL. */ GenericHandler *lastGenericPtr; /* Last handler in list. */ 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; /* Procedure to call. NULL means no * restrictProc is currently in effect. */ ClientData restrictArg; /* Argument to pass to restrictProc. */ } ThreadSpecificData; static Tcl_ThreadDataKey dataKey; /* * Prototypes for procedures that are only referenced locally within * this file. */ static void DelayedMotionProc _ANSI_ARGS_((ClientData clientData)); static int WindowEventProc _ANSI_ARGS_((Tcl_Event *evPtr, int flags)); static int TkXErrorHandler _ANSI_ARGS_((ClientData clientData, XErrorEvent *errEventPtr)); /* *-------------------------------------------------------------- * * 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; 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((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; 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(proc, clientData) 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 procedure 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(proc) Tk_ClientMessageProc *proc; /* Procedure 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(proc) 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 procedures initializes all the event module * structures used by the current thread. It must be * called before any other procedure in this file is * called. * * Results: * None. * * Side Effects: * None. * *-------------------------------------------------------------- */ void TkEventInit _ANSI_ARGS_((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, errEventPtr) ClientData clientData; /* Pointer to flag we set */ XErrorEvent *errEventPtr; /* X error info */ { int *error; 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, w) 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, (ClientData) (&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(eventPtr) XEvent *eventPtr; /* Event to dispatch. */ { register TkEventHandler *handlerPtr; register GenericHandler *genericPtr; register GenericHandler *genPrevPtr; TkWindow *winPtr; unsigned long mask; InProgress ip; Window handlerWindow; Window parentXId; TkDisplay *dispPtr; Tcl_Interp *interp = (Tcl_Interp *) NULL; ThreadSpecificData *tsdPtr = (ThreadSpecificData *) Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); /* * Hack for simulated X-events: Correct the state field * of the event record to match with the ButtonPress * and ButtonRelease events. */ if (eventPtr->type==ButtonPress) { dispPtr = TkGetDisplay(eventPtr->xbutton.display); eventPtr->xbutton.state |= dispPtr->mouseButtonState; switch (eventPtr->xbutton.button) { case 1: dispPtr->mouseButtonState |= Button1Mask; break; case 2: dispPtr->mouseButtonState |= Button2Mask; break; case 3: dispPtr->mouseButtonState |= Button3Mask; break; } } else if (eventPtr->type==ButtonRelease) { dispPtr = TkGetDisplay(eventPtr->xbutton.display); switch (eventPtr->xbutton.button) { case 1: dispPtr->mouseButtonState &= ~Button1Mask; break; case 2: dispPtr->mouseButtonState &= ~Button2Mask; break; case 3: dispPtr->mouseButtonState &= ~Button3Mask; break; } eventPtr->xbutton.state |= dispPtr->mouseButtonState; } else if (eventPtr->type==MotionNotify) { dispPtr = TkGetDisplay(eventPtr->xmotion.display); eventPtr->xmotion.state |= dispPtr->mouseButtonState; } /* * 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 = tsdPtr->genericList; genericPtr != NULL; ) { if (genericPtr->deleteFlag) { if (!tsdPtr->handlersActive) { 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) { tsdPtr->genericList = tmpPtr; } else { genPrevPtr->nextPtr = tmpPtr; } if (tmpPtr == NULL) { tsdPtr->lastGenericPtr = genPrevPtr; } (void) ckfree((char *) genericPtr); genericPtr = tmpPtr; continue; } } else { int done; tsdPtr->handlersActive++; done = (*genericPtr->proc)(genericPtr->clientData, eventPtr); tsdPtr->handlersActive--; 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. */ if (eventPtr->type == MappingNotify) { dispPtr = TkGetDisplay(eventPtr->xmapping.display); if (dispPtr != NULL) { XRefreshKeyboardMapping(&eventPtr->xmapping); dispPtr->bindInfoStale = 1; } return; } /* * 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. */ 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; } } 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; } TkSelPropProc(eventPtr); /* Get handlerWindow's parent. */ parentXId = ParentXId(eventPtr->xany.display, handlerWindow); if (parentXId == None) { return; } winPtr = (TkWindow *) Tk_IdToWindow(eventPtr->xany.display, parentXId); if (winPtr == NULL) { return; } if (!(winPtr->flags & TK_PROP_PROPCHANGE)) { return; } handlerWindow = parentXId; } /* * 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)) { return; } if (winPtr->mainPtr != NULL) { /* * 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. */ interp = winPtr->mainPtr->interp; Tcl_Preserve((ClientData) interp); /* * 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)) { Tcl_Release((ClientData) interp); return; } /* * Redirect KeyPress and KeyRelease events to the focus window, * or ignore them entirely if there is no focus window. We also * route the MouseWheel event to the focus window. The MouseWheel * event is an extension to the X event set. Currently, it is only * available on the Windows version of Tk. */ if (mask & (KeyPressMask|KeyReleaseMask|MouseWheelMask)) { winPtr->dispPtr->lastEventTime = eventPtr->xkey.time; winPtr = TkFocusKeyEvent(winPtr, eventPtr); if (winPtr == NULL) { Tcl_Release((ClientData) interp); return; } } /* * 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) { goto done; } } } #ifdef TK_USE_INPUT_METHODS /* * Pass the event to the input method(s), if there are any, and * discard the event if the input method(s) insist. Create the * input context for the window if it hasn't already been done * (XFilterEvent needs this context). XIM is only ever enabled on * Unix, but this hasn't been factored out of the generic code yet. */ dispPtr = winPtr->dispPtr; if ((dispPtr->flags & TK_DISPLAY_USE_IM)) { if (!(winPtr->flags & (TK_CHECKED_IC|TK_ALREADY_DEAD))) { winPtr->flags |= TK_CHECKED_IC; if (dispPtr->inputMethod != NULL) { #if TK_XIM_SPOT if (dispPtr->flags & TK_DISPLAY_XIM_SPOT) { XVaNestedList preedit_attr; XPoint spot = {0, 0}; if (dispPtr->inputXfs == NULL) { /* * We only need to create one XFontSet */ char **missing_list; int missing_count; char *def_string; dispPtr->inputXfs = XCreateFontSet(dispPtr->display, "-*-*-*-R-Normal--14-130-75-75-*-*", &missing_list, &missing_count, &def_string); if (missing_count > 0) { XFreeStringList(missing_list); } } preedit_attr = XVaCreateNestedList(0, XNSpotLocation, &spot, XNFontSet, dispPtr->inputXfs, NULL); if (winPtr->inputContext != NULL) panic("inputContext not NULL"); winPtr->inputContext = XCreateIC(dispPtr->inputMethod, XNInputStyle, XIMPreeditPosition|XIMStatusNothing, XNClientWindow, winPtr->window, XNFocusWindow, winPtr->window, XNPreeditAttributes, preedit_attr, NULL); XFree(preedit_attr); } else #endif if (winPtr->inputContext != NULL) panic("inputContext not NULL"); winPtr->inputContext = XCreateIC(dispPtr->inputMethod, XNInputStyle, XIMPreeditNothing|XIMStatusNothing, XNClientWindow, winPtr->window, XNFocusWindow, winPtr->window, NULL); } } if (XFilterEvent(eventPtr, None)) { goto done; } } #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 { /* * Finally, invoke any ClientMessage event handlers. */ for (genPrevPtr = NULL, genericPtr = tsdPtr->cmList; genericPtr != NULL; ) { if (genericPtr->deleteFlag) { if (!tsdPtr->handlersActive) { GenericHandler *tmpPtr; /* * 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 = genericPtr->nextPtr; if (genPrevPtr == NULL) { tsdPtr->cmList = tmpPtr; } else { genPrevPtr->nextPtr = tmpPtr; } if (tmpPtr == NULL) { tsdPtr->lastGenericPtr = genPrevPtr; } (void) ckfree((char *) genericPtr); genericPtr = tmpPtr; continue; } } else { int done; tsdPtr->handlersActive++; done = (*(Tk_ClientMessageProc *)genericPtr->proc) ((Tk_Window) winPtr, eventPtr); tsdPtr->handlersActive--; if (done) { break; } } genPrevPtr = genericPtr; genericPtr = genPrevPtr->nextPtr; } } } } 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. */ /* * ...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 != None) && ((mask != SubstructureNotifyMask) || (eventPtr->type == CreateNotify))) { TkBindEventProc(winPtr, eventPtr); } } tsdPtr->pendingPtr = ip.nextPtr; done: /* * Release the interpreter for this window so that it can be potentially * deleted if requested. */ if (interp != (Tcl_Interp *) NULL) { Tcl_Release((ClientData) interp); } } /* *-------------------------------------------------------------- * * 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; 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 = 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; 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 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, deferred, or * discarded. * * 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, * defer or discard each incoming X event. * *---------------------------------------------------------------------- */ Tk_RestrictProc * Tk_RestrictEvents(proc, arg, prevArgPtr) Tk_RestrictProc *proc; /* Procedure 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 procedure 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, collapse) 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 procedure adds it to the * Tcl event queue at the given position. This procedure 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(eventPtr, position) XEvent *eventPtr; /* Event to add to queue. This * procedures 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, (ClientData) 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) { panic("Tk_QueueWindowEvent found unexpected delayed motion event"); } dispPtr->delayedMotionPtr = wevPtr; Tcl_DoWhenIdle(DelayedMotionProc, (ClientData) 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(winPtr, eventPtr) TkWindow *winPtr; /* Window to which event is sent. */ XEvent *eventPtr; /* The event to be sent. */ { TkWindow *childPtr; 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; } } /* *---------------------------------------------------------------------- * * WindowEventProc -- * * This procedure is called by Tcl_DoOneEvent when a window event * reaches the front of the event queue. This procedure 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(evPtr, flags) 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. */ return 1; } } } Tk_HandleEvent(&wevPtr->event); return 1; } /* *---------------------------------------------------------------------- * * DelayedMotionProc -- * * This procedure 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 clientData; /* Pointer to display containing a delayed * motion event to be serviced. */ { TkDisplay *dispPtr = (TkDisplay *) clientData; if (dispPtr->delayedMotionPtr == NULL) { panic("DelayedMotionProc found no delayed mouse motion event"); } Tcl_QueueEvent(&dispPtr->delayedMotionPtr->header, TCL_QUEUE_TAIL); dispPtr->delayedMotionPtr = NULL; } /* *-------------------------------------------------------------- * * 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() { while (Tk_GetNumMainWindows() > 0) { Tcl_DoOneEvent(0); } }