/*
* 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 <signal.h>
/*
* 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);
}
}