/*1* tkGrab.c --2*3* This file provides procedures that implement grabs for Tk.4*5* Copyright (c) 1992-1994 The Regents of the University of California.6* Copyright (c) 1994-1995 Sun Microsystems, Inc.7*8* See the file "license.terms" for information on usage and redistribution9* of this file, and for a DISCLAIMER OF ALL WARRANTIES.10*11* SCCS: @(#) tkGrab.c 1.51 96/09/05 12:29:4312*/1314#include "tkInt.h"1516/*17* The grab state machine has four states: ungrabbed, button pressed,18* grabbed, and button pressed while grabbed. In addition, there are19* three pieces of grab state information: the current grab window,20* the current restrict window, and whether the mouse is captured.21*22* The current grab window specifies the point in the Tk window23* heirarchy above which pointer events will not be reported. Any24* window within the subtree below the grab window will continue to25* receive events as normal. Events outside of the grab tree will be26* reported to the grab window.27*28* If the current restrict window is set, then all pointer events will29* be reported only to the restrict window. The restrict window is30* normally set during an automatic button grab.31*32* The mouse capture state specifies whether the window system will33* report mouse events outside of any Tk toplevels. This is set34* during a global grab or an automatic button grab.35*36* The transitions between different states is given in the following37* table:38*39* Event\State U B G GB40* ----------- -- -- -- --41* FirstPress B B GB GB42* Press B B G GB43* Release U B G GB44* LastRelease U U G G45* Grab G G G G46* Ungrab U B U U47*48* Note: U=Ungrabbed, B=Button, G=Grabbed, GB=Grab and Button49*50* In addition, the following conditions are always true:51*52* State\Variable Grab Restrict Capture53* -------------- ---- -------- -------54* Ungrabbed 0 0 055* Button 0 1 156* Grabbed 1 0 b/g57* Grab and Button 1 1 158*59* Note: 0 means variable is set to NULL, 1 means variable is set to60* some window, b/g means the variable is set to a window if a button61* is currently down or a global grab is in effect.62*63* The final complication to all of this is enter and leave events.64* In order to correctly handle all of the various cases, Tk cannot65* rely on X enter/leave events in all situations. The following66* describes the correct sequence of enter and leave events that67* should be observed by Tk scripts:68*69* Event(state) Enter/Leave From -> To70* ------------ ----------------------71* LastRelease(B | GB): restrict window -> anc(grab window, event window)72* Grab(U | B): event window -> anc(grab window, event window)73* Grab(G): anc(old grab window, event window) ->74* anc(new grab window, event window)75* Grab(GB): restrict window -> anc(new grab window, event window)76* Ungrab(G): anc(grab window, event window) -> event window77* Ungrab(GB): restrict window -> event window78*79* Note: anc(x,y) returns the least ancestor of y that is in the tree80* of x, terminating at toplevels.81*/8283/*84* The following structure is used to pass information to85* GrabRestrictProc from EatGrabEvents.86*/8788typedef struct {89Display *display; /* Display from which to discard events. */90unsigned int serial; /* Serial number with which to compare. */91} GrabInfo;9293/*94* Bit definitions for grabFlags field of TkDisplay structures:95*96* GRAB_GLOBAL 1 means this is a global grab (we grabbed via97* the server so all applications are locked out).98* 0 means this is a local grab that affects99* only this application.100* GRAB_TEMP_GLOBAL 1 means we've temporarily grabbed via the101* server because a button is down and we want102* to make sure that we get the button-up103* event. The grab will be released when the104* last mouse button goes up.105*/106107#define GRAB_GLOBAL 1108#define GRAB_TEMP_GLOBAL 4109110/*111* The following structure is a Tcl_Event that triggers a change in112* the grabWinPtr field of a display. This event guarantees that113* the change occurs in the proper order relative to enter and leave114* events.115*/116117typedef struct NewGrabWinEvent {118Tcl_Event header; /* Standard information for all Tcl events. */119TkDisplay *dispPtr; /* Display whose grab window is to change. */120Window grabWindow; /* New grab window for display. This is121* recorded instead of a (TkWindow *) because122* it will allow us to detect cases where123* the window is destroyed before this event124* is processed. */125} NewGrabWinEvent;126127/*128* The following magic value is stored in the "send_event" field of129* EnterNotify and LeaveNotify events that are generated in this130* file. This allows us to separate "real" events coming from the131* server from those that we generated.132*/133134#define GENERATED_EVENT_MAGIC ((Bool) 0x147321ac)135136/*137* Mask that selects any of the state bits corresponding to buttons,138* plus masks that select individual buttons' bits:139*/140141#define ALL_BUTTONS \142(Button1Mask|Button2Mask|Button3Mask|Button4Mask|Button5Mask)143static unsigned int buttonStates[] = {144Button1Mask, Button2Mask, Button3Mask, Button4Mask, Button5Mask145};146147/*148* Forward declarations for procedures declared later in this file:149*/150151static void EatGrabEvents _ANSI_ARGS_((TkDisplay *dispPtr,152unsigned int serial));153static TkWindow * FindCommonAncestor _ANSI_ARGS_((TkWindow *winPtr1,154TkWindow *winPtr2, int *countPtr1,155int *countPtr2));156static Tk_RestrictAction GrabRestrictProc _ANSI_ARGS_((ClientData arg,157XEvent *eventPtr));158static int GrabWinEventProc _ANSI_ARGS_((Tcl_Event *evPtr,159int flags));160static void MovePointer2 _ANSI_ARGS_((TkWindow *sourcePtr,161TkWindow *destPtr, int mode, int leaveEvents,162int EnterEvents));163static void QueueGrabWindowChange _ANSI_ARGS_((TkDisplay *dispPtr,164TkWindow *grabWinPtr));165static void ReleaseButtonGrab _ANSI_ARGS_((TkDisplay *dispPtr));166167/*168*----------------------------------------------------------------------169*170* Tk_GrabCmd --171*172* This procedure is invoked to process the "grab" Tcl command.173* See the user documentation for details on what it does.174*175* Results:176* A standard Tcl result.177*178* Side effects:179* See the user documentation.180*181*----------------------------------------------------------------------182*/183184/* ARGSUSED */185int186Tk_GrabCmd(clientData, interp, argc, argv)187ClientData clientData; /* Main window associated with188* interpreter. */189Tcl_Interp *interp; /* Current interpreter. */190int argc; /* Number of arguments. */191char **argv; /* Argument strings. */192{193int globalGrab, c;194Tk_Window tkwin;195TkDisplay *dispPtr;196size_t length;197198if (argc < 2) {199badArgs:200Tcl_AppendResult(interp, "wrong # args: should be \"",201argv[0], " ?-global? window\" or \"", argv[0],202" option ?arg arg ...?\"", (char *) NULL);203return TCL_ERROR;204}205c = argv[1][0];206length = strlen(argv[1]);207if (c == '.') {208if (argc != 2) {209goto badArgs;210}211tkwin = Tk_NameToWindow(interp, argv[1], (Tk_Window) clientData);212if (tkwin == NULL) {213return TCL_ERROR;214}215return Tk_Grab(interp, tkwin, 0);216} else if ((c == '-') && (strncmp(argv[1], "-global", length) == 0)217&& (length >= 2)) {218if (argc != 3) {219goto badArgs;220}221tkwin = Tk_NameToWindow(interp, argv[2], (Tk_Window) clientData);222if (tkwin == NULL) {223return TCL_ERROR;224}225return Tk_Grab(interp, tkwin, 1);226} else if ((c == 'c') && (strncmp(argv[1], "current", length) == 0)) {227if (argc > 3) {228Tcl_AppendResult(interp, "wrong # args: should be \"",229argv[0], " current ?window?\"", (char *) NULL);230return TCL_ERROR;231}232if (argc == 3) {233tkwin = Tk_NameToWindow(interp, argv[2], (Tk_Window) clientData);234if (tkwin == NULL) {235return TCL_ERROR;236}237dispPtr = ((TkWindow *) tkwin)->dispPtr;238if (dispPtr->eventualGrabWinPtr != NULL) {239interp->result = dispPtr->eventualGrabWinPtr->pathName;240}241} else {242for (dispPtr = tkDisplayList; dispPtr != NULL;243dispPtr = dispPtr->nextPtr) {244if (dispPtr->eventualGrabWinPtr != NULL) {245Tcl_AppendElement(interp,246dispPtr->eventualGrabWinPtr->pathName);247}248}249}250return TCL_OK;251} else if ((c == 'r') && (strncmp(argv[1], "release", length) == 0)) {252if (argc != 3) {253Tcl_AppendResult(interp, "wrong # args: should be \"",254argv[0], " release window\"", (char *) NULL);255return TCL_ERROR;256}257tkwin = Tk_NameToWindow(interp, argv[2], (Tk_Window) clientData);258if (tkwin == NULL) {259Tcl_ResetResult(interp);260} else {261Tk_Ungrab(tkwin);262}263} else if ((c == 's') && (strncmp(argv[1], "set", length) == 0)264&& (length >= 2)) {265if ((argc != 3) && (argc != 4)) {266Tcl_AppendResult(interp, "wrong # args: should be \"",267argv[0], " set ?-global? window\"", (char *) NULL);268return TCL_ERROR;269}270if (argc == 3) {271globalGrab = 0;272tkwin = Tk_NameToWindow(interp, argv[2], (Tk_Window) clientData);273} else {274globalGrab = 1;275length = strlen(argv[2]);276if ((strncmp(argv[2], "-global", length) != 0) || (length < 2)) {277Tcl_AppendResult(interp, "bad argument \"", argv[2],278"\": must be \"", argv[0], " set ?-global? window\"",279(char *) NULL);280return TCL_ERROR;281}282tkwin = Tk_NameToWindow(interp, argv[3], (Tk_Window) clientData);283}284if (tkwin == NULL) {285return TCL_ERROR;286}287return Tk_Grab(interp, tkwin, globalGrab);288} else if ((c == 's') && (strncmp(argv[1], "status", length) == 0)289&& (length >= 2)) {290TkWindow *winPtr;291292if (argc != 3) {293Tcl_AppendResult(interp, "wrong # args: should be \"",294argv[0], " status window\"", (char *) NULL);295return TCL_ERROR;296}297winPtr = (TkWindow *) Tk_NameToWindow(interp, argv[2],298(Tk_Window) clientData);299if (winPtr == NULL) {300return TCL_ERROR;301}302dispPtr = winPtr->dispPtr;303if (dispPtr->eventualGrabWinPtr != winPtr) {304interp->result = "none";305} else if (dispPtr->grabFlags & GRAB_GLOBAL) {306interp->result = "global";307} else {308interp->result = "local";309}310} else {311Tcl_AppendResult(interp, "unknown or ambiguous option \"", argv[1],312"\": must be current, release, set, or status",313(char *) NULL);314return TCL_ERROR;315}316return TCL_OK;317}318319/*320*----------------------------------------------------------------------321*322* Tk_Grab --323*324* Grabs the pointer and keyboard, so that mouse-related events are325* only reported relative to a given window and its descendants.326*327* Results:328* A standard Tcl result is returned. TCL_OK is the normal return329* value; if the grab could not be set then TCL_ERROR is returned330* and interp->result will hold an error message.331*332* Side effects:333* Once this call completes successfully, no window outside the334* tree rooted at tkwin will receive pointer- or keyboard-related335* events until the next call to Tk_Ungrab. If a previous grab was336* in effect within this application, then it is replaced with a new337* one.338*339*----------------------------------------------------------------------340*/341342int343Tk_Grab(interp, tkwin, grabGlobal)344Tcl_Interp *interp; /* Used for error reporting. */345Tk_Window tkwin; /* Window on whose behalf the pointer346* is to be grabbed. */347int grabGlobal; /* Non-zero means issue a grab to the348* server so that no other application349* gets mouse or keyboard events.350* Zero means the grab only applies351* within this application. */352{353int grabResult, numTries;354TkWindow *winPtr = (TkWindow *) tkwin;355TkDisplay *dispPtr = winPtr->dispPtr;356TkWindow *winPtr2;357unsigned int serial;358359ReleaseButtonGrab(dispPtr);360if (dispPtr->eventualGrabWinPtr != NULL) {361if ((dispPtr->eventualGrabWinPtr == winPtr)362&& (grabGlobal == ((dispPtr->grabFlags & GRAB_GLOBAL) != 0))) {363return TCL_OK;364}365if (dispPtr->eventualGrabWinPtr->mainPtr != winPtr->mainPtr) {366alreadyGrabbed:367interp->result = "grab failed: another application has grab";368return TCL_ERROR;369}370Tk_Ungrab((Tk_Window) dispPtr->eventualGrabWinPtr);371}372373Tk_MakeWindowExist(tkwin);374if (!grabGlobal) {375Window dummy1, dummy2;376int dummy3, dummy4, dummy5, dummy6;377unsigned int state;378379/*380* Local grab. However, if any mouse buttons are down, turn381* it into a global grab temporarily, until the last button382* goes up. This does two things: (a) it makes sure that we383* see the button-up event; and (b) it allows us to track mouse384* motion among all of the windows of this application.385*/386387dispPtr->grabFlags &= ~(GRAB_GLOBAL|GRAB_TEMP_GLOBAL);388XQueryPointer(dispPtr->display, winPtr->window, &dummy1,389&dummy2, &dummy3, &dummy4, &dummy5, &dummy6, &state);390if ((state & ALL_BUTTONS) != 0) {391dispPtr->grabFlags |= GRAB_TEMP_GLOBAL;392goto setGlobalGrab;393}394} else {395dispPtr->grabFlags |= GRAB_GLOBAL;396setGlobalGrab:397398/*399* Tricky point: must ungrab before grabbing. This is needed400* in case there is a button auto-grab already in effect. If401* there is, and the mouse has moved to a different window, X402* won't generate enter and leave events to move the mouse if403* we grab without ungrabbing.404*/405406XUngrabPointer(dispPtr->display, CurrentTime);407serial = NextRequest(dispPtr->display);408409/*410* Another tricky point: there are races with some window411* managers that can cause grabs to fail because the window412* manager hasn't released its grab quickly enough. To work413* around this problem, retry a few times after AlreadyGrabbed414* errors to give the grab release enough time to register with415* the server.416*/417418grabResult = 0; /* Needed only to prevent gcc419* compiler warnings. */420for (numTries = 0; numTries < 10; numTries++) {421grabResult = XGrabPointer(dispPtr->display, winPtr->window,422True, ButtonPressMask|ButtonReleaseMask|ButtonMotionMask423|PointerMotionMask, GrabModeAsync, GrabModeAsync, None,424None, CurrentTime);425if (grabResult != AlreadyGrabbed) {426break;427}428Tcl_Sleep(100);429}430if (grabResult != 0) {431grabError:432if (grabResult == GrabNotViewable) {433interp->result = "grab failed: window not viewable";434} else if (grabResult == AlreadyGrabbed) {435goto alreadyGrabbed;436} else if (grabResult == GrabFrozen) {437interp->result = "grab failed: keyboard or pointer frozen";438} else if (grabResult == GrabInvalidTime) {439interp->result = "grab failed: invalid time";440} else {441char msg[100];442443sprintf(msg, "grab failed for unknown reason (code %d)",444grabResult);445Tcl_AppendResult(interp, msg, (char *) NULL);446}447return TCL_ERROR;448}449grabResult = XGrabKeyboard(dispPtr->display, Tk_WindowId(tkwin),450False, GrabModeAsync, GrabModeAsync, CurrentTime);451if (grabResult != 0) {452XUngrabPointer(dispPtr->display, CurrentTime);453goto grabError;454}455456/*457* Eat up any grab-related events generated by the server for the458* grab. There are several reasons for doing this:459*460* 1. We have to synthesize the events for local grabs anyway, since461* the server doesn't participate in them.462* 2. The server doesn't always generate the right events for global463* grabs (e.g. it generates events even if the current window is464* in the grab tree, which we don't want).465* 3. We want all the grab-related events to be processed immediately466* (before other events that are already queued); events coming467* from the server will be in the wrong place, but events we468* synthesize here will go to the front of the queue.469*/470471EatGrabEvents(dispPtr, serial);472}473474/*475* Synthesize leave events to move the pointer from its current window476* up to the lowest ancestor that it has in common with the grab window.477* However, only do this if the pointer is outside the grab window's478* subtree but inside the grab window's application.479*/480481if ((dispPtr->serverWinPtr != NULL)482&& (dispPtr->serverWinPtr->mainPtr == winPtr->mainPtr)) {483for (winPtr2 = dispPtr->serverWinPtr; ; winPtr2 = winPtr2->parentPtr) {484if (winPtr2 == winPtr) {485break;486}487if (winPtr2 == NULL) {488MovePointer2(dispPtr->serverWinPtr, winPtr, NotifyGrab, 1, 0);489break;490}491}492}493QueueGrabWindowChange(dispPtr, winPtr);494return TCL_OK;495}496497/*498*----------------------------------------------------------------------499*500* Tk_Ungrab --501*502* Releases a grab on the mouse pointer and keyboard, if there503* is one set on the specified window.504*505* Results:506* None.507*508* Side effects:509* Pointer and keyboard events will start being delivered to other510* windows again.511*512*----------------------------------------------------------------------513*/514515void516Tk_Ungrab(tkwin)517Tk_Window tkwin; /* Window whose grab should be518* released. */519{520TkDisplay *dispPtr;521TkWindow *grabWinPtr, *winPtr;522unsigned int serial;523524grabWinPtr = (TkWindow *) tkwin;525dispPtr = grabWinPtr->dispPtr;526if (grabWinPtr != dispPtr->eventualGrabWinPtr) {527return;528}529ReleaseButtonGrab(dispPtr);530QueueGrabWindowChange(dispPtr, (TkWindow *) NULL);531if (dispPtr->grabFlags & (GRAB_GLOBAL|GRAB_TEMP_GLOBAL)) {532dispPtr->grabFlags &= ~(GRAB_GLOBAL|GRAB_TEMP_GLOBAL);533serial = NextRequest(dispPtr->display);534XUngrabPointer(dispPtr->display, CurrentTime);535XUngrabKeyboard(dispPtr->display, CurrentTime);536EatGrabEvents(dispPtr, serial);537}538539/*540* Generate events to move the pointer back to the window where it541* really is. Some notes:542* 1. As with grabs, only do this if the "real" window is not a543* descendant of the grab window, since in this case the pointer544* is already where it's supposed to be.545* 2. If the "real" window is in some other application then don't546* generate any events at all, since everything's already been547* reported correctly.548* 3. Only generate enter events. Don't generate leave events,549* because we never told the lower-level windows that they550* had the pointer in the first place.551*/552553for (winPtr = dispPtr->serverWinPtr; ; winPtr = winPtr->parentPtr) {554if (winPtr == grabWinPtr) {555break;556}557if (winPtr == NULL) {558if ((dispPtr->serverWinPtr == NULL) ||559(dispPtr->serverWinPtr->mainPtr == grabWinPtr->mainPtr)) {560MovePointer2(grabWinPtr, dispPtr->serverWinPtr,561NotifyUngrab, 0, 1);562}563break;564}565}566}567568/*569*----------------------------------------------------------------------570*571* ReleaseButtonGrab --572*573* This procedure is called to release a simulated button grab, if574* there is one in effect. A button grab is present whenever575* dispPtr->buttonWinPtr is non-NULL or when the GRAB_TEMP_GLOBAL576* flag is set.577*578* Results:579* None.580*581* Side effects:582* DispPtr->buttonWinPtr is reset to NULL, and enter and leave583* events are generated if necessary to move the pointer from584* the button grab window to its current window.585*586*----------------------------------------------------------------------587*/588589static void590ReleaseButtonGrab(dispPtr)591register TkDisplay *dispPtr; /* Display whose button grab is to be592* released. */593{594unsigned int serial;595596if (dispPtr->buttonWinPtr != NULL) {597if (dispPtr->buttonWinPtr != dispPtr->serverWinPtr) {598MovePointer2(dispPtr->buttonWinPtr, dispPtr->serverWinPtr,599NotifyUngrab, 1, 1);600}601dispPtr->buttonWinPtr = NULL;602}603if (dispPtr->grabFlags & GRAB_TEMP_GLOBAL) {604dispPtr->grabFlags &= ~GRAB_TEMP_GLOBAL;605serial = NextRequest(dispPtr->display);606XUngrabPointer(dispPtr->display, CurrentTime);607XUngrabKeyboard(dispPtr->display, CurrentTime);608EatGrabEvents(dispPtr, serial);609}610}611612/*613*----------------------------------------------------------------------614*615* TkPointerEvent --616*617* This procedure is called for each pointer-related event, before618* the event has been processed. It does various things to make619* grabs work correctly.620*621* Results:622* If the return value is 1 it means the event should be processed623* (event handlers should be invoked). If the return value is 0624* it means the event should be ignored in order to make grabs625* work correctly. In some cases this procedure modifies the event.626*627* Side effects:628* Grab state information may be updated. New events may also be629* pushed back onto the event queue to replace or augment the630* one passed in here.631*632*----------------------------------------------------------------------633*/634635int636TkPointerEvent(eventPtr, winPtr)637register XEvent *eventPtr; /* Pointer to the event. */638TkWindow *winPtr; /* Tk's information for window639* where event was reported. */640{641register TkWindow *winPtr2;642TkDisplay *dispPtr = winPtr->dispPtr;643unsigned int serial;644int outsideGrabTree = 0;645int ancestorOfGrab = 0;646int appGrabbed = 0; /* Non-zero means event is being647* reported to an application that is648* affected by the grab. */649650/*651* Collect information about the grab (if any).652*/653654switch (TkGrabState(winPtr)) {655case TK_GRAB_IN_TREE:656appGrabbed = 1;657break;658case TK_GRAB_ANCESTOR:659appGrabbed = 1;660outsideGrabTree = 1;661ancestorOfGrab = 1;662break;663case TK_GRAB_EXCLUDED:664appGrabbed = 1;665outsideGrabTree = 1;666break;667}668669if ((eventPtr->type == EnterNotify) || (eventPtr->type == LeaveNotify)) {670/*671* Keep track of what window the mouse is *really* over.672* Any events that we generate have a special send_event value,673* which is detected below and used to ignore the event for674* purposes of setting serverWinPtr.675*/676677if (eventPtr->xcrossing.send_event != GENERATED_EVENT_MAGIC) {678if ((eventPtr->type == LeaveNotify) &&679(winPtr->flags & TK_TOP_LEVEL)) {680dispPtr->serverWinPtr = NULL;681} else {682dispPtr->serverWinPtr = winPtr;683}684}685686/*687* When a grab is active, X continues to report enter and leave688* events for windows outside the tree of the grab window:689* 1. Detect these events and ignore them except for690* windows above the grab window.691* 2. Allow Enter and Leave events to pass through the692* windows above the grab window, but never let them693* end up with the pointer *in* one of those windows.694*/695696if (dispPtr->grabWinPtr != NULL) {697if (outsideGrabTree && appGrabbed) {698if (!ancestorOfGrab) {699return 0;700}701switch (eventPtr->xcrossing.detail) {702case NotifyInferior:703return 0;704case NotifyAncestor:705eventPtr->xcrossing.detail = NotifyVirtual;706break;707case NotifyNonlinear:708eventPtr->xcrossing.detail = NotifyNonlinearVirtual;709break;710}711}712713/*714* Make buttons have the same grab-like behavior inside a grab715* as they do outside a grab: do this by ignoring enter and716* leave events except for the window in which the button was717* pressed.718*/719720if ((dispPtr->buttonWinPtr != NULL)721&& (winPtr != dispPtr->buttonWinPtr)) {722return 0;723}724}725return 1;726}727728if (!appGrabbed) {729return 1;730}731732if (eventPtr->type == MotionNotify) {733/*734* When grabs are active, X reports motion events relative to the735* window under the pointer. Instead, it should report the events736* relative to the window the button went down in, if there is a737* button down. Otherwise, if the pointer window is outside the738* subtree of the grab window, the events should be reported739* relative to the grab window. Otherwise, the event should be740* reported to the pointer window.741*/742743winPtr2 = winPtr;744if (dispPtr->buttonWinPtr != NULL) {745winPtr2 = dispPtr->buttonWinPtr;746} else if (outsideGrabTree || (dispPtr->serverWinPtr == NULL)) {747winPtr2 = dispPtr->grabWinPtr;748}749if (winPtr2 != winPtr) {750TkChangeEventWindow(eventPtr, winPtr2);751Tk_QueueWindowEvent(eventPtr, TCL_QUEUE_HEAD);752return 0;753}754return 1;755}756757/*758* Process ButtonPress and ButtonRelease events:759* 1. Keep track of whether a button is down and what window it760* went down in.761* 2. If the first button goes down outside the grab tree, pretend762* it went down in the grab window. Note: it's important to763* redirect events to the grab window like this in order to make764* things like menus work, where button presses outside the765* grabbed menu need to be seen. An application can always766* ignore the events if they occur outside its window.767* 3. If a button press or release occurs outside the window where768* the first button was pressed, retarget the event so it's reported769* to the window where the first button was pressed.770* 4. If the last button is released in a window different than where771* the first button was pressed, generate Enter/Leave events to772* move the mouse from the button window to its current window.773* 5. If the grab is set at a time when a button is already down, or774* if the window where the button was pressed was deleted, then775* dispPtr->buttonWinPtr will stay NULL. Just forget about the776* auto-grab for the button press; events will go to whatever777* window contains the pointer. If this window isn't in the grab778* tree then redirect events to the grab window.779* 6. When a button is pressed during a local grab, the X server sets780* a grab of its own, since it doesn't even know about our local781* grab. This causes enter and leave events no longer to be782* generated in the same way as for global grabs. To eliminate this783* problem, set a temporary global grab when the first button goes784* down and release it when the last button comes up.785*/786787if ((eventPtr->type == ButtonPress) || (eventPtr->type == ButtonRelease)) {788winPtr2 = dispPtr->buttonWinPtr;789if (winPtr2 == NULL) {790if (outsideGrabTree) {791winPtr2 = dispPtr->grabWinPtr; /* Note 5. */792} else {793winPtr2 = winPtr; /* Note 5. */794}795}796if (eventPtr->type == ButtonPress) {797if ((eventPtr->xbutton.state & ALL_BUTTONS) == 0) {798if (outsideGrabTree) {799TkChangeEventWindow(eventPtr, dispPtr->grabWinPtr);800Tk_QueueWindowEvent(eventPtr, TCL_QUEUE_HEAD);801return 0; /* Note 2. */802}803if (!(dispPtr->grabFlags & GRAB_GLOBAL)) { /* Note 6. */804serial = NextRequest(dispPtr->display);805if (XGrabPointer(dispPtr->display,806dispPtr->grabWinPtr->window, True,807ButtonPressMask|ButtonReleaseMask|ButtonMotionMask,808GrabModeAsync, GrabModeAsync, None, None,809CurrentTime) == 0) {810EatGrabEvents(dispPtr, serial);811if (XGrabKeyboard(dispPtr->display, winPtr->window,812False, GrabModeAsync, GrabModeAsync,813CurrentTime) == 0) {814dispPtr->grabFlags |= GRAB_TEMP_GLOBAL;815} else {816XUngrabPointer(dispPtr->display, CurrentTime);817}818}819}820dispPtr->buttonWinPtr = winPtr;821return 1;822}823} else {824if ((eventPtr->xbutton.state & ALL_BUTTONS)825== buttonStates[eventPtr->xbutton.button - Button1]) {826ReleaseButtonGrab(dispPtr); /* Note 4. */827}828}829if (winPtr2 != winPtr) {830TkChangeEventWindow(eventPtr, winPtr2);831Tk_QueueWindowEvent(eventPtr, TCL_QUEUE_HEAD);832return 0; /* Note 3. */833}834}835836return 1;837}838839/*840*----------------------------------------------------------------------841*842* TkChangeEventWindow --843*844* Given an event and a new window to which the event should be845* retargeted, modify fields of the event so that the event is846* properly retargeted to the new window.847*848* Results:849* The following fields of eventPtr are modified: window,850* subwindow, x, y, same_screen.851*852* Side effects:853* None.854*855*----------------------------------------------------------------------856*/857858void859TkChangeEventWindow(eventPtr, winPtr)860register XEvent *eventPtr; /* Event to retarget. Must have861* type ButtonPress, ButtonRelease, KeyPress,862* KeyRelease, MotionNotify, EnterNotify,863* or LeaveNotify. */864TkWindow *winPtr; /* New target window for event. */865{866int x, y, sameScreen, bd;867register TkWindow *childPtr;868869eventPtr->xmotion.window = Tk_WindowId(winPtr);870if (eventPtr->xmotion.root ==871RootWindow(winPtr->display, winPtr->screenNum)) {872Tk_GetRootCoords((Tk_Window) winPtr, &x, &y);873eventPtr->xmotion.x = eventPtr->xmotion.x_root - x;874eventPtr->xmotion.y = eventPtr->xmotion.y_root - y;875eventPtr->xmotion.subwindow = None;876for (childPtr = winPtr->childList; childPtr != NULL;877childPtr = childPtr->nextPtr) {878if (childPtr->flags & TK_TOP_LEVEL) {879continue;880}881x = eventPtr->xmotion.x - childPtr->changes.x;882y = eventPtr->xmotion.y - childPtr->changes.y;883bd = childPtr->changes.border_width;884if ((x >= -bd) && (y >= -bd)885&& (x < (childPtr->changes.width + bd))886&& (y < (childPtr->changes.height + bd))) {887eventPtr->xmotion.subwindow = childPtr->window;888}889}890sameScreen = 1;891} else {892eventPtr->xmotion.x = 0;893eventPtr->xmotion.y = 0;894eventPtr->xmotion.subwindow = None;895sameScreen = 0;896}897if (eventPtr->type == MotionNotify) {898eventPtr->xmotion.same_screen = sameScreen;899} else {900eventPtr->xbutton.same_screen = sameScreen;901}902}903904/*905*----------------------------------------------------------------------906*907* TkInOutEvents --908*909* This procedure synthesizes EnterNotify and LeaveNotify events910* to correctly transfer the pointer from one window to another.911* It can also be used to generate FocusIn and FocusOut events912* to move the input focus.913*914* Results:915* None.916*917* Side effects:918* Synthesized events may be pushed back onto the event queue.919* The event pointed to by eventPtr is modified.920*921*----------------------------------------------------------------------922*/923924void925TkInOutEvents(eventPtr, sourcePtr, destPtr, leaveType, enterType, position)926XEvent *eventPtr; /* A template X event. Must have all fields927* properly set except for type, window,928* subwindow, x, y, detail, and same_screen929* (Not all of these fields are valid for930* FocusIn/FocusOut events; x_root and y_root931* must be valid for Enter/Leave events, even932* though x and y needn't be valid). */933TkWindow *sourcePtr; /* Window that used to have the pointer or934* focus (NULL means it was not in a window935* managed by this process). */936TkWindow *destPtr; /* Window that is to end up with the pointer937* or focus (NULL means it's not one managed938* by this process). */939int leaveType; /* Type of events to generate for windows940* being left (LeaveNotify or FocusOut). 0941* means don't generate leave events. */942int enterType; /* Type of events to generate for windows943* being entered (EnterNotify or FocusIn). 0944* means don't generate enter events. */945Tcl_QueuePosition position; /* Position at which events are added to946* the system event queue. */947{948register TkWindow *winPtr;949int upLevels, downLevels, i, j, focus;950951/*952* There are four possible cases to deal with:953*954* 1. SourcePtr and destPtr are the same. There's nothing to do in955* this case.956* 2. SourcePtr is an ancestor of destPtr in the same top-level957* window. Must generate events down the window tree from source958* to dest.959* 3. DestPtr is an ancestor of sourcePtr in the same top-level960* window. Must generate events up the window tree from sourcePtr961* to destPtr.962* 4. All other cases. Must first generate events up the window tree963* from sourcePtr to its top-level, then down from destPtr's964* top-level to destPtr. This form is called "non-linear."965*966* The call to FindCommonAncestor separates these four cases and decides967* how many levels up and down events have to be generated for.968*/969970if (sourcePtr == destPtr) {971return;972}973if ((leaveType == FocusOut) || (enterType == FocusIn)) {974focus = 1;975} else {976focus = 0;977}978FindCommonAncestor(sourcePtr, destPtr, &upLevels, &downLevels);979980/*981* Generate enter/leave events and add them to the grab event queue.982*/983984985#define QUEUE(w, t, d) \986if (w->window != None) { \987eventPtr->type = t; \988if (focus) { \989eventPtr->xfocus.window = w->window; \990eventPtr->xfocus.detail = d; \991} else { \992eventPtr->xcrossing.detail = d; \993TkChangeEventWindow(eventPtr, w); \994} \995Tk_QueueWindowEvent(eventPtr, position); \996}997998if (downLevels == 0) {9991000/*1001* SourcePtr is an inferior of destPtr.1002*/10031004if (leaveType != 0) {1005QUEUE(sourcePtr, leaveType, NotifyAncestor);1006for (winPtr = sourcePtr->parentPtr, i = upLevels-1; i > 0;1007winPtr = winPtr->parentPtr, i--) {1008QUEUE(winPtr, leaveType, NotifyVirtual);1009}1010}1011if ((enterType != 0) && (destPtr != NULL)) {1012QUEUE(destPtr, enterType, NotifyInferior);1013}1014} else if (upLevels == 0) {10151016/*1017* DestPtr is an inferior of sourcePtr.1018*/10191020if ((leaveType != 0) && (sourcePtr != NULL)) {1021QUEUE(sourcePtr, leaveType, NotifyInferior);1022}1023if (enterType != 0) {1024for (i = downLevels-1; i > 0; i--) {1025for (winPtr = destPtr->parentPtr, j = 1; j < i;1026winPtr = winPtr->parentPtr, j++) {1027}1028QUEUE(winPtr, enterType, NotifyVirtual);1029}1030if (destPtr != NULL) {1031QUEUE(destPtr, enterType, NotifyAncestor);1032}1033}1034} else {10351036/*1037* Non-linear: neither window is an inferior of the other.1038*/10391040if (leaveType != 0) {1041QUEUE(sourcePtr, leaveType, NotifyNonlinear);1042for (winPtr = sourcePtr->parentPtr, i = upLevels-1; i > 0;1043winPtr = winPtr->parentPtr, i--) {1044QUEUE(winPtr, leaveType, NotifyNonlinearVirtual);1045}1046}1047if (enterType != 0) {1048for (i = downLevels-1; i > 0; i--) {1049for (winPtr = destPtr->parentPtr, j = 1; j < i;1050winPtr = winPtr->parentPtr, j++) {1051}1052QUEUE(winPtr, enterType, NotifyNonlinearVirtual);1053}1054if (destPtr != NULL) {1055QUEUE(destPtr, enterType, NotifyNonlinear);1056}1057}1058}1059}10601061/*1062*----------------------------------------------------------------------1063*1064* MovePointer2 --1065*1066* This procedure synthesizes EnterNotify and LeaveNotify events1067* to correctly transfer the pointer from one window to another.1068* It is different from TkInOutEvents in that no template X event1069* needs to be supplied; this procedure generates the template1070* event and calls TkInOutEvents.1071*1072* Results:1073* None.1074*1075* Side effects:1076* Synthesized events may be pushed back onto the event queue.1077*1078*----------------------------------------------------------------------1079*/10801081static void1082MovePointer2(sourcePtr, destPtr, mode, leaveEvents, enterEvents)1083TkWindow *sourcePtr; /* Window currently containing pointer (NULL1084* means it's not one managed by this1085* process). */1086TkWindow *destPtr; /* Window that is to end up containing the1087* pointer (NULL means it's not one managed1088* by this process). */1089int mode; /* Mode for enter/leave events, such as1090* NotifyNormal or NotifyUngrab. */1091int leaveEvents; /* Non-zero means generate leave events for the1092* windows being left. Zero means don't1093* generate leave events. */1094int enterEvents; /* Non-zero means generate enter events for the1095* windows being entered. Zero means don't1096* generate enter events. */1097{1098XEvent event;1099Window dummy1, dummy2;1100int dummy3, dummy4;1101TkWindow *winPtr;11021103winPtr = sourcePtr;1104if ((winPtr == NULL) || (winPtr->window == None)) {1105winPtr = destPtr;1106if ((winPtr == NULL) || (winPtr->window == None)) {1107return;1108}1109}11101111event.xcrossing.serial = LastKnownRequestProcessed(1112winPtr->display);1113event.xcrossing.send_event = GENERATED_EVENT_MAGIC;1114event.xcrossing.display = winPtr->display;1115event.xcrossing.root = RootWindow(winPtr->display,1116winPtr->screenNum);1117event.xcrossing.time = TkCurrentTime(winPtr->dispPtr);1118XQueryPointer(winPtr->display, winPtr->window, &dummy1, &dummy2,1119&event.xcrossing.x_root, &event.xcrossing.y_root,1120&dummy3, &dummy4, &event.xcrossing.state);1121event.xcrossing.mode = mode;1122event.xcrossing.focus = False;1123TkInOutEvents(&event, sourcePtr, destPtr, (leaveEvents) ? LeaveNotify : 0,1124(enterEvents) ? EnterNotify : 0, TCL_QUEUE_MARK);1125}11261127/*1128*----------------------------------------------------------------------1129*1130* TkGrabDeadWindow --1131*1132* This procedure is invoked whenever a window is deleted, so that1133* grab-related cleanup can be performed.1134*1135* Results:1136* None.1137*1138* Side effects:1139* Various cleanups happen, such as generating events to move the1140* pointer back to its "natural" window as if an ungrab had been1141* done. See the code.1142*1143*----------------------------------------------------------------------1144*/11451146void1147TkGrabDeadWindow(winPtr)1148register TkWindow *winPtr; /* Window that is in the process1149* of being deleted. */1150{1151TkDisplay *dispPtr = winPtr->dispPtr;11521153if (dispPtr->eventualGrabWinPtr == winPtr) {1154/*1155* Grab window was deleted. Release the grab.1156*/11571158Tk_Ungrab((Tk_Window) dispPtr->eventualGrabWinPtr);1159} else if (dispPtr->buttonWinPtr == winPtr) {1160ReleaseButtonGrab(dispPtr);1161}1162if (dispPtr->serverWinPtr == winPtr) {1163if (winPtr->flags & TK_TOP_LEVEL) {1164dispPtr->serverWinPtr = NULL;1165} else {1166dispPtr->serverWinPtr = winPtr->parentPtr;1167}1168}1169if (dispPtr->grabWinPtr == winPtr) {1170dispPtr->grabWinPtr = NULL;1171}1172}11731174/*1175*----------------------------------------------------------------------1176*1177* EatGrabEvents --1178*1179* This procedure is called to eliminate any Enter, Leave,1180* FocusIn, or FocusOut events in the event queue for a1181* display that have mode NotifyGrab or NotifyUngrab and1182* have a serial number no less than a given value and are not1183* generated by the grab module.1184*1185* Results:1186* None.1187*1188* Side effects:1189* DispPtr's display gets sync-ed, and some of the events get1190* removed from the Tk event queue.1191*1192*----------------------------------------------------------------------1193*/11941195static void1196EatGrabEvents(dispPtr, serial)1197TkDisplay *dispPtr; /* Display from which to consume events. */1198unsigned int serial; /* Only discard events that have a serial1199* number at least this great. */1200{1201Tk_RestrictProc *oldProc;1202GrabInfo info;1203ClientData oldArg, dummy;12041205info.display = dispPtr->display;1206info.serial = serial;1207XSync(dispPtr->display, False);1208oldProc = Tk_RestrictEvents(GrabRestrictProc, (ClientData)&info, &oldArg);1209while (Tcl_DoOneEvent(TCL_DONT_WAIT|TCL_WINDOW_EVENTS)) {1210}1211Tk_RestrictEvents(oldProc, oldArg, &dummy);1212}12131214/*1215*----------------------------------------------------------------------1216*1217* GrabRestrictProc --1218*1219* A Tk_RestrictProc used by EatGrabEvents to eliminate any1220* Enter, Leave, FocusIn, or FocusOut events in the event queue1221* for a display that has mode NotifyGrab or NotifyUngrab and1222* have a serial number no less than a given value.1223*1224* Results:1225* Returns either TK_DISCARD_EVENT or TK_DEFER_EVENT.1226*1227* Side effects:1228* None.1229*1230*----------------------------------------------------------------------1231*/12321233static Tk_RestrictAction1234GrabRestrictProc(arg, eventPtr)1235ClientData arg;1236XEvent *eventPtr;1237{1238GrabInfo *info = (GrabInfo *) arg;1239int mode, diff;12401241/*1242* The diff caculation is trickier than it may seem. Don't forget1243* that serial numbers can wrap around, so can't compare the two1244* serial numbers directly.1245*/12461247diff = eventPtr->xany.serial - info->serial;1248if ((eventPtr->type == EnterNotify)1249|| (eventPtr->type == LeaveNotify)) {1250mode = eventPtr->xcrossing.mode;1251} else if ((eventPtr->type == FocusIn)1252|| (eventPtr->type == FocusOut)) {1253mode = eventPtr->xfocus.mode;1254} else {1255mode = NotifyNormal;1256}1257if ((info->display != eventPtr->xany.display) || (mode == NotifyNormal)1258|| (diff < 0)) {1259return TK_DEFER_EVENT;1260} else {1261return TK_DISCARD_EVENT;1262}1263}12641265/*1266*----------------------------------------------------------------------1267*1268* QueueGrabWindowChange --1269*1270* This procedure queues a special event in the Tcl event queue,1271* which will cause the "grabWinPtr" field for the display to get1272* modified when the event is processed. This is needed to make1273* sure that the grab window changes at the proper time relative1274* to grab-related enter and leave events that are also in the1275* queue. In particular, this approach works even when multiple1276* grabs and ungrabs happen back-to-back.1277*1278* Results:1279* None.1280*1281* Side effects:1282* DispPtr->grabWinPtr will be modified later (by GrabWinEventProc)1283* when the event is removed from the grab event queue.1284*1285*----------------------------------------------------------------------1286*/12871288static void1289QueueGrabWindowChange(dispPtr, grabWinPtr)1290TkDisplay *dispPtr; /* Display on which to change the grab1291* window. */1292TkWindow *grabWinPtr; /* Window that is to become the new grab1293* window (may be NULL). */1294{1295NewGrabWinEvent *grabEvPtr;12961297grabEvPtr = (NewGrabWinEvent *) ckalloc(sizeof(NewGrabWinEvent));1298grabEvPtr->header.proc = GrabWinEventProc;1299grabEvPtr->dispPtr = dispPtr;1300if (grabWinPtr == NULL) {1301grabEvPtr->grabWindow = None;1302} else {1303grabEvPtr->grabWindow = grabWinPtr->window;1304}1305Tcl_QueueEvent(&grabEvPtr->header, TCL_QUEUE_MARK);1306dispPtr->eventualGrabWinPtr = grabWinPtr;1307}13081309/*1310*----------------------------------------------------------------------1311*1312* GrabWinEventProc --1313*1314* This procedure is invoked as a handler for Tcl_Events of type1315* NewGrabWinEvent. It updates the current grab window field in1316* a display.1317*1318* Results:1319* Returns 1 if the event was processed, 0 if it should be deferred1320* for processing later.1321*1322* Side effects:1323* The grabWinPtr field is modified in the display associated with1324* the event.1325*1326*----------------------------------------------------------------------1327*/13281329static int1330GrabWinEventProc(evPtr, flags)1331Tcl_Event *evPtr; /* Event of type NewGrabWinEvent. */1332int flags; /* Flags argument to Tk_DoOneEvent: indicates1333* what kinds of events are being processed1334* right now. */1335{1336NewGrabWinEvent *grabEvPtr = (NewGrabWinEvent *) evPtr;13371338grabEvPtr->dispPtr->grabWinPtr = (TkWindow *) Tk_IdToWindow(1339grabEvPtr->dispPtr->display, grabEvPtr->grabWindow);1340return 1;1341}13421343/*1344*----------------------------------------------------------------------1345*1346* FindCommonAncestor --1347*1348* Given two windows, this procedure finds their least common1349* ancestor and also computes how many levels up this ancestor1350* is from each of the original windows.1351*1352* Results:1353* If the windows are in different applications or top-level1354* windows, then NULL is returned and *countPtr1 and *countPtr21355* are set to the depths of the two windows in their respective1356* top-level windows (1 means the window is a top-level, 2 means1357* its parent is a top-level, and so on). Otherwise, the return1358* value is a pointer to the common ancestor and the counts are1359* set to the distance of winPtr1 and winPtr2 from this ancestor1360* (1 means they're children, 2 means grand-children, etc.).1361*1362* Side effects:1363* None.1364*1365*----------------------------------------------------------------------1366*/13671368static TkWindow *1369FindCommonAncestor(winPtr1, winPtr2, countPtr1, countPtr2)1370TkWindow *winPtr1; /* First window. May be NULL. */1371TkWindow *winPtr2; /* Second window. May be NULL. */1372int *countPtr1; /* Store nesting level of winPtr1 within1373* common ancestor here. */1374int *countPtr2; /* Store nesting level of winPtr2 within1375* common ancestor here. */1376{1377register TkWindow *winPtr;1378TkWindow *ancestorPtr;1379int count1, count2, i;13801381/*1382* Mark winPtr1 and all of its ancestors with a special flag bit.1383*/13841385if (winPtr1 != NULL) {1386for (winPtr = winPtr1; winPtr != NULL; winPtr = winPtr->parentPtr) {1387winPtr->flags |= TK_GRAB_FLAG;1388if (winPtr->flags & TK_TOP_LEVEL) {1389break;1390}1391}1392}13931394/*1395* Search upwards from winPtr2 until an ancestor of winPtr1 is1396* found or a top-level window is reached.1397*/13981399winPtr = winPtr2;1400count2 = 0;1401ancestorPtr = NULL;1402if (winPtr2 != NULL) {1403for (; winPtr != NULL; count2++, winPtr = winPtr->parentPtr) {1404if (winPtr->flags & TK_GRAB_FLAG) {1405ancestorPtr = winPtr;1406break;1407}1408if (winPtr->flags & TK_TOP_LEVEL) {1409count2++;1410break;1411}1412}1413}14141415/*1416* Search upwards from winPtr1 again, clearing the flag bits and1417* remembering how many levels up we had to go.1418*/14191420if (winPtr1 == NULL) {1421count1 = 0;1422} else {1423count1 = -1;1424for (i = 0, winPtr = winPtr1; winPtr != NULL;1425i++, winPtr = winPtr->parentPtr) {1426winPtr->flags &= ~TK_GRAB_FLAG;1427if (winPtr == ancestorPtr) {1428count1 = i;1429}1430if (winPtr->flags & TK_TOP_LEVEL) {1431if (count1 == -1) {1432count1 = i+1;1433}1434break;1435}1436}1437}14381439*countPtr1 = count1;1440*countPtr2 = count2;1441return ancestorPtr;1442}14431444/*1445*----------------------------------------------------------------------1446*1447* TkPositionInTree --1448*1449* Compute where the given window is relative to a particular1450* subtree of the window hierarchy.1451*1452* Results:1453*1454* Returns TK_GRAB_IN_TREE if the window is contained in the1455* subtree. Returns TK_GRAB_ANCESTOR if the window is an1456* ancestor of the subtree, in the same toplevel. Otherwise1457* it returns TK_GRAB_EXCLUDED.1458*1459* Side effects:1460* None.1461*1462*----------------------------------------------------------------------1463*/14641465int1466TkPositionInTree(winPtr, treePtr)1467TkWindow *winPtr; /* Window to be checked. */1468TkWindow *treePtr; /* Root of tree to compare against. */1469{1470TkWindow *winPtr2;14711472for (winPtr2 = winPtr; winPtr2 != treePtr;1473winPtr2 = winPtr2->parentPtr) {1474if (winPtr2 == NULL) {1475for (winPtr2 = treePtr; winPtr2 != NULL;1476winPtr2 = winPtr2->parentPtr) {1477if (winPtr2 == winPtr) {1478return TK_GRAB_ANCESTOR;1479}1480if (winPtr2->flags & TK_TOP_LEVEL) {1481break;1482}1483}1484return TK_GRAB_EXCLUDED;1485}1486}1487return TK_GRAB_IN_TREE;1488}14891490/*1491*----------------------------------------------------------------------1492*1493* TkGrabState --1494*1495* Given a window, this procedure returns a value that indicates1496* the grab state of the application relative to the window.1497*1498* Results:1499* The return value is one of three things:1500* TK_GRAB_NONE - no grab is in effect.1501* TK_GRAB_IN_TREE - there is a grab in effect, and winPtr1502* is in the grabbed subtree.1503* TK_GRAB_ANCESTOR - there is a grab in effect; winPtr is1504* an ancestor of the grabbed window, in1505* the same toplevel.1506* TK_GRAB_EXCLUDED - there is a grab in effect; winPtr is1507* outside the tree of the grab and is not1508* an ancestor of the grabbed window in the1509* same toplevel.1510*1511* Side effects:1512* None.1513*1514*----------------------------------------------------------------------1515*/15161517int1518TkGrabState(winPtr)1519TkWindow *winPtr; /* Window for which grab information is1520* needed. */1521{1522TkWindow *grabWinPtr = winPtr->dispPtr->grabWinPtr;15231524if (grabWinPtr == NULL) {1525return TK_GRAB_NONE;1526}1527if ((winPtr->mainPtr != grabWinPtr->mainPtr)1528&& !(winPtr->dispPtr->grabFlags & GRAB_GLOBAL)) {1529return TK_GRAB_NONE;1530}15311532return TkPositionInTree(winPtr, grabWinPtr);1533}153415351536