/*
* 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.17.2.1 2003/07/19 01:03:25 hobbs Exp $
*/
#include "tkPort.h"
#include "tkInt.h"
#include <signal.h>
/* This does not belong here it belong in the platform window manager code! */
extern Window XmuClientWindow _ANSI_ARGS_((Display *dpy, Window win));
/*
* 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);
dispPtr->mouseButtonWindow = eventPtr->xbutton.window;
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);
dispPtr->mouseButtonWindow = 0;
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);
if (dispPtr->mouseButtonState & (Button1Mask|Button2Mask|Button3Mask)) {
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 &=
~(Button1Mask|Button2Mask|Button3Mask);
dispPtr->mouseButtonWindow = 0;
} else {
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);
/* Some Drag&Drop messages get sent to wrappers for which there is
* an X ICCM scheme to locate the "real" window - it is not clear
* if this is "done right" yet but should be harmless.
*/
if (winPtr == NULL && eventPtr->type == ClientMessage) {
handlerWindow = XmuClientWindow(eventPtr->xany.display, handlerWindow);
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 {
if (winPtr->inputContext != NULL)
panic("inputContext not NULL");
winPtr->inputContext = XCreateIC(dispPtr->inputMethod,
XNInputStyle, XIMPreeditNothing|XIMStatusNothing,
XNClientWindow, winPtr->window,
XNFocusWindow, winPtr->window,
NULL);
}
#else
if (winPtr->inputContext != NULL)
panic("inputContext not NULL");
winPtr->inputContext = XCreateIC(dispPtr->inputMethod,
XNInputStyle, XIMPreeditNothing|XIMStatusNothing,
XNClientWindow, winPtr->window,
XNFocusWindow, winPtr->window,
NULL);
#endif
}
}
if (eventPtr->type == KeyPress || eventPtr->type == KeyRelease) {
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;
/* WARNING: this stores stack address - longjmp() and
similar "exception" mechanisms need to take steps
to avoid bypassing the cleanup in this code
*/
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 {
/* Perl/Tk had/has its own hook for this */
if (eventPtr && winPtr && winPtr->mainPtr) {
LangClientMessage(winPtr->mainPtr->interp, (Tk_Window) winPtr, eventPtr);
}
/*
* 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, fallbackCurrent)
TkDisplay *dispPtr; /* Display for which the time is desired. */
{
register XEvent *eventPtr;
ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
InProgress *pending;
pending = tsdPtr->pendingPtr;
while (pending != NULL) {
eventPtr = pending->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;
}
pending = pending->nextPtr;
}
return (fallbackCurrent) ? CurrentTime : 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 (eventPtr->xany.window == PointerWindow && eventPtr->type == ClientMessage)
{
/* XDE Drag&Drop does SendEvent to 0 which is "PointerWindow".
X does not fill in real Window so do so here
The pointer may have moved - but what else can we do ?
*/
Window root = DefaultRootWindow(eventPtr->xany.display);
Window child = None;
int rx, ry, wx, wy, mask;
if (!XQueryPointer(eventPtr->xany.display, root, &root, &child, &rx, &ry, &wx, &wy, &mask) || child == None)
child = root;
while (child != None)
{
eventPtr->xany.window = child;
XTranslateCoordinates(eventPtr->xany.display, root, child, rx, ry, &wx, &wy, &child);
}
}
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_QueueProcEvent(WindowEventProc, &dispPtr->delayedMotionPtr->header, position);
dispPtr->delayedMotionPtr = NULL;
Tcl_CancelIdleCall(DelayedMotionProc, (ClientData) dispPtr);
}
}
wevPtr = (TkWindowEvent *) ckalloc(sizeof(TkWindowEvent));
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_QueueProcEvent(WindowEventProc, &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) {
/* TK_DEFER_EVENT */
/* WARNING - Beware this happening to SelectionNotify events
* DragDrop is broken if they don't happen...
*/
return 0;
} else {
/*
* TK_DISCARD_EVENT: return and say we processed the event,
* even though we didn't do anything at all.
*/
if (result != TK_DISCARD_EVENT) {
LangDebug("Bad restrict proc code %d\n", result);
return 0; /* DEFER instead */
}
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_QueueProcEvent(WindowEventProc, &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);
}
}