/* * tkXEvent.c (CTk) -- * * This file provides basic low-level facilities for managing * X events. It builds on the facilities provided in tkEvent.c. * * Copyright (c) 1990-1994 The Regents of the University of California. * Copyright (c) 1994 Sun Microsystems, Inc. * Copyright (c) 1995 Cleveland Clinic Foundation * * See the file "license.terms" for information on usage and redistribution * of this file, and for a DISCLAIMER OF ALL WARRANTIES. * * @(#) $Id: ctk.shar,v 1.50 1996/01/15 14:47:16 andrewm Exp andrewm $ */ #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; static InProgress *pendingPtr = NULL; /* Topmost search in progress, or * NULL if none. */ /* * For each call to Tk_CreateGenericHandler, an instance of the following * structure will be created. All of the active handlers are linked into a * list. */ typedef struct GenericHandler { Tk_GenericProc *proc; /* Procedure to dispatch on all X events. */ ClientData clientData; /* Client data to pass to procedure. */ int deleteFlag; /* Flag to set when this handler is deleted. */ struct GenericHandler *nextPtr; /* Next handler in list of all generic * handlers, or NULL for end of list. */ } GenericHandler; static GenericHandler *genericList = NULL; /* First handler in the list, or NULL. */ static GenericHandler *lastGenericPtr = NULL; /* Last handler in list. */ /* * There's a potential problem if Tk_HandleEvent is entered recursively. * A handler cannot be deleted physically until we have returned from * calling it. Otherwise, we're looking at unallocated memory in advancing to * its `next' entry. We deal with the problem by using the `delete flag' and * deleting handlers only when it's known that there's no handler active. * * The following variable has a non-zero value when a handler is active. */ static int genericHandlersActive = 0; /* * Array of event masks corresponding to each X event: */ static unsigned long eventMasks[] = { CTK_MAP_EVENT_MASK, /* CTK_MAP_EVENT */ CTK_MAP_EVENT_MASK, /* CTK_UNMAP_EVENT */ CTK_EXPOSE_EVENT_MASK, /* CTK_EXPOSE_EVENT */ CTK_FOCUS_EVENT_MASK, /* CTK_FOCUS_EVENT */ CTK_FOCUS_EVENT_MASK, /* CTK_UNFOCUS_EVENT */ CTK_KEY_EVENT_MASK, /* CTK_KEY_EVENT */ CTK_DESTROY_EVENT_MASK, /* CTK_DESTROY_EVENT */ 0 /* CTK_UNSUPPORTED_EVENT */ }; /* *-------------------------------------------------------------- * * Tk_CreateEventHandler -- * * Arrange for a given procedure to be invoked whenever * events from a given class occur in a given window. * * Results: * None. * * Side effects: * From now on, whenever an event of the type given by * mask occurs for token and is processed by Tk_HandleEvent, * proc will be called. See the manual entry for details * of the calling sequence and return value for proc. * *-------------------------------------------------------------- */ void Tk_CreateEventHandler(token, mask, proc, clientData) Tk_Window token; /* Token for window in which to * create handler. */ unsigned long mask; /* Events for which proc should * be called. */ Tk_EventProc *proc; /* Procedure to call for each * selected event */ ClientData clientData; /* Arbitrary data to pass to proc. */ { register TkEventHandler *handlerPtr; register TkWindow *winPtr = (TkWindow *) token; int found; /* * Skim through the list of existing handlers to (a) compute the * overall event mask for the window (so we can pass this new * value to the X system) and (b) see if there's already a handler * declared with the same callback and clientData (if so, just * change the mask). If no existing handler matches, then create * a new handler. */ found = 0; if (winPtr->handlerList == NULL) { handlerPtr = (TkEventHandler *) ckalloc( (unsigned) sizeof(TkEventHandler)); winPtr->handlerList = handlerPtr; goto initHandler; } else { for (handlerPtr = winPtr->handlerList; ; handlerPtr = handlerPtr->nextPtr) { if ((handlerPtr->proc == proc) && (handlerPtr->clientData == clientData)) { handlerPtr->mask = mask; found = 1; } if (handlerPtr->nextPtr == NULL) { break; } } } /* * Create a new handler if no matching old handler was found. */ if (!found) { handlerPtr->nextPtr = (TkEventHandler *) ckalloc(sizeof(TkEventHandler)); handlerPtr = handlerPtr->nextPtr; initHandler: handlerPtr->mask = mask; handlerPtr->proc = proc; handlerPtr->clientData = clientData; handlerPtr->nextPtr = NULL; } /* * No need to call XSelectInput: Tk always selects on all events * for all windows (needed to support bindings on classes and "all"). */ } /* *-------------------------------------------------------------- * * Tk_DeleteEventHandler -- * * Delete a previously-created handler. * * Results: * None. * * Side effects: * If there existed a handler as described by the * parameters, the handler is deleted so that proc * will not be invoked again. * *-------------------------------------------------------------- */ void Tk_DeleteEventHandler(token, mask, proc, clientData) Tk_Window token; /* Same as corresponding arguments passed */ unsigned long mask; /* previously to Tk_CreateEventHandler. */ Tk_EventProc *proc; ClientData clientData; { register TkEventHandler *handlerPtr; register InProgress *ipPtr; TkEventHandler *prevPtr; register TkWindow *winPtr = (TkWindow *) token; /* * Find the event handler to be deleted, or return * immediately if it doesn't exist. */ for (handlerPtr = winPtr->handlerList, prevPtr = NULL; ; prevPtr = handlerPtr, handlerPtr = handlerPtr->nextPtr) { if (handlerPtr == NULL) { return; } if ((handlerPtr->mask == mask) && (handlerPtr->proc == proc) && (handlerPtr->clientData == clientData)) { break; } } /* * If Tk_HandleEvent is about to process this handler, tell it to * process the next one instead. */ for (ipPtr = pendingPtr; ipPtr != NULL; ipPtr = ipPtr->nextPtr) { if (ipPtr->nextHandler == handlerPtr) { ipPtr->nextHandler = handlerPtr->nextPtr; } } /* * Free resources associated with the handler. */ if (prevPtr == NULL) { winPtr->handlerList = handlerPtr->nextPtr; } else { prevPtr->nextPtr = handlerPtr->nextPtr; } ckfree((char *) handlerPtr); /* * No need to call XSelectInput: Tk always selects on all events * for all windows (needed to support bindings on classes and "all"). */ } /*-------------------------------------------------------------- * * Tk_CreateGenericHandler -- * * Register a procedure to be called on each X event, regardless * of display or window. Generic handlers are useful for capturing * events that aren't associated with windows, or events for windows * not managed by Tk. * * Results: * None. * * Side Effects: * From now on, whenever an X event is given to Tk_HandleEvent, * invoke proc, giving it clientData and the event as arguments. * *-------------------------------------------------------------- */ void Tk_CreateGenericHandler(proc, clientData) Tk_GenericProc *proc; /* Procedure to call on every event. */ ClientData clientData; /* One-word value to pass to proc. */ { GenericHandler *handlerPtr; handlerPtr = (GenericHandler *) ckalloc (sizeof (GenericHandler)); handlerPtr->proc = proc; handlerPtr->clientData = clientData; handlerPtr->deleteFlag = 0; handlerPtr->nextPtr = NULL; if (genericList == NULL) { genericList = handlerPtr; } else { lastGenericPtr->nextPtr = handlerPtr; } lastGenericPtr = handlerPtr; } /* *-------------------------------------------------------------- * * Tk_DeleteGenericHandler -- * * Delete a previously-created generic handler. * * Results: * None. * * Side Effects: * If there existed a handler as described by the parameters, * that handler is logically deleted so that proc will not be * invoked again. The physical deletion happens in the event * loop in Tk_HandleEvent. * *-------------------------------------------------------------- */ void Tk_DeleteGenericHandler(proc, clientData) Tk_GenericProc *proc; ClientData clientData; { GenericHandler * handler; for (handler = genericList; handler; handler = handler->nextPtr) { if ((handler->proc == proc) && (handler->clientData == clientData)) { handler->deleteFlag = 1; } } } /* *-------------------------------------------------------------- * * Tk_HandleEvent -- * * Given an event, invoke all the handlers that have * been registered for the event. * * Results: * None. * * Side effects: * Depends on the handlers. * *-------------------------------------------------------------- */ void Tk_HandleEvent(eventPtr) XEvent *eventPtr; /* Event to dispatch. */ { register TkEventHandler *handlerPtr; register GenericHandler *genericPtr; register GenericHandler *genPrevPtr; TkWindow *winPtr = eventPtr->window; unsigned long mask = eventMasks[eventPtr->type]; InProgress ip; static int lastSerial = 0; /* * Assign the event serial number. */ lastSerial++; eventPtr->serial = lastSerial; /* * Invoke all the generic event handlers (those that are * invoked for all events). If a generic event handler reports that * an event is fully processed, go no further. */ for (genPrevPtr = NULL, genericPtr = genericList; genericPtr != NULL; ) { if (genericPtr->deleteFlag) { if (!genericHandlersActive) { GenericHandler *tmpPtr; /* * This handler needs to be deleted and there are no * calls pending through the handler, so now is a safe * time to delete it. */ tmpPtr = genericPtr->nextPtr; if (genPrevPtr == NULL) { genericList = tmpPtr; } else { genPrevPtr->nextPtr = tmpPtr; } if (tmpPtr == NULL) { lastGenericPtr = genPrevPtr; } (void) ckfree((char *) genericPtr); genericPtr = tmpPtr; continue; } } else { int done; genericHandlersActive++; done = (*genericPtr->proc)(genericPtr->clientData, eventPtr); genericHandlersActive--; if (done) { return; } } genPrevPtr = genericPtr; genericPtr = genPrevPtr->nextPtr; } /* * There's a potential interaction here with Tk_DeleteEventHandler. * Read the documentation for pendingPtr. */ ip.eventPtr = eventPtr; ip.winPtr = winPtr; ip.nextHandler = NULL; ip.nextPtr = pendingPtr; pendingPtr = &ip; 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. */ if (ip.winPtr) { TkBindEventProc(winPtr, eventPtr); } pendingPtr = ip.nextPtr; } /* *-------------------------------------------------------------- * * Tk_DoOneEvent -- * * Flushes displays and the calls Tcl_DoOneEvent. * * Results: * See Tcl_DoOneEvent(). * * Side effects: * Displays get flushed. * *-------------------------------------------------------------- */ int Tk_DoOneEvent(int flags) { int retval; Ctk_DisplayFlush(NULL); retval = Tcl_DoOneEvent(flags); Ctk_DisplayFlush(NULL); return(retval); } /* *-------------------------------------------------------------- * * Tk_MainLoop -- * * Call Tk_DoOneEvent over and over again in an infinite * loop as long as there exist any main windows. * * Results: * None. * * Side effects: * Arbitrary; depends on handlers for events. * *-------------------------------------------------------------- */ void Tk_MainLoop() { while (tk_NumMainWindows > 0) { Tk_DoOneEvent(0); } } /* *-------------------------------------------------------------- * * TkEventDeadWindow -- * * This procedure is invoked when it is determined that * a window is dead. It cleans up event-related information * about the window. * * Results: * None. * * Side effects: * Various things get cleaned up and recycled. * *-------------------------------------------------------------- */ void TkEventDeadWindow(winPtr) TkWindow *winPtr; /* Information about the window * that is being deleted. */ { register TkEventHandler *handlerPtr; register InProgress *ipPtr; /* * While deleting all the handlers, be careful to check for * Tk_HandleEvent being about to process one of the deleted * handlers. If it is, tell it to quit (all of the handlers * are being deleted). */ while (winPtr->handlerList != NULL) { handlerPtr = winPtr->handlerList; winPtr->handlerList = handlerPtr->nextPtr; for (ipPtr = pendingPtr; ipPtr != NULL; ipPtr = ipPtr->nextPtr) { if (ipPtr->nextHandler == handlerPtr) { ipPtr->nextHandler = NULL; } if (ipPtr->winPtr == winPtr) { ipPtr->winPtr = NULL; } } ckfree((char *) handlerPtr); } }