diff options
Diffstat (limited to 'tk8.6/generic/tkEvent.c')
-rw-r--r-- | tk8.6/generic/tkEvent.c | 2147 |
1 files changed, 2147 insertions, 0 deletions
diff --git a/tk8.6/generic/tkEvent.c b/tk8.6/generic/tkEvent.c new file mode 100644 index 0000000..95aeda1 --- /dev/null +++ b/tk8.6/generic/tkEvent.c @@ -0,0 +1,2147 @@ +/* + * 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 realEventMasks[MappingNotify+1] = { + 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 */ +}; + +static const unsigned long virtualEventMasks[TK_LASTEVENT-VirtualEvent] = { + 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 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. */ + + /* + * 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 int GetButtonMask(unsigned int Button); +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 void UpdateButtonEventState(XEvent *eventPtr); +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; + } + + /* + * 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; + + /* + * Get the event mask from the correct table. Note that there are two + * tables here because that means we no longer need this code to rely on + * the exact value of VirtualEvent, which has caused us problems in the + * past when X11 changed the value of LASTEvent. [Bug ???] + */ + + if (eventPtr->xany.type <= MappingNotify) { + mask = realEventMasks[eventPtr->xany.type]; + } else if (eventPtr->xany.type >= VirtualEvent + && eventPtr->xany.type<TK_LASTEVENT) { + mask = virtualEventMasks[eventPtr->xany.type - VirtualEvent]; + } 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; +} + +/* + *---------------------------------------------------------------------- + * + * GetButtonMask -- + * + * Return the proper Button${n}Mask for the button. + * + * Results: + * A button mask. + * + * Side effects: + * None. + * + *---------------------------------------------------------------------- + */ + +static int +GetButtonMask( + unsigned int button) +{ + switch (button) { + case 1: + return Button1Mask; + case 2: + return Button2Mask; + case 3: + return Button3Mask; + case 4: + return Button4Mask; + case 5: + return Button5Mask; + } + return 0; +} + +/* + *---------------------------------------------------------------------- + * + * UpdateButtonEventState -- + * + * Update the button event state in our TkDisplay using the XEvent + * passed. We also may modify the the XEvent passed to fit some aspects + * of our TkDisplay. + * + * Results: + * None. + * + * Side effects: + * The TkDisplay's private button state may be modified. The eventPtr's + * state may be updated to reflect masks stored in our TkDisplay that the + * event doesn't contain. The eventPtr may also be modified to not + * contain a button state for the window in which it was not pressed in. + * + *---------------------------------------------------------------------- + */ + +static void +UpdateButtonEventState( + XEvent *eventPtr) +{ + TkDisplay *dispPtr; + int allButtonsMask = Button1Mask | Button2Mask | Button3Mask + | Button4Mask | Button5Mask; + + switch (eventPtr->type) { + case ButtonPress: + dispPtr = TkGetDisplay(eventPtr->xbutton.display); + dispPtr->mouseButtonWindow = eventPtr->xbutton.window; + eventPtr->xbutton.state |= dispPtr->mouseButtonState; + + dispPtr->mouseButtonState |= GetButtonMask(eventPtr->xbutton.button); + break; + + case ButtonRelease: + dispPtr = TkGetDisplay(eventPtr->xbutton.display); + dispPtr->mouseButtonWindow = None; + dispPtr->mouseButtonState &= ~GetButtonMask(eventPtr->xbutton.button); + eventPtr->xbutton.state |= dispPtr->mouseButtonState; + break; + + case MotionNotify: + dispPtr = TkGetDisplay(eventPtr->xmotion.display); + if (dispPtr->mouseButtonState & allButtonsMask) { + if (eventPtr->xbutton.window != dispPtr->mouseButtonWindow) { + /* + * This motion event should not be interpreted as a button + * press + motion event since this is not the same window the + * button was pressed down in. + */ + + dispPtr->mouseButtonState &= ~allButtonsMask; + dispPtr->mouseButtonWindow = None; + } else { + eventPtr->xmotion.state |= dispPtr->mouseButtonState; + } + } + break; + } +} + +/* + *---------------------------------------------------------------------- + * + * 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. */ +{ + register TkEventHandler *handlerPtr; + register 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 = 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 = 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) +{ + register TkEventHandler *handlerPtr; + register InProgress *ipPtr; + TkEventHandler *prevPtr; + register TkWindow *winPtr = (TkWindow *) token; + ThreadSpecificData *tsdPtr = + 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 = + Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); + + handlerPtr = 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 = + 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 = + 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 = 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 = + 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 = + 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. */ + XErrorEvent *errEventPtr) /* X error info. */ +{ + int *error = 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. */ +{ + register TkEventHandler *handlerPtr; + TkWindow *winPtr; + unsigned long mask; + InProgress ip; + Tcl_Interp *interp = NULL; + ThreadSpecificData *tsdPtr = + Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); + + UpdateButtonEventState(eventPtr); + + /* + * 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 ((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->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 != None) + && ((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. */ +{ + register TkEventHandler *handlerPtr; + register InProgress *ipPtr; + ThreadSpecificData *tsdPtr = + 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(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. */ +{ + register XEvent *eventPtr; + ThreadSpecificData *tsdPtr = + 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 = + 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 = 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 = 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; + } +} + +/* + *---------------------------------------------------------------------- + * + * 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 = + 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: { + TkKeyEvent *kePtr = (TkKeyEvent *) eventPtr; + + if (kePtr->charValuePtr != NULL) { + ckfree(kePtr->charValuePtr); + kePtr->charValuePtr = NULL; + kePtr->charValueLen = 0; + } + 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 = 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 = 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 = + Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData)); + + exitPtr = 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 = + 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( + ClientData clientData) /* 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( + ClientData clientData) /* Arbitrary value to pass to proc. */ +{ + ExitHandler *exitPtr; + ThreadSpecificData *tsdPtr = + 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: + */ |