OSDN Git Service

*** empty log message ***
[pf3gnuchains/sourceware.git] / tk / generic / tkGrab.c
1 /* 
2  * tkGrab.c --
3  *
4  *      This file provides procedures that implement grabs for Tk.
5  *
6  * Copyright (c) 1992-1994 The Regents of the University of California.
7  * Copyright (c) 1994-1997 Sun Microsystems, Inc.
8  *
9  * See the file "license.terms" for information on usage and redistribution
10  * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
11  *
12  * RCS: @(#) $Id$
13  */
14
15 #include "tkPort.h"
16 #include "tkInt.h"
17
18 #if !(defined(__WIN32__) || defined(MAC_TCL) || defined(MAC_OSX_TK))
19 #include "tkUnixInt.h"
20 #endif
21
22 /*
23  * The grab state machine has four states: ungrabbed, button pressed,
24  * grabbed, and button pressed while grabbed.  In addition, there are
25  * three pieces of grab state information: the current grab window,
26  * the current restrict window, and whether the mouse is captured.
27  *
28  * The current grab window specifies the point in the Tk window
29  * heirarchy above which pointer events will not be reported.  Any
30  * window within the subtree below the grab window will continue to
31  * receive events as normal.  Events outside of the grab tree will be
32  * reported to the grab window.
33  *
34  * If the current restrict window is set, then all pointer events will
35  * be reported only to the restrict window.  The restrict window is
36  * normally set during an automatic button grab.
37  *
38  * The mouse capture state specifies whether the window system will
39  * report mouse events outside of any Tk toplevels.  This is set
40  * during a global grab or an automatic button grab.
41  *
42  * The transitions between different states is given in the following
43  * table:
44  * 
45  * Event\State  U       B       G       GB
46  * -----------  --      --      --      --
47  * FirstPress   B       B       GB      GB
48  * Press        B       B       G       GB
49  * Release      U       B       G       GB
50  * LastRelease  U       U       G       G
51  * Grab         G       G       G       G
52  * Ungrab       U       B       U       U
53  *
54  * Note: U=Ungrabbed, B=Button, G=Grabbed, GB=Grab and Button
55  *
56  * In addition, the following conditions are always true:
57  *
58  * State\Variable       Grab         Restrict        Capture
59  * --------------       ----         --------        -------
60  * Ungrabbed             0              0               0
61  * Button                0              1               1
62  * Grabbed               1              0               b/g
63  * Grab and Button       1              1               1
64  *
65  * Note: 0 means variable is set to NULL, 1 means variable is set to
66  * some window, b/g means the variable is set to a window if a button
67  * is currently down or a global grab is in effect.
68  *
69  * The final complication to all of this is enter and leave events.
70  * In order to correctly handle all of the various cases, Tk cannot
71  * rely on X enter/leave events in all situations.  The following
72  * describes the correct sequence of enter and leave events that
73  * should be observed by Tk scripts:
74  *
75  * Event(state)         Enter/Leave From -> To
76  * ------------         ----------------------
77  * LastRelease(B | GB): restrict window -> anc(grab window, event window)
78  * Grab(U | B):         event window -> anc(grab window, event window)
79  * Grab(G):             anc(old grab window, event window) ->
80  *                              anc(new grab window, event window)
81  * Grab(GB):            restrict window -> anc(new grab window, event window)
82  * Ungrab(G):           anc(grab window, event window) -> event window
83  * Ungrab(GB):          restrict window -> event window
84  *
85  * Note: anc(x,y) returns the least ancestor of y that is in the tree
86  * of x, terminating at toplevels.
87  */
88
89 /*
90  * The following structure is used to pass information to 
91  * GrabRestrictProc from EatGrabEvents.
92  */
93
94 typedef struct {
95     Display *display;           /* Display from which to discard events. */
96     unsigned int serial;        /* Serial number with which to compare. */
97 } GrabInfo;
98
99 /*
100  * Bit definitions for grabFlags field of TkDisplay structures:
101  *
102  * GRAB_GLOBAL                  1 means this is a global grab (we grabbed via
103  *                              the server so all applications are locked out).
104  *                              0 means this is a local grab that affects
105  *                              only this application.
106  * GRAB_TEMP_GLOBAL             1 means we've temporarily grabbed via the
107  *                              server because a button is down and we want
108  *                              to make sure that we get the button-up
109  *                              event.  The grab will be released when the
110  *                              last mouse button goes up.
111  */
112
113 #define GRAB_GLOBAL             1
114 #define GRAB_TEMP_GLOBAL        4
115
116 /*
117  * The following structure is a Tcl_Event that triggers a change in
118  * the grabWinPtr field of a display.  This event guarantees that
119  * the change occurs in the proper order relative to enter and leave
120  * events.
121  */
122
123 typedef struct NewGrabWinEvent {
124     Tcl_Event header;           /* Standard information for all Tcl events. */
125     TkDisplay *dispPtr;         /* Display whose grab window is to change. */
126     Window grabWindow;          /* New grab window for display.  This is
127                                  * recorded instead of a (TkWindow *) because
128                                  * it will allow us to detect cases where
129                                  * the window is destroyed before this event
130                                  * is processed. */
131 } NewGrabWinEvent;
132
133 /*
134  * The following magic value is stored in the "send_event" field of
135  * EnterNotify and LeaveNotify events that are generated in this
136  * file.  This allows us to separate "real" events coming from the
137  * server from those that we generated.
138  */
139
140 #define GENERATED_EVENT_MAGIC ((Bool) 0x147321ac)
141
142 /*
143  * Mask that selects any of the state bits corresponding to buttons,
144  * plus masks that select individual buttons' bits:
145  */
146
147 #define ALL_BUTTONS \
148         (Button1Mask|Button2Mask|Button3Mask|Button4Mask|Button5Mask)
149 static unsigned int buttonStates[] = {
150     Button1Mask, Button2Mask, Button3Mask, Button4Mask, Button5Mask
151 };
152
153 /*
154  * Forward declarations for procedures declared later in this file:
155  */
156
157 static void             EatGrabEvents _ANSI_ARGS_((TkDisplay *dispPtr,
158                             unsigned int serial));
159 static TkWindow *       FindCommonAncestor _ANSI_ARGS_((TkWindow *winPtr1,
160                             TkWindow *winPtr2, int *countPtr1,
161                             int *countPtr2));
162 static Tk_RestrictAction GrabRestrictProc _ANSI_ARGS_((ClientData arg,
163                             XEvent *eventPtr));
164 static int              GrabWinEventProc _ANSI_ARGS_((Tcl_Event *evPtr,
165                             int flags));
166 static void             MovePointer2 _ANSI_ARGS_((TkWindow *sourcePtr,
167                             TkWindow *destPtr, int mode, int leaveEvents,
168                             int EnterEvents));
169 static void             QueueGrabWindowChange _ANSI_ARGS_((TkDisplay *dispPtr,
170                             TkWindow *grabWinPtr));
171 static void             ReleaseButtonGrab _ANSI_ARGS_((TkDisplay *dispPtr));
172 \f
173 /*
174  *----------------------------------------------------------------------
175  *
176  * Tk_GrabObjCmd --
177  *
178  *      This procedure is invoked to process the "grab" Tcl command.
179  *      See the user documentation for details on what it does.
180  *
181  * Results:
182  *      A standard Tcl result.
183  *
184  * Side effects:
185  *      See the user documentation.
186  *
187  *----------------------------------------------------------------------
188  */
189
190         /* ARGSUSED */
191 int
192 Tk_GrabObjCmd(clientData, interp, objc, objv)
193     ClientData clientData;      /* Main window associated with
194                                  * interpreter. */
195     Tcl_Interp *interp;         /* Current interpreter. */
196     int objc;                   /* Number of arguments. */
197     Tcl_Obj *CONST objv[];      /* Argument objects. */
198 {
199     int globalGrab;
200     Tk_Window tkwin;
201     TkDisplay *dispPtr;
202     char *arg;
203     int index;
204     int len;
205     static CONST char *optionStrings[] = { "current", "release",
206                                          "set", "status", (char *) NULL };
207   
208     static CONST char *flagStrings[] = { "-global", (char *) NULL };
209
210     enum options { GRABCMD_CURRENT, GRABCMD_RELEASE,
211                        GRABCMD_SET, GRABCMD_STATUS };
212     
213     if (objc < 2) {
214         /*
215          * Can't use Tcl_WrongNumArgs here because we want the message to
216          * read:
217          * wrong # args: should be "cmd ?-global window" or "cmd option
218          *    ?arg arg ...?"
219          * We can fake it with Tcl_WrongNumArgs if we assume the command name
220          * is "grab", but if it has been aliased, the message will be
221          * incorrect.
222          */
223         Tcl_ResetResult(interp);
224         Tcl_AppendResult(interp, "wrong # args: should be \"",
225                 Tcl_GetString(objv[0]), " ?-global? window\" or \"",
226                 Tcl_GetString(objv[0]), " option ?arg arg ...?\"",
227                 (char *) NULL);
228         return TCL_ERROR;
229     }
230
231     /*
232      * First check for a window name or "-global" as the first argument.
233      */
234
235     arg = Tcl_GetStringFromObj(objv[1], &len);
236     if (arg[0] == '.') {
237         /* [grab window] */
238         if (objc != 2) {
239             Tcl_WrongNumArgs(interp, 1, objv, "?-global? window");
240             return TCL_ERROR;
241         }
242         tkwin = Tk_NameToWindow(interp, arg, (Tk_Window) clientData);
243         if (tkwin == NULL) {
244             return TCL_ERROR;
245         }
246         return Tk_Grab(interp, tkwin, 0);
247     } else if (arg[0] == '-' && len > 1) {
248         if (Tcl_GetIndexFromObj(interp, objv[1], flagStrings, "option", 0,
249                 &index) != TCL_OK) {
250             return TCL_ERROR;
251         }
252
253         /* [grab -global window] */
254         if (objc != 3) {
255             Tcl_WrongNumArgs(interp, 1, objv, "?-global? window");
256             return TCL_ERROR;
257         }
258         tkwin = Tk_NameToWindow(interp, Tcl_GetString(objv[2]),
259                 (Tk_Window) clientData);
260         if (tkwin == NULL) {
261             return TCL_ERROR;
262         }
263         return Tk_Grab(interp, tkwin, 1);
264     }
265
266     /*
267      * First argument is not a window name and not "-global", find out
268      * which option it is.
269      */
270
271     if (Tcl_GetIndexFromObj(interp, objv[1], optionStrings, "option", 0,
272             &index) != TCL_OK) {
273         return TCL_ERROR;
274     }
275     
276     switch ((enum options) index) {
277         case GRABCMD_CURRENT: {
278             /* [grab current ?window?] */
279             if (objc > 3) {
280                 Tcl_WrongNumArgs(interp, 1, objv, "current ?window?");
281                 return TCL_ERROR;
282             }
283             if (objc == 3) {
284                 tkwin = Tk_NameToWindow(interp,
285                         Tcl_GetString(objv[2]), (Tk_Window) clientData);
286                 if (tkwin == NULL) {
287                     return TCL_ERROR;
288                 }
289                 dispPtr = ((TkWindow *) tkwin)->dispPtr;
290                 if (dispPtr->eventualGrabWinPtr != NULL) {
291                     Tcl_SetResult(interp,
292                             dispPtr->eventualGrabWinPtr->pathName, TCL_STATIC);
293                 }
294             } else {
295                 for (dispPtr = TkGetDisplayList(); dispPtr != NULL;
296                      dispPtr = dispPtr->nextPtr) {
297                     if (dispPtr->eventualGrabWinPtr != NULL) {
298                         Tcl_AppendElement(interp,
299                                 dispPtr->eventualGrabWinPtr->pathName);
300                     }
301                 }
302             }
303             return TCL_OK;
304         }
305
306         case GRABCMD_RELEASE: {
307             /* [grab release window] */
308             if (objc != 3) {
309                 Tcl_WrongNumArgs(interp, 1, objv, "release window");
310                 return TCL_ERROR;
311             }
312             tkwin = Tk_NameToWindow(interp,
313                     Tcl_GetString(objv[2]), (Tk_Window) clientData);
314             if (tkwin == NULL) {
315                 Tcl_ResetResult(interp);
316             } else {
317                 Tk_Ungrab(tkwin);
318             }
319             break;
320         }
321         
322         case GRABCMD_SET: {
323             /* [grab set ?-global? window] */
324             if ((objc != 3) && (objc != 4)) {
325                 Tcl_WrongNumArgs(interp, 1, objv, "set ?-global? window");
326                 return TCL_ERROR;
327             }
328             if (objc == 3) {
329                 globalGrab = 0;
330                 tkwin = Tk_NameToWindow(interp,
331                         Tcl_GetString(objv[2]), (Tk_Window) clientData);
332             } else {
333                 globalGrab = 1;
334                 /*
335                  * We could just test the argument by hand instead of using
336                  * Tcl_GetIndexFromObj; the benefit of using the function is
337                  * that it sets up the error message for us, so we are
338                  * certain to be consistant with the rest of Tcl.
339                  */
340                 if (Tcl_GetIndexFromObj(interp, objv[2], flagStrings, "option",
341                         0, &index) != TCL_OK) {
342                     return TCL_ERROR;
343                 }
344                 tkwin = Tk_NameToWindow(interp,
345                         Tcl_GetString(objv[3]), (Tk_Window) clientData);
346             }
347             if (tkwin == NULL) {
348                 return TCL_ERROR;
349             }
350             return Tk_Grab(interp, tkwin, globalGrab);
351         }
352
353         case GRABCMD_STATUS: {
354             /* [grab status window] */
355             TkWindow *winPtr;
356
357             if (objc != 3) {
358                 Tcl_WrongNumArgs(interp, 1, objv, "status window");
359                 return TCL_ERROR;
360             }
361             winPtr = (TkWindow *) Tk_NameToWindow(interp,
362                     Tcl_GetString(objv[2]), (Tk_Window) clientData);
363             if (winPtr == NULL) {
364                 return TCL_ERROR;
365             }
366             dispPtr = winPtr->dispPtr;
367             if (dispPtr->eventualGrabWinPtr != winPtr) {
368                 Tcl_SetResult(interp, "none", TCL_STATIC);
369             } else if (dispPtr->grabFlags & GRAB_GLOBAL) {
370                 Tcl_SetResult(interp, "global", TCL_STATIC);
371             } else {
372                 Tcl_SetResult(interp, "local", TCL_STATIC);
373             }
374             break;
375         }
376     }
377
378     return TCL_OK;
379 }
380 \f
381 /*
382  *----------------------------------------------------------------------
383  *
384  * Tk_Grab --
385  *
386  *      Grabs the pointer and keyboard, so that mouse-related events are
387  *      only reported relative to a given window and its descendants.
388  *
389  * Results:
390  *      A standard Tcl result is returned.  TCL_OK is the normal return
391  *      value;  if the grab could not be set then TCL_ERROR is returned
392  *      and the interp's result will hold an error message.
393  *
394  * Side effects:
395  *      Once this call completes successfully, no window outside the
396  *      tree rooted at tkwin will receive pointer- or keyboard-related
397  *      events until the next call to Tk_Ungrab.  If a previous grab was
398  *      in effect within this application, then it is replaced with a new
399  *      one.
400  *
401  *----------------------------------------------------------------------
402  */
403
404 int
405 Tk_Grab(interp, tkwin, grabGlobal)
406     Tcl_Interp *interp;                 /* Used for error reporting. */
407     Tk_Window tkwin;                    /* Window on whose behalf the pointer
408                                          * is to be grabbed. */
409     int grabGlobal;                     /* Non-zero means issue a grab to the
410                                          * server so that no other application
411                                          * gets mouse or keyboard events.
412                                          * Zero means the grab only applies
413                                          * within this application. */
414 {
415     int grabResult, numTries;
416     TkWindow *winPtr = (TkWindow *) tkwin;
417     TkDisplay *dispPtr = winPtr->dispPtr;
418     TkWindow *winPtr2;
419     unsigned int serial;
420
421     ReleaseButtonGrab(dispPtr);
422     if (dispPtr->eventualGrabWinPtr != NULL) {
423         if ((dispPtr->eventualGrabWinPtr == winPtr)
424                 && (grabGlobal == ((dispPtr->grabFlags & GRAB_GLOBAL) != 0))) {
425             return TCL_OK;
426         }
427         if (dispPtr->eventualGrabWinPtr->mainPtr != winPtr->mainPtr) {
428             alreadyGrabbed:
429             Tcl_SetResult(interp, "grab failed: another application has grab",
430                     TCL_STATIC);
431             return TCL_ERROR;
432         }
433         Tk_Ungrab((Tk_Window) dispPtr->eventualGrabWinPtr);
434     }
435
436     Tk_MakeWindowExist(tkwin);
437     if (!grabGlobal) {
438         Window dummy1, dummy2;
439         int dummy3, dummy4, dummy5, dummy6;
440         unsigned int state;
441
442         /*
443          * Local grab.  However, if any mouse buttons are down, turn
444          * it into a global grab temporarily, until the last button
445          * goes up.  This does two things: (a) it makes sure that we
446          * see the button-up event;  and (b) it allows us to track mouse
447          * motion among all of the windows of this application.
448          */
449
450         dispPtr->grabFlags &= ~(GRAB_GLOBAL|GRAB_TEMP_GLOBAL);
451         XQueryPointer(dispPtr->display, winPtr->window, &dummy1,
452                 &dummy2, &dummy3, &dummy4, &dummy5, &dummy6, &state);
453         if ((state & ALL_BUTTONS) != 0) {
454             dispPtr->grabFlags |= GRAB_TEMP_GLOBAL;
455             goto setGlobalGrab;
456         }
457     } else {
458         dispPtr->grabFlags |= GRAB_GLOBAL;
459         setGlobalGrab:
460
461         /*
462          * Tricky point: must ungrab before grabbing.  This is needed
463          * in case there is a button auto-grab already in effect.  If
464          * there is, and the mouse has moved to a different window, X
465          * won't generate enter and leave events to move the mouse if
466          * we grab without ungrabbing.
467          */
468
469         XUngrabPointer(dispPtr->display, CurrentTime);
470         serial = NextRequest(dispPtr->display);
471
472         /*
473          * Another tricky point: there are races with some window
474          * managers that can cause grabs to fail because the window
475          * manager hasn't released its grab quickly enough.  To work
476          * around this problem, retry a few times after AlreadyGrabbed
477          * errors to give the grab release enough time to register with
478          * the server.
479          */
480
481         grabResult = 0;                 /* Needed only to prevent gcc
482                                          * compiler warnings. */
483         for (numTries = 0; numTries < 10; numTries++) {
484             grabResult = XGrabPointer(dispPtr->display, winPtr->window,
485                     True, ButtonPressMask|ButtonReleaseMask|ButtonMotionMask
486                     |PointerMotionMask, GrabModeAsync, GrabModeAsync, None,
487                     None, CurrentTime);
488             if (grabResult != AlreadyGrabbed) {
489                 break;
490             }
491             Tcl_Sleep(100);
492         }
493         if (grabResult != 0) {
494             grabError:
495             if (grabResult == GrabNotViewable) {
496                 Tcl_SetResult(interp, "grab failed: window not viewable",
497                         TCL_STATIC);
498             } else if (grabResult == AlreadyGrabbed) {
499                 goto alreadyGrabbed;
500             } else if (grabResult == GrabFrozen) {
501                 Tcl_SetResult(interp,
502                         "grab failed: keyboard or pointer frozen", TCL_STATIC);
503             } else if (grabResult == GrabInvalidTime) {
504                 Tcl_SetResult(interp, "grab failed: invalid time",
505                         TCL_STATIC);
506             } else {
507                 char msg[64 + TCL_INTEGER_SPACE];
508         
509                 sprintf(msg, "grab failed for unknown reason (code %d)",
510                         grabResult);
511                 Tcl_AppendResult(interp, msg, (char *) NULL);
512             }
513             return TCL_ERROR;
514         }
515         grabResult = XGrabKeyboard(dispPtr->display, Tk_WindowId(tkwin),
516                 False, GrabModeAsync, GrabModeAsync, CurrentTime);
517         if (grabResult != 0) {
518             XUngrabPointer(dispPtr->display, CurrentTime);
519             goto grabError;
520         }
521
522         /*
523          * Eat up any grab-related events generated by the server for the
524          * grab.  There are several reasons for doing this:
525          *
526          * 1. We have to synthesize the events for local grabs anyway, since
527          *    the server doesn't participate in them.
528          * 2. The server doesn't always generate the right events for global
529          *    grabs (e.g. it generates events even if the current window is
530          *    in the grab tree, which we don't want).
531          * 3. We want all the grab-related events to be processed immediately
532          *    (before other events that are already queued); events coming
533          *    from the server will be in the wrong place, but events we
534          *    synthesize here will go to the front of the queue.
535          */
536
537         EatGrabEvents(dispPtr, serial);
538     }
539
540     /*
541      * Synthesize leave events to move the pointer from its current window
542      * up to the lowest ancestor that it has in common with the grab window.
543      * However, only do this if the pointer is outside the grab window's
544      * subtree but inside the grab window's application.
545      */
546
547     if ((dispPtr->serverWinPtr != NULL)
548             && (dispPtr->serverWinPtr->mainPtr == winPtr->mainPtr)) {
549         for (winPtr2 = dispPtr->serverWinPtr; ; winPtr2 = winPtr2->parentPtr) {
550             if (winPtr2 == winPtr) {
551                 break;
552             }
553             if (winPtr2 == NULL) {
554                 MovePointer2(dispPtr->serverWinPtr, winPtr, NotifyGrab, 1, 0);
555                 break;
556             }
557         }
558     }
559     QueueGrabWindowChange(dispPtr, winPtr);
560     return TCL_OK;
561 }
562 \f
563 /*
564  *----------------------------------------------------------------------
565  *
566  * Tk_Ungrab --
567  *
568  *      Releases a grab on the mouse pointer and keyboard, if there
569  *      is one set on the specified window.
570  *
571  * Results:
572  *      None.
573  *
574  * Side effects:
575  *      Pointer and keyboard events will start being delivered to other
576  *      windows again.
577  *
578  *----------------------------------------------------------------------
579  */
580
581 void
582 Tk_Ungrab(tkwin)
583     Tk_Window tkwin;                    /* Window whose grab should be
584                                          * released. */
585 {
586     TkDisplay *dispPtr;
587     TkWindow *grabWinPtr, *winPtr;
588     unsigned int serial;
589
590     grabWinPtr = (TkWindow *) tkwin;
591     dispPtr = grabWinPtr->dispPtr;
592     if (grabWinPtr != dispPtr->eventualGrabWinPtr) {
593         return;
594     }
595     ReleaseButtonGrab(dispPtr);
596     QueueGrabWindowChange(dispPtr, (TkWindow *) NULL);
597     if (dispPtr->grabFlags & (GRAB_GLOBAL|GRAB_TEMP_GLOBAL)) {
598         dispPtr->grabFlags &= ~(GRAB_GLOBAL|GRAB_TEMP_GLOBAL);
599         serial = NextRequest(dispPtr->display);
600         XUngrabPointer(dispPtr->display, CurrentTime);
601         XUngrabKeyboard(dispPtr->display, CurrentTime);
602         EatGrabEvents(dispPtr, serial);
603     }
604
605     /*
606      * Generate events to move the pointer back to the window where it
607      * really is.  Some notes:
608      * 1. As with grabs, only do this if the "real" window is not a
609      *    descendant of the grab window, since in this case the pointer
610      *    is already where it's supposed to be.
611      * 2. If the "real" window is in some other application then don't
612      *    generate any events at all, since everything's already been
613      *    reported correctly.
614      * 3. Only generate enter events.  Don't generate leave events,
615      *    because we never told the lower-level windows that they
616      *    had the pointer in the first place.
617      */
618
619     for (winPtr = dispPtr->serverWinPtr; ; winPtr = winPtr->parentPtr) {
620         if (winPtr == grabWinPtr) {
621             break;
622         }
623         if (winPtr == NULL) {
624             if ((dispPtr->serverWinPtr == NULL) ||
625                     (dispPtr->serverWinPtr->mainPtr == grabWinPtr->mainPtr)) {
626                 MovePointer2(grabWinPtr, dispPtr->serverWinPtr,
627                         NotifyUngrab, 0, 1);
628             }
629             break;
630         }
631     }
632 }
633 \f
634 /*
635  *----------------------------------------------------------------------
636  *
637  * ReleaseButtonGrab --
638  *
639  *      This procedure is called to release a simulated button grab, if
640  *      there is one in effect.  A button grab is present whenever
641  *      dispPtr->buttonWinPtr is non-NULL or when the GRAB_TEMP_GLOBAL
642  *      flag is set.
643  *
644  * Results:
645  *      None.
646  *
647  * Side effects:
648  *      DispPtr->buttonWinPtr is reset to NULL, and enter and leave
649  *      events are generated if necessary to move the pointer from
650  *      the button grab window to its current window.
651  *
652  *----------------------------------------------------------------------
653  */
654
655 static void
656 ReleaseButtonGrab(dispPtr)
657     register TkDisplay *dispPtr;        /* Display whose button grab is to be
658                                          * released. */
659 {
660     unsigned int serial;
661
662     if (dispPtr->buttonWinPtr != NULL) {
663         if (dispPtr->buttonWinPtr != dispPtr->serverWinPtr) {
664             MovePointer2(dispPtr->buttonWinPtr, dispPtr->serverWinPtr,
665                     NotifyUngrab, 1, 1);
666         }
667         dispPtr->buttonWinPtr = NULL;
668     }
669     if (dispPtr->grabFlags & GRAB_TEMP_GLOBAL) {
670         dispPtr->grabFlags &= ~GRAB_TEMP_GLOBAL;
671         serial = NextRequest(dispPtr->display);
672         XUngrabPointer(dispPtr->display, CurrentTime);
673         XUngrabKeyboard(dispPtr->display, CurrentTime);
674         EatGrabEvents(dispPtr, serial);
675     }
676 }
677 \f
678 /*
679  *----------------------------------------------------------------------
680  *
681  * TkPointerEvent --
682  *
683  *      This procedure is called for each pointer-related event, before
684  *      the event has been processed.  It does various things to make
685  *      grabs work correctly.
686  *
687  * Results:
688  *      If the return value is 1 it means the event should be processed
689  *      (event handlers should be invoked).  If the return value is 0
690  *      it means the event should be ignored in order to make grabs
691  *      work correctly.  In some cases this procedure modifies the event.
692  *
693  * Side effects:
694  *      Grab state information may be updated.  New events may also be
695  *      pushed back onto the event queue to replace or augment the
696  *      one passed in here.
697  *
698  *----------------------------------------------------------------------
699  */
700
701 int
702 TkPointerEvent(eventPtr, winPtr)
703     register XEvent *eventPtr;          /* Pointer to the event. */
704     TkWindow *winPtr;                   /* Tk's information for window
705                                          * where event was reported. */
706 {
707     register TkWindow *winPtr2;
708     TkDisplay *dispPtr = winPtr->dispPtr;
709     unsigned int serial;
710     int outsideGrabTree = 0;
711     int ancestorOfGrab = 0;
712     int appGrabbed = 0;                 /* Non-zero means event is being
713                                          * reported to an application that is
714                                          * affected by the grab. */
715
716     /*
717      * Collect information about the grab (if any).
718      */
719
720     switch (TkGrabState(winPtr)) {
721         case TK_GRAB_IN_TREE:
722             appGrabbed = 1;
723             break;
724         case TK_GRAB_ANCESTOR:
725             appGrabbed = 1;
726             outsideGrabTree = 1;
727             ancestorOfGrab = 1;
728             break;
729         case TK_GRAB_EXCLUDED:
730             appGrabbed = 1;
731             outsideGrabTree = 1;
732             break;
733     }
734
735     if ((eventPtr->type == EnterNotify) || (eventPtr->type == LeaveNotify)) {
736         /*
737          * Keep track of what window the mouse is *really* over.
738          * Any events that we generate have a special send_event value,
739          * which is detected below and used to ignore the event for
740          * purposes of setting serverWinPtr.
741          */
742
743         if (eventPtr->xcrossing.send_event != GENERATED_EVENT_MAGIC) {
744             if ((eventPtr->type == LeaveNotify) &&
745                     (winPtr->flags & TK_TOP_HIERARCHY)) {
746                 dispPtr->serverWinPtr = NULL;
747             } else {
748                 dispPtr->serverWinPtr = winPtr;
749             }
750         }
751
752         /*
753          * When a grab is active, X continues to report enter and leave
754          * events for windows outside the tree of the grab window:
755          * 1. Detect these events and ignore them except for
756          *    windows above the grab window.
757          * 2. Allow Enter and Leave events to pass through the
758          *    windows above the grab window, but never let them
759          *    end up with the pointer *in* one of those windows.
760          */
761
762         if (dispPtr->grabWinPtr != NULL) {
763             if (outsideGrabTree && appGrabbed) {
764                 if (!ancestorOfGrab) {
765                     return 0;
766                 }
767                 switch (eventPtr->xcrossing.detail) {
768                     case NotifyInferior:
769                         return 0;
770                     case NotifyAncestor:
771                         eventPtr->xcrossing.detail = NotifyVirtual;
772                         break;
773                     case NotifyNonlinear:
774                         eventPtr->xcrossing.detail = NotifyNonlinearVirtual;
775                         break;
776                 }
777             }
778
779             /*
780              * Make buttons have the same grab-like behavior inside a grab
781              * as they do outside a grab:  do this by ignoring enter and
782              * leave events except for the window in which the button was
783              * pressed.
784              */
785
786             if ((dispPtr->buttonWinPtr != NULL)
787                     && (winPtr != dispPtr->buttonWinPtr)) {
788                 return 0;
789             }
790         }
791         return 1;
792     }
793
794     if (!appGrabbed) {
795         return 1;
796     }
797
798     if (eventPtr->type == MotionNotify) {
799         /*
800          * When grabs are active, X reports motion events relative to the
801          * window under the pointer.  Instead, it should report the events
802          * relative to the window the button went down in, if there is a
803          * button down.  Otherwise, if the pointer window is outside the
804          * subtree of the grab window, the events should be reported
805          * relative to the grab window.  Otherwise, the event should be
806          * reported to the pointer window.
807          */
808
809         winPtr2 = winPtr;
810         if (dispPtr->buttonWinPtr != NULL) {
811             winPtr2 = dispPtr->buttonWinPtr;
812         } else if (outsideGrabTree || (dispPtr->serverWinPtr == NULL)) {
813             winPtr2 = dispPtr->grabWinPtr;
814         }
815         if (winPtr2 != winPtr) {
816             TkChangeEventWindow(eventPtr, winPtr2);
817             Tk_QueueWindowEvent(eventPtr, TCL_QUEUE_HEAD);
818             return 0;
819         }
820         return 1;
821     }
822
823     /*
824      * Process ButtonPress and ButtonRelease events:
825      * 1. Keep track of whether a button is down and what window it
826      *    went down in.
827      * 2. If the first button goes down outside the grab tree, pretend
828      *    it went down in the grab window.  Note: it's important to
829      *    redirect events to the grab window like this in order to make
830      *    things like menus work, where button presses outside the
831      *    grabbed menu need to be seen.  An application can always
832      *    ignore the events if they occur outside its window.
833      * 3. If a button press or release occurs outside the window where
834      *    the first button was pressed, retarget the event so it's reported
835      *    to the window where the first button was pressed.
836      * 4. If the last button is released in a window different than where
837      *    the first button was pressed, generate Enter/Leave events to
838      *    move the mouse from the button window to its current window.
839      * 5. If the grab is set at a time when a button is already down, or
840      *    if the window where the button was pressed was deleted, then
841      *    dispPtr->buttonWinPtr will stay NULL.  Just forget about the
842      *    auto-grab for the button press;  events will go to whatever
843      *    window contains the pointer.  If this window isn't in the grab
844      *    tree then redirect events to the grab window.
845      * 6. When a button is pressed during a local grab, the X server sets
846      *    a grab of its own, since it doesn't even know about our local
847      *    grab.  This causes enter and leave events no longer to be
848      *    generated in the same way as for global grabs.  To eliminate this
849      *    problem, set a temporary global grab when the first button goes
850      *    down and release it when the last button comes up.
851      */
852
853     if ((eventPtr->type == ButtonPress) || (eventPtr->type == ButtonRelease)) {
854         winPtr2 = dispPtr->buttonWinPtr;
855         if (winPtr2 == NULL) {
856             if (outsideGrabTree) {
857                 winPtr2 = dispPtr->grabWinPtr;                  /* Note 5. */
858             } else {
859                 winPtr2 = winPtr;                               /* Note 5. */
860             }
861         }
862         if (eventPtr->type == ButtonPress) {
863             if ((eventPtr->xbutton.state & ALL_BUTTONS) == 0) {
864                 if (outsideGrabTree) {
865                     TkChangeEventWindow(eventPtr, dispPtr->grabWinPtr);
866                     Tk_QueueWindowEvent(eventPtr, TCL_QUEUE_HEAD);
867                     return 0;                                   /* Note 2. */
868                 }
869                 if (!(dispPtr->grabFlags & GRAB_GLOBAL)) {      /* Note 6. */
870                     serial = NextRequest(dispPtr->display);
871                     if (XGrabPointer(dispPtr->display,
872                             dispPtr->grabWinPtr->window, True,
873                             ButtonPressMask|ButtonReleaseMask|ButtonMotionMask,
874                             GrabModeAsync, GrabModeAsync, None, None,
875                             CurrentTime) == 0) {
876                         EatGrabEvents(dispPtr, serial);
877                         if (XGrabKeyboard(dispPtr->display, winPtr->window,
878                                 False, GrabModeAsync, GrabModeAsync,
879                                 CurrentTime) == 0) {
880                             dispPtr->grabFlags |= GRAB_TEMP_GLOBAL;
881                         } else {
882                             XUngrabPointer(dispPtr->display, CurrentTime);
883                         }
884                     }
885                 }
886                 dispPtr->buttonWinPtr = winPtr;
887                 return 1;
888             }
889         } else {
890             if ((eventPtr->xbutton.state & ALL_BUTTONS)
891                     == buttonStates[eventPtr->xbutton.button - Button1]) {
892                 ReleaseButtonGrab(dispPtr);                     /* Note 4. */
893             }
894         }
895         if (winPtr2 != winPtr) {
896             TkChangeEventWindow(eventPtr, winPtr2);
897             Tk_QueueWindowEvent(eventPtr, TCL_QUEUE_HEAD);
898             return 0;                                           /* Note 3. */
899         }
900     }
901
902     return 1;
903 }
904 \f
905 /*
906  *----------------------------------------------------------------------
907  *
908  * TkChangeEventWindow --
909  *
910  *      Given an event and a new window to which the event should be
911  *      retargeted, modify fields of the event so that the event is
912  *      properly retargeted to the new window.
913  *
914  * Results:
915  *      The following fields of eventPtr are modified:  window,
916  *      subwindow, x, y, same_screen.
917  *
918  * Side effects:
919  *      None.
920  *
921  *----------------------------------------------------------------------
922  */
923
924 void
925 TkChangeEventWindow(eventPtr, winPtr)
926     register XEvent *eventPtr;  /* Event to retarget.  Must have
927                                  * type ButtonPress, ButtonRelease, KeyPress,
928                                  * KeyRelease, MotionNotify, EnterNotify,
929                                  * or LeaveNotify. */
930     TkWindow *winPtr;           /* New target window for event. */
931 {
932     int x, y, sameScreen, bd;
933     register TkWindow *childPtr;
934
935     eventPtr->xmotion.window = Tk_WindowId(winPtr);
936     if (eventPtr->xmotion.root ==
937             RootWindow(winPtr->display, winPtr->screenNum)) {
938         Tk_GetRootCoords((Tk_Window) winPtr, &x, &y);
939         eventPtr->xmotion.x = eventPtr->xmotion.x_root - x;
940         eventPtr->xmotion.y = eventPtr->xmotion.y_root - y;
941         eventPtr->xmotion.subwindow = None;
942         for (childPtr = winPtr->childList; childPtr != NULL;
943                 childPtr = childPtr->nextPtr) {
944             if (childPtr->flags & TK_TOP_HIERARCHY) {
945                 continue;
946             }
947             x = eventPtr->xmotion.x - childPtr->changes.x;
948             y = eventPtr->xmotion.y - childPtr->changes.y;
949             bd = childPtr->changes.border_width;
950             if ((x >= -bd) && (y >= -bd)
951                     && (x < (childPtr->changes.width + bd))
952                     && (y < (childPtr->changes.height + bd))) {
953                 eventPtr->xmotion.subwindow = childPtr->window;
954             }
955         }
956         sameScreen = 1;
957     } else {
958         eventPtr->xmotion.x = 0;
959         eventPtr->xmotion.y = 0;
960         eventPtr->xmotion.subwindow = None;
961         sameScreen = 0;
962     }
963     if (eventPtr->type == MotionNotify) {
964         eventPtr->xmotion.same_screen = sameScreen;
965     } else {
966         eventPtr->xbutton.same_screen = sameScreen;
967     }
968 }
969 \f
970 /*
971  *----------------------------------------------------------------------
972  *
973  * TkInOutEvents --
974  *
975  *      This procedure synthesizes EnterNotify and LeaveNotify events
976  *      to correctly transfer the pointer from one window to another.
977  *      It can also be used to generate FocusIn and FocusOut events
978  *      to move the input focus.
979  *
980  * Results:
981  *      None.
982  *
983  * Side effects:
984  *      Synthesized events may be pushed back onto the event queue.
985  *      The event pointed to by eventPtr is modified.
986  *
987  *----------------------------------------------------------------------
988  */
989
990 void
991 TkInOutEvents(eventPtr, sourcePtr, destPtr, leaveType, enterType, position)
992     XEvent *eventPtr;           /* A template X event.  Must have all fields
993                                  * properly set except for type, window,
994                                  * subwindow, x, y, detail, and same_screen
995                                  * (Not all of these fields are valid for
996                                  * FocusIn/FocusOut events;  x_root and y_root
997                                  * must be valid for Enter/Leave events, even
998                                  * though x and y needn't be valid). */
999     TkWindow *sourcePtr;        /* Window that used to have the pointer or
1000                                  * focus (NULL means it was not in a window
1001                                  * managed by this process). */
1002     TkWindow *destPtr;          /* Window that is to end up with the pointer
1003                                  * or focus (NULL means it's not one managed
1004                                  * by this process). */
1005     int leaveType;              /* Type of events to generate for windows
1006                                  * being left (LeaveNotify or FocusOut).  0
1007                                  * means don't generate leave events. */
1008     int enterType;              /* Type of events to generate for windows
1009                                  * being entered (EnterNotify or FocusIn).  0
1010                                  * means don't generate enter events. */
1011     Tcl_QueuePosition position; /* Position at which events are added to
1012                                  * the system event queue. */
1013 {
1014     register TkWindow *winPtr;
1015     int upLevels, downLevels, i, j, focus;
1016
1017     /*
1018      * There are four possible cases to deal with:
1019      *
1020      * 1. SourcePtr and destPtr are the same.  There's nothing to do in
1021      *    this case.
1022      * 2. SourcePtr is an ancestor of destPtr in the same top-level
1023      *    window.  Must generate events down the window tree from source
1024      *    to dest.
1025      * 3. DestPtr is an ancestor of sourcePtr in the same top-level
1026      *    window.  Must generate events up the window tree from sourcePtr
1027      *    to destPtr.
1028      * 4. All other cases.  Must first generate events up the window tree
1029      *    from sourcePtr to its top-level, then down from destPtr's
1030      *    top-level to destPtr. This form is called "non-linear."
1031      *
1032      * The call to FindCommonAncestor separates these four cases and decides
1033      * how many levels up and down events have to be generated for.
1034      */
1035
1036     if (sourcePtr == destPtr) {
1037         return;
1038     }
1039     if ((leaveType == FocusOut) || (enterType == FocusIn)) {
1040         focus = 1;
1041     } else {
1042         focus = 0;
1043     }
1044     FindCommonAncestor(sourcePtr, destPtr, &upLevels, &downLevels);
1045
1046     /*
1047      * Generate enter/leave events and add them to the grab event queue.
1048      */
1049
1050
1051 #define QUEUE(w, t, d)                                  \
1052     if (w->window != None) {                            \
1053         eventPtr->type = t;                             \
1054         if (focus) {                                    \
1055             eventPtr->xfocus.window = w->window;        \
1056             eventPtr->xfocus.detail = d;                \
1057         } else {                                        \
1058             eventPtr->xcrossing.detail = d;             \
1059             TkChangeEventWindow(eventPtr, w);           \
1060         }                                               \
1061         Tk_QueueWindowEvent(eventPtr, position);        \
1062     }
1063
1064     if (downLevels == 0) {
1065     
1066         /*
1067          * SourcePtr is an inferior of destPtr.
1068          */
1069
1070         if (leaveType != 0) {
1071             QUEUE(sourcePtr, leaveType, NotifyAncestor);
1072             for (winPtr = sourcePtr->parentPtr, i = upLevels-1; i > 0;
1073                     winPtr = winPtr->parentPtr, i--) {
1074                 QUEUE(winPtr, leaveType, NotifyVirtual);
1075             }
1076         }
1077         if ((enterType != 0) && (destPtr != NULL)) {
1078             QUEUE(destPtr, enterType, NotifyInferior);
1079         }
1080     } else if (upLevels == 0) {
1081
1082         /*
1083          * DestPtr is an inferior of sourcePtr.
1084          */
1085
1086         if ((leaveType != 0) && (sourcePtr != NULL)) {
1087             QUEUE(sourcePtr, leaveType, NotifyInferior);
1088         }
1089         if (enterType != 0) {
1090             for (i = downLevels-1; i > 0; i--) {
1091                 for (winPtr = destPtr->parentPtr, j = 1; j < i;
1092                         winPtr = winPtr->parentPtr, j++) {
1093                 }
1094                 QUEUE(winPtr, enterType, NotifyVirtual);
1095             }
1096             if (destPtr != NULL) {
1097                 QUEUE(destPtr, enterType, NotifyAncestor);
1098             }
1099         }
1100     } else {
1101
1102         /*
1103          * Non-linear:  neither window is an inferior of the other.
1104          */
1105
1106         if (leaveType != 0) {
1107             QUEUE(sourcePtr, leaveType, NotifyNonlinear);
1108             for (winPtr = sourcePtr->parentPtr, i = upLevels-1; i > 0;
1109                     winPtr = winPtr->parentPtr, i--) {
1110                 QUEUE(winPtr, leaveType, NotifyNonlinearVirtual);
1111             }
1112         }
1113         if (enterType != 0) {
1114             for (i = downLevels-1; i > 0; i--) {
1115                 for (winPtr = destPtr->parentPtr, j = 1; j < i;
1116                         winPtr = winPtr->parentPtr, j++) {
1117                 }
1118                 QUEUE(winPtr, enterType, NotifyNonlinearVirtual);
1119             }
1120             if (destPtr != NULL) {
1121                 QUEUE(destPtr, enterType, NotifyNonlinear);
1122             }
1123         }
1124     }
1125 }
1126 \f
1127 /*
1128  *----------------------------------------------------------------------
1129  *
1130  * MovePointer2 --
1131  *
1132  *      This procedure synthesizes  EnterNotify and LeaveNotify events
1133  *      to correctly transfer the pointer from one window to another.
1134  *      It is different from TkInOutEvents in that no template X event
1135  *      needs to be supplied;  this procedure generates the template
1136  *      event and calls TkInOutEvents.
1137  *
1138  * Results:
1139  *      None.
1140  *
1141  * Side effects:
1142  *      Synthesized events may be pushed back onto the event queue.
1143  *
1144  *----------------------------------------------------------------------
1145  */
1146
1147 static void
1148 MovePointer2(sourcePtr, destPtr, mode, leaveEvents, enterEvents)
1149     TkWindow *sourcePtr;        /* Window currently containing pointer (NULL
1150                                  * means it's not one managed by this
1151                                  * process). */
1152     TkWindow *destPtr;          /* Window that is to end up containing the
1153                                  * pointer (NULL means it's not one managed
1154                                  * by this process). */
1155     int mode;                   /* Mode for enter/leave events, such as
1156                                  * NotifyNormal or NotifyUngrab. */
1157     int leaveEvents;            /* Non-zero means generate leave events for the
1158                                  * windows being left.  Zero means don't
1159                                  * generate leave events. */
1160     int enterEvents;            /* Non-zero means generate enter events for the
1161                                  * windows being entered.  Zero means don't
1162                                  * generate enter events. */
1163 {
1164     XEvent event;
1165     Window dummy1, dummy2;
1166     int dummy3, dummy4;
1167     TkWindow *winPtr;
1168
1169     winPtr = sourcePtr;
1170     if ((winPtr == NULL) || (winPtr->window == None)) {
1171         winPtr = destPtr;
1172         if ((winPtr == NULL) || (winPtr->window == None)) {
1173             return;
1174         }
1175     }
1176
1177     event.xcrossing.serial = LastKnownRequestProcessed(
1178         winPtr->display);
1179     event.xcrossing.send_event = GENERATED_EVENT_MAGIC;
1180     event.xcrossing.display = winPtr->display;
1181     event.xcrossing.root = RootWindow(winPtr->display,
1182             winPtr->screenNum);
1183     event.xcrossing.time = TkCurrentTime(winPtr->dispPtr);
1184     XQueryPointer(winPtr->display, winPtr->window, &dummy1, &dummy2,
1185             &event.xcrossing.x_root, &event.xcrossing.y_root,
1186             &dummy3, &dummy4, &event.xcrossing.state);
1187     event.xcrossing.mode = mode;
1188     event.xcrossing.focus = False;
1189     TkInOutEvents(&event, sourcePtr, destPtr, (leaveEvents) ? LeaveNotify : 0,
1190             (enterEvents) ? EnterNotify : 0, TCL_QUEUE_MARK);
1191 }
1192 \f
1193 /*
1194  *----------------------------------------------------------------------
1195  *
1196  * TkGrabDeadWindow --
1197  *
1198  *      This procedure is invoked whenever a window is deleted, so that
1199  *      grab-related cleanup can be performed.
1200  *
1201  * Results:
1202  *      None.
1203  *
1204  * Side effects:
1205  *      Various cleanups happen, such as generating events to move the
1206  *      pointer back to its "natural" window as if an ungrab had been
1207  *      done.  See the code.
1208  *
1209  *----------------------------------------------------------------------
1210  */
1211
1212 void
1213 TkGrabDeadWindow(winPtr)
1214     register TkWindow *winPtr;          /* Window that is in the process
1215                                          * of being deleted. */
1216 {
1217     TkDisplay *dispPtr = winPtr->dispPtr;
1218
1219     if (dispPtr->eventualGrabWinPtr == winPtr) {
1220         /*
1221          * Grab window was deleted.  Release the grab.
1222          */
1223
1224         Tk_Ungrab((Tk_Window) dispPtr->eventualGrabWinPtr);
1225     } else if (dispPtr->buttonWinPtr == winPtr) {
1226         ReleaseButtonGrab(dispPtr);
1227     }
1228     if (dispPtr->serverWinPtr == winPtr) {
1229         if (winPtr->flags & TK_TOP_HIERARCHY) {
1230             dispPtr->serverWinPtr = NULL;
1231         } else {
1232             dispPtr->serverWinPtr = winPtr->parentPtr;
1233         }
1234     }
1235     if (dispPtr->grabWinPtr == winPtr) {
1236         dispPtr->grabWinPtr = NULL;
1237     }
1238 }
1239 \f
1240 /*
1241  *----------------------------------------------------------------------
1242  *
1243  * EatGrabEvents --
1244  *
1245  *      This procedure is called to eliminate any Enter, Leave,
1246  *      FocusIn, or FocusOut events in the event queue for a
1247  *      display that have mode NotifyGrab or NotifyUngrab and
1248  *      have a serial number no less than a given value and are not
1249  *      generated by the grab module.
1250  *
1251  * Results:
1252  *      None.
1253  *
1254  * Side effects:
1255  *      DispPtr's display gets sync-ed, and some of the events get
1256  *      removed from the Tk event queue.
1257  *
1258  *----------------------------------------------------------------------
1259  */
1260
1261 static void
1262 EatGrabEvents(dispPtr, serial)
1263     TkDisplay *dispPtr;         /* Display from which to consume events. */
1264     unsigned int serial;        /* Only discard events that have a serial
1265                                  * number at least this great. */
1266 {
1267     Tk_RestrictProc *oldProc;
1268     GrabInfo info;
1269     ClientData oldArg, dummy;
1270
1271     info.display = dispPtr->display;
1272     info.serial = serial;
1273     TkpSync(info.display);
1274     oldProc = Tk_RestrictEvents(GrabRestrictProc, (ClientData)&info, &oldArg);
1275     while (Tcl_ServiceEvent(TCL_WINDOW_EVENTS)) {
1276     }
1277     Tk_RestrictEvents(oldProc, oldArg, &dummy);
1278 }
1279 \f
1280 /*
1281  *----------------------------------------------------------------------
1282  *
1283  * GrabRestrictProc --
1284  *
1285  *      A Tk_RestrictProc used by EatGrabEvents to eliminate any
1286  *      Enter, Leave, FocusIn, or FocusOut events in the event queue
1287  *      for a display that has mode NotifyGrab or NotifyUngrab and
1288  *      have a serial number no less than a given value.
1289  *
1290  * Results:
1291  *      Returns either TK_DISCARD_EVENT or TK_DEFER_EVENT.
1292  *
1293  * Side effects:
1294  *      None.
1295  *
1296  *----------------------------------------------------------------------
1297  */
1298
1299 static Tk_RestrictAction
1300 GrabRestrictProc(arg, eventPtr)
1301     ClientData arg;
1302     XEvent *eventPtr;
1303 {
1304     GrabInfo *info = (GrabInfo *) arg;
1305     int mode, diff;
1306
1307     /*
1308      * The diff caculation is trickier than it may seem.  Don't forget
1309      * that serial numbers can wrap around, so can't compare the two
1310      * serial numbers directly.
1311      */
1312
1313     diff = eventPtr->xany.serial - info->serial;
1314     if ((eventPtr->type == EnterNotify)
1315             || (eventPtr->type == LeaveNotify)) {
1316         mode = eventPtr->xcrossing.mode;
1317     } else if ((eventPtr->type == FocusIn)
1318             || (eventPtr->type == FocusOut)) {
1319         mode = eventPtr->xfocus.mode;
1320     } else {
1321         mode = NotifyNormal;
1322     }
1323     if ((info->display != eventPtr->xany.display) || (mode == NotifyNormal)
1324             || (diff < 0)) {
1325         return TK_DEFER_EVENT;
1326     } else {
1327         return TK_DISCARD_EVENT;
1328     }
1329 }
1330 \f
1331 /*
1332  *----------------------------------------------------------------------
1333  *
1334  * QueueGrabWindowChange --
1335  *
1336  *      This procedure queues a special event in the Tcl event queue,
1337  *      which will cause the "grabWinPtr" field for the display to get
1338  *      modified when the event is processed.  This is needed to make
1339  *      sure that the grab window changes at the proper time relative
1340  *      to grab-related enter and leave events that are also in the
1341  *      queue.  In particular, this approach works even when multiple
1342  *      grabs and ungrabs happen back-to-back.
1343  *
1344  * Results:
1345  *      None.
1346  *
1347  * Side effects:
1348  *      DispPtr->grabWinPtr will be modified later (by GrabWinEventProc)
1349  *      when the event is removed from the grab event queue.
1350  *
1351  *----------------------------------------------------------------------
1352  */
1353
1354 static void
1355 QueueGrabWindowChange(dispPtr, grabWinPtr)
1356     TkDisplay *dispPtr;         /* Display on which to change the grab
1357                                  * window. */
1358     TkWindow *grabWinPtr;       /* Window that is to become the new grab
1359                                  * window (may be NULL). */
1360 {
1361     NewGrabWinEvent *grabEvPtr;
1362
1363     grabEvPtr = (NewGrabWinEvent *) ckalloc(sizeof(NewGrabWinEvent));
1364     grabEvPtr->header.proc = GrabWinEventProc;
1365     grabEvPtr->dispPtr = dispPtr;
1366     if (grabWinPtr == NULL) {
1367         grabEvPtr->grabWindow = None;
1368     } else {
1369         grabEvPtr->grabWindow = grabWinPtr->window;
1370     }
1371     Tcl_QueueEvent(&grabEvPtr->header, TCL_QUEUE_MARK);
1372     dispPtr->eventualGrabWinPtr = grabWinPtr;
1373 }
1374 \f
1375 /*
1376  *----------------------------------------------------------------------
1377  *
1378  * GrabWinEventProc --
1379  *
1380  *      This procedure is invoked as a handler for Tcl_Events of type
1381  *      NewGrabWinEvent.  It updates the current grab window field in
1382  *      a display.
1383  *
1384  * Results:
1385  *      Returns 1 if the event was processed, 0 if it should be deferred
1386  *      for processing later.
1387  *
1388  * Side effects:
1389  *      The grabWinPtr field is modified in the display associated with
1390  *      the event.
1391  *
1392  *----------------------------------------------------------------------
1393  */
1394
1395 static int
1396 GrabWinEventProc(evPtr, flags)
1397     Tcl_Event *evPtr;           /* Event of type NewGrabWinEvent. */
1398     int flags;                  /* Flags argument to Tk_DoOneEvent: indicates
1399                                  * what kinds of events are being processed
1400                                  * right now. */
1401 {
1402     NewGrabWinEvent *grabEvPtr = (NewGrabWinEvent *) evPtr;
1403
1404     grabEvPtr->dispPtr->grabWinPtr = (TkWindow *) Tk_IdToWindow(
1405             grabEvPtr->dispPtr->display, grabEvPtr->grabWindow);
1406     return 1;
1407 }
1408 \f
1409 /*
1410  *----------------------------------------------------------------------
1411  *
1412  * FindCommonAncestor --
1413  *
1414  *      Given two windows, this procedure finds their least common
1415  *      ancestor and also computes how many levels up this ancestor
1416  *      is from each of the original windows.
1417  *
1418  * Results:
1419  *      If the windows are in different applications or top-level
1420  *      windows, then NULL is returned and *countPtr1 and *countPtr2
1421  *      are set to the depths of the two windows in their respective
1422  *      top-level windows (1 means the window is a top-level, 2 means
1423  *      its parent is a top-level, and so on).  Otherwise, the return
1424  *      value is a pointer to the common ancestor and the counts are
1425  *      set to the distance of winPtr1 and winPtr2 from this ancestor
1426  *      (1 means they're children, 2 means grand-children, etc.).
1427  *
1428  * Side effects:
1429  *      None.
1430  *
1431  *----------------------------------------------------------------------
1432  */
1433
1434 static TkWindow *
1435 FindCommonAncestor(winPtr1, winPtr2, countPtr1, countPtr2)
1436     TkWindow *winPtr1;          /* First window.   May be NULL. */
1437     TkWindow *winPtr2;          /* Second window.  May be NULL. */
1438     int *countPtr1;             /* Store nesting level of winPtr1 within
1439                                  * common ancestor here. */
1440     int *countPtr2;             /* Store nesting level of winPtr2 within
1441                                  * common ancestor here. */
1442 {
1443     register TkWindow *winPtr;
1444     TkWindow *ancestorPtr;
1445     int count1, count2, i;
1446
1447     /*
1448      * Mark winPtr1 and all of its ancestors with a special flag bit.
1449      */
1450
1451     if (winPtr1 != NULL) {
1452         for (winPtr = winPtr1; winPtr != NULL; winPtr = winPtr->parentPtr) {
1453             winPtr->flags |= TK_GRAB_FLAG;
1454             if (winPtr->flags & TK_TOP_HIERARCHY) {
1455                 break;
1456             }
1457         }
1458     }
1459
1460     /*
1461      * Search upwards from winPtr2 until an ancestor of winPtr1 is
1462      * found or a top-level window is reached.
1463      */
1464
1465     winPtr = winPtr2;
1466     count2 = 0;
1467     ancestorPtr = NULL;
1468     if (winPtr2 != NULL) {
1469         for (; winPtr != NULL; count2++, winPtr = winPtr->parentPtr) {
1470             if (winPtr->flags & TK_GRAB_FLAG) {
1471                 ancestorPtr = winPtr;
1472                 break;
1473             }
1474             if (winPtr->flags & TK_TOP_HIERARCHY)  {
1475                 count2++;
1476                 break;
1477             }
1478         }
1479     }
1480
1481     /*
1482      * Search upwards from winPtr1 again, clearing the flag bits and
1483      * remembering how many levels up we had to go.
1484      */
1485
1486     if (winPtr1 == NULL) {
1487         count1 = 0;
1488     } else {
1489         count1 = -1;
1490         for (i = 0, winPtr = winPtr1; winPtr != NULL;
1491                 i++, winPtr = winPtr->parentPtr) {
1492             winPtr->flags &= ~TK_GRAB_FLAG;
1493             if (winPtr == ancestorPtr) {
1494                 count1 = i;
1495             }
1496             if (winPtr->flags & TK_TOP_HIERARCHY) {
1497                 if (count1 == -1) {
1498                     count1 = i+1;
1499                 }
1500                 break;
1501             }
1502         }
1503     }
1504
1505     *countPtr1 = count1;
1506     *countPtr2 = count2;
1507     return ancestorPtr;
1508 }
1509 \f
1510 /*
1511  *----------------------------------------------------------------------
1512  *
1513  * TkPositionInTree --
1514  *
1515  *      Compute where the given window is relative to a particular
1516  *      subtree of the window hierarchy.
1517  *
1518  * Results:
1519  *
1520  *      Returns TK_GRAB_IN_TREE if the window is contained in the
1521  *      subtree.  Returns TK_GRAB_ANCESTOR if the window is an
1522  *      ancestor of the subtree, in the same toplevel.  Otherwise
1523  *      it returns TK_GRAB_EXCLUDED.
1524  *
1525  * Side effects:
1526  *      None.
1527  *
1528  *----------------------------------------------------------------------
1529  */
1530
1531 int
1532 TkPositionInTree(winPtr, treePtr)
1533     TkWindow *winPtr;           /* Window to be checked. */
1534     TkWindow *treePtr;          /* Root of tree to compare against. */
1535 {
1536     TkWindow *winPtr2;
1537
1538     for (winPtr2 = winPtr; winPtr2 != treePtr;
1539            winPtr2 = winPtr2->parentPtr) {
1540         if (winPtr2 == NULL) {
1541             for (winPtr2 = treePtr; winPtr2 != NULL;
1542                     winPtr2 = winPtr2->parentPtr) {
1543                 if (winPtr2 == winPtr) {
1544                     return TK_GRAB_ANCESTOR;
1545                 }
1546                 if (winPtr2->flags & TK_TOP_HIERARCHY) {
1547                     break;
1548                 }
1549             }
1550             return TK_GRAB_EXCLUDED;
1551         }
1552     }
1553     return TK_GRAB_IN_TREE;
1554 }
1555 \f
1556 /*
1557  *----------------------------------------------------------------------
1558  *
1559  * TkGrabState --
1560  *
1561  *      Given a window, this procedure returns a value that indicates
1562  *      the grab state of the application relative to the window.
1563  *
1564  * Results:
1565  *      The return value is one of three things:
1566  *          TK_GRAB_NONE -      no grab is in effect.
1567  *          TK_GRAB_IN_TREE -   there is a grab in effect, and winPtr
1568  *                              is in the grabbed subtree.
1569  *          TK_GRAB_ANCESTOR -  there is a grab in effect;  winPtr is
1570  *                              an ancestor of the grabbed window, in
1571  *                              the same toplevel.
1572  *          TK_GRAB_EXCLUDED -  there is a grab in effect; winPtr is
1573  *                              outside the tree of the grab and is not
1574  *                              an ancestor of the grabbed window in the
1575  *                              same toplevel.
1576  *
1577  * Side effects:
1578  *      None.
1579  *
1580  *----------------------------------------------------------------------
1581  */
1582
1583 int
1584 TkGrabState(winPtr)
1585     TkWindow *winPtr;           /* Window for which grab information is
1586                                  * needed. */
1587 {
1588     TkWindow *grabWinPtr = winPtr->dispPtr->grabWinPtr;
1589
1590     if (grabWinPtr == NULL) {
1591         return TK_GRAB_NONE;
1592     }
1593     if ((winPtr->mainPtr != grabWinPtr->mainPtr)
1594             && !(winPtr->dispPtr->grabFlags & GRAB_GLOBAL)) {
1595         return TK_GRAB_NONE;
1596     }
1597
1598     return TkPositionInTree(winPtr, grabWinPtr);
1599 }