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: tkGrab.c,v 1.9 2002/08/31 06:12:20 das Exp $ 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 94typedef 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 123typedef 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) 149static unsigned int buttonStates[] = { 150 Button1Mask, Button2Mask, Button3Mask, Button4Mask, Button5Mask 151}; 152 153/* 154 * Forward declarations for procedures declared later in this file: 155 */ 156 157static void EatGrabEvents _ANSI_ARGS_((TkDisplay *dispPtr, 158 unsigned int serial)); 159static TkWindow * FindCommonAncestor _ANSI_ARGS_((TkWindow *winPtr1, 160 TkWindow *winPtr2, int *countPtr1, 161 int *countPtr2)); 162static Tk_RestrictAction GrabRestrictProc _ANSI_ARGS_((ClientData arg, 163 XEvent *eventPtr)); 164static int GrabWinEventProc _ANSI_ARGS_((Tcl_Event *evPtr, 165 int flags)); 166static void MovePointer2 _ANSI_ARGS_((TkWindow *sourcePtr, 167 TkWindow *destPtr, int mode, int leaveEvents, 168 int EnterEvents)); 169static void QueueGrabWindowChange _ANSI_ARGS_((TkDisplay *dispPtr, 170 TkWindow *grabWinPtr)); 171static void ReleaseButtonGrab _ANSI_ARGS_((TkDisplay *dispPtr)); 172 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 */ 191int 192Tk_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 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 404int 405Tk_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 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 581void 582Tk_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 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 655static void 656ReleaseButtonGrab(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 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 701int 702TkPointerEvent(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 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 924void 925TkChangeEventWindow(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 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 990void 991TkInOutEvents(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 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 1147static void 1148MovePointer2(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 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 1212void 1213TkGrabDeadWindow(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 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 1261static void 1262EatGrabEvents(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 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 1299static Tk_RestrictAction 1300GrabRestrictProc(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 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 1354static void 1355QueueGrabWindowChange(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 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 1395static int 1396GrabWinEventProc(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 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 1434static TkWindow * 1435FindCommonAncestor(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 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 1531int 1532TkPositionInTree(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 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 1583int 1584TkGrabState(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} 1600