]> git.zerfleddert.de Git - micropolis/blob - src/tk/tkgrab.c
fe5cc4e4fa81d81e6a8a85aac0aeaebe66ccf029
[micropolis] / src / tk / tkgrab.c
1 /*
2 * tkGrab.c --
3 *
4 * This file provides procedures that implement grabs for Tk.
5 *
6 * Copyright 1992 Regents of the University of California.
7 * Permission to use, copy, modify, and distribute this
8 * software and its documentation for any purpose and without
9 * fee is hereby granted, provided that the above copyright
10 * notice appear in all copies. The University of California
11 * makes no representations about the suitability of this
12 * software for any purpose. It is provided "as is" without
13 * express or implied warranty.
14 */
15
16 #ifndef lint
17 static char rcsid[] = "$Header: /user6/ouster/wish/RCS/tkGrab.c,v 1.18 92/08/07 09:55:31 ouster Exp $ SPRITE (Berkeley)";
18 #endif
19
20 #include "tkconfig.h"
21 #include "tkint.h"
22
23 /*
24 *-------------------------------------------------------------------
25 * Problems with current grab implementation (8/7/92):
26 *
27 * 1. In a local grab the synthesized events are always placed at the
28 * front of the event queue. If there are several grabs and ungrabs
29 * in a row, the groups of events for the different grabs/ungrabs
30 * end up in backwards order.
31 * 2. The variables serverWinPtr and pointerWinPtr are hardly used at
32 * all and should probably be eliminated.
33 * 3. The fact that grabWinPtr is set at the time a grab is set or
34 * released, rather than when its events are processed, means that
35 * it can get out of sync with the event queue if there's a rapid
36 * sequence of grabs or ungrabs. The only solution I can think of
37 * is to keep a parallel queue to the event queue to update grabWinPtr
38 * (or, synthesize an event to change the pointer?).
39 *-------------------------------------------------------------------
40 */
41
42 /*
43 * Bit definitions for grabFlags field of TkDisplay structures:
44 *
45 * GRAB_GLOBAL 1 means this is a global grab (we grabbed via
46 * the server so all applications are locked out.
47 * 0 means this is a local grab that affects
48 * only this application.
49 * GRAB_BUTTON_RELEASE 1 means that a button-release event just
50 * occurred and we're in the middle of a sequence
51 * of Enter and Leave events with NotifyUngrab
52 * mode.
53 */
54
55 #define GRAB_GLOBAL 1
56 #define GRAB_BUTTON_RELEASE 2
57
58 /*
59 * Forward declarations for procedures declared later in this file:
60 */
61
62 static void ChangeEventWindow _ANSI_ARGS_((XEvent *eventPtr,
63 TkWindow *winPtr));
64 static void MovePointer _ANSI_ARGS_((XEvent *eventPtr,
65 TkWindow *sourcePtr, TkWindow *destPtr));
66 static void MovePointer2 _ANSI_ARGS_((TkWindow *sourcePtr,
67 TkWindow *destPtr, int mode));
68 \f
69 /*
70 *----------------------------------------------------------------------
71 *
72 * Tk_GrabCmd --
73 *
74 * This procedure is invoked to process the "grab" Tcl command.
75 * See the user documentation for details on what it does.
76 *
77 * Results:
78 * A standard Tcl result.
79 *
80 * Side effects:
81 * See the user documentation.
82 *
83 *----------------------------------------------------------------------
84 */
85
86 /* ARGSUSED */
87 int
88 Tk_GrabCmd(clientData, interp, argc, argv)
89 ClientData clientData; /* Main window associated with
90 * interpreter. */
91 Tcl_Interp *interp; /* Current interpreter. */
92 int argc; /* Number of arguments. */
93 char **argv; /* Argument strings. */
94 {
95 TkWindow *winPtr = (TkWindow *) clientData;
96 int length, lockScreen;
97 char *window;
98
99 if (argc > 3) {
100 badArgs:
101 Tcl_AppendResult(interp, "wrong # args: should be \"",
102 argv[0], " ?-global? ?window?\"", (char *) NULL);
103 return TCL_ERROR;
104 }
105 if (argc == 1) {
106 if ((winPtr->dispPtr->grabWinPtr != NULL)
107 && (winPtr->dispPtr->grabWinPtr->mainPtr
108 == winPtr->mainPtr)) {
109 interp->result = Tk_PathName(winPtr->dispPtr->grabWinPtr);
110 } else {
111 interp->result = "none";
112 }
113 return TCL_OK;
114 }
115 if (argc == 3) {
116 length = strlen(argv[1]);
117 if (strncmp(argv[1], "-off", length) == 0) {
118 lockScreen = -1;
119 } else {
120 if ((strncmp(argv[1], "-global", length) != 0) || (length < 2)) {
121 goto badArgs;
122 }
123 lockScreen = 1;
124 }
125 window = argv[2];
126 } else {
127 lockScreen = 0;
128 window = argv[1];
129 }
130 if ((window[0] == '\0')
131 || (strncmp(window, "none", strlen(window)) == 0)) {
132 Tk_Ungrab((Tk_Window) winPtr);
133 } else {
134 Tk_Window tkwin;
135
136 tkwin = Tk_NameToWindow(interp, window, (Tk_Window) winPtr);
137 if (tkwin == NULL) {
138 return TCL_ERROR;
139 }
140 if (lockScreen < 0) {
141 Tk_Ungrab(tkwin);
142 } else {
143 return Tk_Grab(interp, tkwin, lockScreen);
144 }
145 }
146 return TCL_OK;
147 }
148 \f
149 /*
150 *----------------------------------------------------------------------
151 *
152 * Tk_Grab --
153 *
154 * Grabs the pointer and keyboard, so that mouse-related events are
155 * only reported relative to a given window and its descendants.
156 *
157 * Results:
158 * A standard Tcl result is returned. TCL_OK is the normal return
159 * value; if the grab could not be set then TCL_ERROR is returned
160 * and interp->result will hold an error message.
161 *
162 * Side effects:
163 * Once this call completes successfully, no window outside the
164 * tree rooted at tkwin will receive pointer- or keyboard-related
165 * events until the next call to Tk_Ungrab. If a previous grab was
166 * in effect within this application, then it is replaced with a new
167 * one.
168 *
169 *----------------------------------------------------------------------
170 */
171
172 int
173 Tk_Grab(interp, tkwin, grabGlobal)
174 Tcl_Interp *interp; /* Used for error reporting. */
175 Tk_Window tkwin; /* Window on whose behalf the pointer
176 * is to be grabbed. */
177 int grabGlobal; /* Non-zero means issue a grab to the
178 * server so that no other application
179 * gets mouse or keyboard events.
180 * Zero means the grab only applies
181 * within this application. */
182 {
183 int grabResult;
184 TkWindow *winPtr = (TkWindow *) tkwin;
185 TkDisplay *dispPtr = winPtr->dispPtr;
186 int grabRequest, inSequence, ignoring, numEvents, i, diff;
187 XEvent *events, *eventPtr;
188 TkWindow *winPtr2;
189
190 if (dispPtr->grabWinPtr != NULL) {
191 if ((dispPtr->grabWinPtr == winPtr)
192 && (grabGlobal == ((dispPtr->grabFlags & GRAB_GLOBAL) != 0))) {
193 return TCL_OK;
194 }
195 if (dispPtr->grabWinPtr->mainPtr != winPtr->mainPtr) {
196 alreadyGrabbed:
197 interp->result = "grab failed: another application has grab";
198 return TCL_ERROR;
199 }
200 Tk_Ungrab(tkwin);
201 }
202
203 if (grabGlobal) {
204 grabRequest = NextRequest(dispPtr->display);
205 grabResult = XGrabPointer(dispPtr->display, Tk_WindowId(tkwin),
206 True, ButtonPressMask|ButtonReleaseMask|ButtonMotionMask|PointerMotionMask,
207 GrabModeAsync, GrabModeAsync, None, None,
208 TkCurrentTime(dispPtr));
209 if (grabResult != 0) {
210 grabError:
211 if (grabResult == GrabNotViewable) {
212 interp->result = "grab failed: window not viewable";
213 } else if (grabResult == AlreadyGrabbed) {
214 goto alreadyGrabbed;
215 } else if (grabResult == GrabFrozen) {
216 interp->result = "grab failed: keyboard or pointer frozen";
217 } else if (grabResult == GrabInvalidTime) {
218 interp->result = "grab failed: invalid time";
219 } else {
220 char msg[100];
221
222 sprintf(msg, "grab failed for unknown reason (code %d)",
223 grabResult);
224 Tcl_AppendResult(interp, msg, (char *) NULL);
225 }
226 return TCL_ERROR;
227 }
228 grabResult = XGrabKeyboard(dispPtr->display, Tk_WindowId(tkwin),
229 False, GrabModeAsync, GrabModeAsync, TkCurrentTime(dispPtr));
230 if (grabResult != 0) {
231 XUngrabPointer(dispPtr->display, TkCurrentTime(dispPtr));
232 goto grabError;
233 }
234 dispPtr->grabFlags |= GRAB_GLOBAL;
235 } else {
236 /*
237 * The call to XUngrabPointer below is needed to release any
238 * existing auto-grab due to a button press. This is needed
239 * so that local grabs behave the same as global grabs (the
240 * button grab is released by the X server in a global grab).
241 */
242
243 XUngrabPointer(dispPtr->display, TkCurrentTime(dispPtr));
244 grabRequest = LastKnownRequestProcessed(dispPtr->display);
245 dispPtr->grabFlags &= ~GRAB_GLOBAL;
246
247 /*
248 * Since we're not telling the server about the grab, we have
249 * to generate Leave and Enter events to move the pointer from
250 * its current window to the grab window.
251 */
252
253 MovePointer2(dispPtr->pointerWinPtr, winPtr, NotifyGrab);
254 }
255 dispPtr->grabWinPtr = winPtr;
256
257 /*
258 * When a grab occurs, X generates Enter and Leave events to move
259 * the pointer from its current window to the grab window, even if
260 * the current window is in the grab tree. We don't want these
261 * events getting through to the application if the current window
262 * is in the grab tree. In order to eliminate the bogus events,
263 * process all pending events and filter out the bogus ones.
264 *
265 * Also, filter out the final enter event into the grab window in
266 * any case: this event shouldn't be delivered until the mouse really
267 * moves into that window.
268 *
269 * The code below reads in all the pending events, filters out the bad
270 * ones, and then pushes back all the events that weren't filtered.
271 * Another alternative would be to simply process the events
272 * immediately rather than pushing them back again. However, this
273 * tends to interfere with scripts since it causes pending events
274 * to be processed during the "grab" command. The "grab" command
275 * might have been invoked in the middle of some computation where
276 * it's a bad idea to process new events.
277 */
278
279 XSync(dispPtr->display, False);
280 numEvents = QLength(dispPtr->display);
281 if (numEvents == 0) {
282 return TCL_OK;
283 }
284 events = (XEvent *) ckalloc((unsigned) (numEvents * sizeof(XEvent)));
285 for (i = 0; i < numEvents; i++) {
286 XNextEvent(dispPtr->display, &events[i]);
287 }
288 inSequence = ignoring = 0;
289 for (i = numEvents-1, eventPtr = events; i >= 0; i--, eventPtr++) {
290 if (((eventPtr->type != EnterNotify)
291 && (eventPtr->type != LeaveNotify))
292 || (eventPtr->xcrossing.mode != NotifyGrab)) {
293 continue;
294 }
295
296 /*
297 * The diff caculcation below is trickier than you might think,
298 * due to the fact that the event serial number is unsigned and
299 * serial numbers can wrap around.
300 */
301
302 diff = eventPtr->xcrossing.serial;
303 diff -= grabRequest;
304 if (!inSequence && (diff >= 0)) {
305 /*
306 * This is the first event of the grab sequence. See if its
307 * window is in the grab tree and ignore the sequence if it is.
308 */
309
310 inSequence = 1;
311 if (XFindContext(dispPtr->display, eventPtr->xcrossing.window,
312 tkWindowContext, (void *) &winPtr2) == 0) {
313 for ( ; winPtr2 != NULL; winPtr2 = winPtr2->parentPtr) {
314 if (winPtr2 == dispPtr->grabWinPtr) {
315 ignoring = 1;
316 break;
317 }
318 }
319 }
320 }
321 if (ignoring) {
322 eventPtr->type = 0;
323 }
324 if (inSequence && (eventPtr->type == EnterNotify)
325 && (dispPtr->grabWinPtr->window
326 == eventPtr->xcrossing.window)) {
327 eventPtr->type = 0;
328 break;
329 }
330 }
331 for (i = numEvents-1, eventPtr = &events[i]; i >= 0; i--, eventPtr--) {
332 if (eventPtr->type != 0) {
333 XPutBackEvent(dispPtr->display, eventPtr);
334 }
335 }
336 ckfree((char *) events);
337 return TCL_OK;
338 }
339 \f
340 /*
341 *----------------------------------------------------------------------
342 *
343 * Tk_Ungrab --
344 *
345 * Releases a grab on the mouse pointer and keyboard.
346 *
347 * Results:
348 * None.
349 *
350 * Side effects:
351 * Pointer and keyboard events will start being delivered to other
352 * windows again.
353 *
354 *----------------------------------------------------------------------
355 */
356
357 void
358 Tk_Ungrab(tkwin)
359 Tk_Window tkwin; /* Window that identifies display
360 * for grab to be released. */
361 {
362 TkDisplay *dispPtr = ((TkWindow *) tkwin)->dispPtr;
363 int inSequence, ignoring, ungrabRequest, numEvents, i, j, diff;
364 TkWindow *grabWinPtr, *winPtr;
365 XEvent *events, *eventPtr, *eventPtr2;
366
367 grabWinPtr = dispPtr->grabWinPtr;
368 if (grabWinPtr == NULL) {
369 return;
370 }
371 dispPtr->grabWinPtr = NULL;
372 dispPtr->buttonWinPtr = NULL;
373 if (dispPtr->grabFlags & GRAB_GLOBAL) {
374 ungrabRequest = NextRequest(dispPtr->display);
375 XUngrabPointer(dispPtr->display, TkCurrentTime(dispPtr));
376 XUngrabKeyboard(dispPtr->display, TkCurrentTime(dispPtr));
377 XSync(dispPtr->display, False);
378 } else {
379 ungrabRequest = LastKnownRequestProcessed(dispPtr->display);
380 if ((dispPtr->ungrabWinPtr != NULL)
381 && (dispPtr->ungrabWinPtr->mainPtr != grabWinPtr->mainPtr)) {
382
383 /*
384 * Don't report entries down into a window of a different
385 * application, since it's already seen those entries earlier.
386 */
387
388 dispPtr->ungrabWinPtr = NULL;
389 }
390 MovePointer2(grabWinPtr, dispPtr->ungrabWinPtr, NotifyUngrab);
391 }
392
393 /*
394 * We have to filter all the pending events in a fashion similar to
395 * Tk_Grab. As with grabs, the X server generates an Enter-Leave event
396 * sequence to move the pointer from the grab window back to its
397 * current window. We need to ignore this sequence if the pointer
398 * is being moved to a window that's already in the grab tree.
399 */
400
401 numEvents = QLength(dispPtr->display);
402 if (numEvents == 0) {
403 return;
404 }
405 events = (XEvent *) ckalloc((unsigned) (numEvents * sizeof(XEvent)));
406 for (i = 0; i < numEvents; i++) {
407 XNextEvent(dispPtr->display, &events[i]);
408 }
409 inSequence = ignoring = 0;
410 for (i = numEvents-1, eventPtr = events; i >= 0; i--, eventPtr++) {
411 if (((eventPtr->type != EnterNotify)
412 && (eventPtr->type != LeaveNotify))
413 || (eventPtr->xcrossing.mode != NotifyUngrab)) {
414 continue;
415 }
416 diff = eventPtr->xcrossing.serial;
417 diff -= ungrabRequest;
418 if (!inSequence && (diff >= 0)) {
419
420 /*
421 * This is the first event of the ungrab sequence. Scan forward
422 * looking for the final Enter event in the sequence. Then see
423 * if that event's window is in the grab tree.
424 */
425
426 inSequence = 1;
427 for (j = i, eventPtr2 = eventPtr; j >= 0; j--, eventPtr2++) {
428 if (eventPtr2->type == EnterNotify) {
429 if (eventPtr2->xcrossing.mode != NotifyUngrab) {
430 break;
431 }
432 if ((eventPtr2->xcrossing.detail != NotifyAncestor)
433 && (eventPtr2->xcrossing.detail != NotifyInferior)
434 && (eventPtr2->xcrossing.detail
435 != NotifyNonlinear)) {
436 continue;
437 }
438 if (XFindContext(dispPtr->display,
439 eventPtr2->xcrossing.window,
440 tkWindowContext, (void *) &winPtr) == 0) {
441 for ( ; winPtr != NULL; winPtr = winPtr->parentPtr) {
442 if (winPtr == grabWinPtr) {
443 ignoring = 1;
444 break;
445 }
446 }
447 }
448 break;
449 } else if ((eventPtr2->type != LeaveNotify)
450 || (eventPtr2->xcrossing.mode != NotifyUngrab)) {
451 break;
452 }
453 }
454 }
455 if (ignoring) {
456 eventPtr->type = 0;
457 }
458 }
459 for (i = numEvents-1, eventPtr = &events[i]; i >= 0; i--, eventPtr--) {
460 if (eventPtr->type != 0) {
461 XPutBackEvent(dispPtr->display, eventPtr);
462 }
463 }
464 ckfree((char *) events);
465 }
466 \f
467 /*
468 *----------------------------------------------------------------------
469 *
470 * TkPointerEvent --
471 *
472 * This procedure is called for each pointer-related event, before
473 * the event has been processed. It does various things to make
474 * grabs work correctly.
475 *
476 * Results:
477 * If the return value is 1 it means the event should be processed
478 * (event handlers should be invoked). If the return value is 0
479 * it means the event should be ignored in order to make grabs
480 * work correctly. Note: the event may be modified by this procedure.
481 *
482 * Side effects:
483 * Grab state information may be updated.
484 *
485 *----------------------------------------------------------------------
486 */
487
488 int
489 TkPointerEvent(eventPtr, winPtr)
490 register XEvent *eventPtr; /* Pointer to the event. */
491 TkWindow *winPtr; /* Tk's information for window
492 * where event was reported. */
493 {
494 register TkWindow *winPtr2;
495 TkDisplay *dispPtr = winPtr->dispPtr;
496 int outsideGrabTree = 0;
497 int originalFlags;
498 int appGrabbed = 0; /* Non-zero means event is being
499 * reported to an application that is
500 * affected by the grab. */
501 #define ALL_BUTTONS \
502 (Button1Mask|Button2Mask|Button3Mask|Button4Mask|Button5Mask)
503 static unsigned int state[] = {
504 Button1Mask, Button2Mask, Button3Mask, Button4Mask, Button5Mask
505 };
506
507 /*
508 * Don't do any filtering on events generated by the event-sharing code.
509 */
510
511 if (eventPtr == tkShareEventPtr) {
512 return 1;
513 }
514
515 /*
516 * If a grab is in effect, see if the event is being reported to
517 * a window in the grab tree. Also see if the event is being reported
518 * to an application that is affected by the grab.
519 */
520
521 if (dispPtr->grabWinPtr != NULL) {
522 if ((winPtr->mainPtr == dispPtr->grabWinPtr->mainPtr)
523 || (dispPtr->grabFlags & GRAB_GLOBAL)) {
524 appGrabbed = 1;
525 }
526 for (winPtr2 = winPtr; winPtr2 != dispPtr->grabWinPtr;
527 winPtr2 = winPtr2->parentPtr) {
528 if (winPtr2 == NULL) {
529 outsideGrabTree = 1;
530 break;
531 }
532 }
533 }
534
535 originalFlags = dispPtr->grabFlags;
536 dispPtr->grabFlags &= ~GRAB_BUTTON_RELEASE;
537 if ((eventPtr->type == EnterNotify) || (eventPtr->type == LeaveNotify)) {
538 if ((eventPtr->type == EnterNotify)
539 && (eventPtr->xcrossing.detail != NotifyVirtual)
540 && (eventPtr->xcrossing.detail != NotifyNonlinearVirtual)) {
541 if ((dispPtr->grabWinPtr == NULL)
542 || (dispPtr->grabWinPtr->mainPtr == winPtr->mainPtr)) {
543 dispPtr->ungrabWinPtr = winPtr;
544 }
545 dispPtr->serverWinPtr = winPtr;
546 } else {
547 dispPtr->serverWinPtr = NULL;
548 }
549 if (dispPtr->grabWinPtr != NULL) {
550 if (eventPtr->xcrossing.mode == NotifyNormal) {
551 /*
552 * When a grab is active, X continues to report enter and
553 * leave events for windows outside the tree of the grab
554 * window. Detect these events and ignore them.
555 */
556
557 if (outsideGrabTree && appGrabbed) {
558 return 0;
559 }
560
561 /*
562 * Make buttons have the same grab-like behavior inside a grab
563 * as they do outside a grab: do this by ignoring enter and
564 * leave events except for the window in which the button was
565 * pressed.
566 */
567
568 if ((dispPtr->buttonWinPtr != NULL)
569 && (winPtr != dispPtr->buttonWinPtr)) {
570 return 0;
571 }
572 } else if (eventPtr->xcrossing.mode == NotifyUngrab) {
573 /*
574 * Keep the GRAB_BUTTON_RELEASE flag on if it used to be on.
575 */
576
577 dispPtr->grabFlags = originalFlags;
578 if (outsideGrabTree && appGrabbed
579 && (dispPtr->grabFlags & GRAB_BUTTON_RELEASE)) {
580 /*
581 * The only way we get here is if a button was pressed,
582 * then moved to a different window and released. Enter
583 * and leave events were deferred while the button was
584 * down, but now we're getting them to move the pointer
585 * back to the right window, and this particular event
586 * is for a window outside the grab tree. Ignore it.
587 */
588
589 return 0;
590 }
591 }
592 }
593
594 /*
595 * Keep track of the window containing the mouse, in order to
596 * detect various bogus event sequences.
597 */
598
599 dispPtr->pointerWinPtr = dispPtr->serverWinPtr;
600 return 1;
601 }
602 if ((dispPtr->grabWinPtr == NULL) || !appGrabbed) {
603 return 1;
604 }
605
606 if (eventPtr->type == MotionNotify) {
607 /*
608 * When grabs are active, X reports motion events relative to the
609 * window under the pointer. Instead, it should report the events
610 * relative to the window the button went down in, if there is a
611 * button down. Otherwise, if the pointer window is outside the
612 * subtree of the grab window, the events should be reported
613 * relative to the grab window. Otherwise, the event should be
614 * reported to the pointer window.
615 */
616
617 winPtr2 = winPtr;
618 if (dispPtr->buttonWinPtr != NULL) {
619 winPtr2 = dispPtr->buttonWinPtr;
620 } else if (outsideGrabTree || (dispPtr->serverWinPtr == NULL)) {
621 winPtr2 = dispPtr->grabWinPtr;
622 }
623 if (winPtr2 != winPtr) {
624 XEvent newEvent;
625
626 newEvent = *eventPtr;
627 ChangeEventWindow(&newEvent, winPtr2);
628 XPutBackEvent(winPtr2->display, &newEvent);
629 return 0;
630 }
631 return 1;
632 }
633
634 /*
635 * Process ButtonPress and ButtonRelease events:
636 * 1. Keep track of whether a button is down and what window it
637 * went down in.
638 * 2. If the first button goes down outside the grab tree, pretend
639 * it went down in the grab window. Note: it's important to
640 * redirect events to the grab window like this in order to make
641 * things like menus work, where button presses outside the
642 * grabbed menu need to be seen. An application can always
643 * ignore the events if they occur outside its window.
644 * 3. If a button press or release occurs outside the window where
645 * the first button was pressed, retarget the event so it's reported
646 * to the window where the first button was pressed.
647 * 4. If the last button is released in a window different than where
648 * the first button was pressed, generate Enter/Leave events to
649 * move the mouse from the button window to its current window.
650 * 5. If the grab is set at a time when a button is already down, or
651 * if the window where the button was pressed was deleted, then
652 * dispPtr->buttonWinPtr will stay NULL. Just forget about the
653 * auto-grab for the button press; events will go to whatever
654 * window contains the pointer. If this window isn't in the grab
655 * tree then redirect events to the grab window.
656 */
657
658 if ((eventPtr->type == ButtonPress) || (eventPtr->type == ButtonRelease)) {
659 winPtr2 = dispPtr->buttonWinPtr;
660 if (winPtr2 == NULL) {
661 if (outsideGrabTree) {
662 winPtr2 = dispPtr->grabWinPtr; /* Note 5. */
663 } else {
664 winPtr2 = winPtr; /* Note 5. */
665 }
666 }
667 if (eventPtr->type == ButtonPress) {
668 if ((eventPtr->xbutton.state & ALL_BUTTONS) == 0) {
669 if (outsideGrabTree) {
670 XEvent newEvent;
671
672 newEvent = *eventPtr;
673 ChangeEventWindow(&newEvent, dispPtr->grabWinPtr);
674 XPutBackEvent(dispPtr->display, &newEvent);
675 return 0; /* Note 2. */
676 }
677 dispPtr->buttonWinPtr = winPtr;
678 return 1;
679 }
680 } else {
681 if ((eventPtr->xbutton.state & ALL_BUTTONS)
682 == state[eventPtr->xbutton.button - Button1]) {
683 if ((dispPtr->buttonWinPtr != winPtr)
684 && (dispPtr->buttonWinPtr != NULL)) {
685 XEvent newEvent; /* Note 4. */
686
687 /*
688 * If the button release is made with pointer outside
689 * all applications, X reports it relative to the grab
690 * window. Change the current window to NULL to
691 * reflect that the pointer's outside everything. Do
692 * the same if the pointer's in a window that's not
693 * part of the grab tree.
694 */
695
696 if (outsideGrabTree || (dispPtr->serverWinPtr == NULL)) {
697 winPtr = NULL;
698 }
699 newEvent = *eventPtr;
700 newEvent.xcrossing.mode = NotifyUngrab;
701 newEvent.xcrossing.focus = False;
702 newEvent.xcrossing.state =
703 eventPtr->xbutton.state & ~ALL_BUTTONS;
704 MovePointer(&newEvent, dispPtr->buttonWinPtr, winPtr);
705 }
706 dispPtr->buttonWinPtr = NULL;
707 dispPtr->grabFlags |= GRAB_BUTTON_RELEASE;
708 }
709 }
710 if (winPtr2 != winPtr) {
711 XEvent newEvent;
712
713 newEvent = *eventPtr;
714 ChangeEventWindow(&newEvent, winPtr2);
715 XPutBackEvent(dispPtr->display, &newEvent);
716 return 0; /* Note 3. */
717 }
718 }
719
720 return 1;
721 }
722 \f
723 /*
724 *----------------------------------------------------------------------
725 *
726 * ChangeEventWindow --
727 *
728 * Given an event and a new window to which the event should be
729 * retargeted, modify fields of the event so that the event is
730 * properly retargeted to the new window.
731 *
732 * Results:
733 * The following fields of eventPtr are modified: window,
734 * subwindow, x, y, same_screen.
735 *
736 * Side effects:
737 * None.
738 *
739 *----------------------------------------------------------------------
740 */
741
742 static void
743 ChangeEventWindow(eventPtr, winPtr)
744 register XEvent *eventPtr; /* Event to retarget. Must have
745 * type ButtonPress, ButtonRelease, KeyPress,
746 * KeyRelease, MotionNotify, EnterNotify,
747 * or LeaveNotify. */
748 TkWindow *winPtr; /* New target window for event. */
749 {
750 int x, y, sameScreen, bd;
751 register TkWindow *childPtr;
752
753 eventPtr->xmotion.window = Tk_WindowId(winPtr);
754 if (eventPtr->xmotion.root ==
755 RootWindow(winPtr->display, winPtr->screenNum)) {
756 Tk_GetRootCoords((Tk_Window) winPtr, &x, &y);
757 eventPtr->xmotion.x = eventPtr->xmotion.x_root - x;
758 eventPtr->xmotion.y = eventPtr->xmotion.y_root - y;
759 eventPtr->xmotion.subwindow = None;
760 for (childPtr = winPtr->childList; childPtr != NULL;
761 childPtr = childPtr->nextPtr) {
762 if (childPtr->flags & TK_TOP_LEVEL) {
763 continue;
764 }
765 x = eventPtr->xmotion.x - childPtr->changes.x;
766 y = eventPtr->xmotion.y - childPtr->changes.y;
767 bd = childPtr->changes.border_width;
768 if ((x >= -bd) && (y >= -bd)
769 && (x < (childPtr->changes.width + bd))
770 && (y < (childPtr->changes.width + bd))) {
771 eventPtr->xmotion.subwindow = childPtr->window;
772 }
773 }
774 sameScreen = 1;
775 } else {
776 eventPtr->xmotion.x = 0;
777 eventPtr->xmotion.y = 0;
778 eventPtr->xmotion.subwindow = None;
779 sameScreen = 0;
780 }
781 if (eventPtr->type == MotionNotify) {
782 eventPtr->xmotion.same_screen = sameScreen;
783 } else {
784 eventPtr->xbutton.same_screen = sameScreen;
785 }
786 }
787 \f
788 /*
789 *----------------------------------------------------------------------
790 *
791 * MovePointer --
792 *
793 * This procedure synthesizes EnterNotify and LeaveNotify events
794 * to correctly transfer the pointer from one window to another.
795 *
796 * Results:
797 * None.
798 *
799 * Side effects:
800 * Synthesized events may be pushed back onto the event queue.
801 * The event pointed to by eventPtr is modified.
802 *
803 *----------------------------------------------------------------------
804 */
805
806 static void
807 MovePointer(eventPtr, sourcePtr, destPtr)
808 XEvent *eventPtr; /* A template X event. Must have all fields
809 * properly set for EnterNotify and LeaveNotify
810 * events except window, subwindow, x, y,
811 * detail, and same_screen. (x_root and y_root
812 * must be valid, even though x and y needn't
813 * be valid). */
814 TkWindow *sourcePtr; /* Window currently containing pointer (NULL
815 * means it's not one managed by this
816 * process). */
817 TkWindow *destPtr; /* Window that is to end up containing the
818 * pointer (NULL means it's not one managed
819 * by this process). */
820 {
821 TkDisplay *dispPtr;
822 register TkWindow *ancestorPtr; /* Lowest ancestor shared between
823 * sourcePtr and destPtr, or
824 * sourcePtr's top-level window if no
825 * shared ancestor. */
826 register TkWindow *winPtr;
827 int upLevels, downLevels, i, j;
828
829 /*
830 * There are four possible cases to deal with:
831 *
832 * 1. SourcePtr and destPtr are the same. There's nothing to do in
833 * this case.
834 * 2. SourcePtr is an ancestor of destPtr in the same top-level
835 * window. Must generate events down the window tree from source
836 * to dest.
837 * 3. DestPtr is an ancestor of sourcePtr in the same top-level
838 * window. Must generate events up the window tree from sourcePtr
839 * to destPtr.
840 * 4. All other cases. Must first generate events up the window tree
841 * from sourcePtr to its top-level, then down from destPtr's
842 * top-level to destPtr. This form is called "non-linear."
843 *
844 * The code below separates these four cases and decides how many levels
845 * up and down events have to be generated for.
846 */
847
848 if (sourcePtr == destPtr) {
849 return;
850 }
851
852 /*
853 * Mark destPtr and all of its ancestors with a special flag bit.
854 */
855
856 if (destPtr != NULL) {
857 dispPtr = destPtr->dispPtr;
858 for (winPtr = destPtr; ; winPtr = winPtr->parentPtr) {
859 winPtr->flags |= TK_GRAB_FLAG;
860 if (winPtr->flags & TK_TOP_LEVEL) {
861 break;
862 }
863 }
864 } else {
865 dispPtr = sourcePtr->dispPtr;
866 }
867
868 /*
869 * Search upwards from sourcePtr until an ancestor of destPtr is
870 * found or a top-level window is reached. Remember if we pass out
871 * of the grab tree along the way, since this means we'll have to
872 * skip some of the events that would otherwise be generated.
873 */
874
875 ancestorPtr = sourcePtr;
876 upLevels = 0;
877 if (sourcePtr != NULL) {
878 for (; ; upLevels++, ancestorPtr = ancestorPtr->parentPtr) {
879 if (ancestorPtr->flags & TK_GRAB_FLAG) {
880 break;
881 }
882 if (ancestorPtr->flags & TK_TOP_LEVEL) {
883 upLevels++;
884 break;
885 }
886 }
887 }
888
889 /*
890 * Search upwards from destPtr again, clearing the flag bits and
891 * remembering how many levels up we had to go.
892 */
893
894 if (destPtr == NULL) {
895 downLevels = 0;
896 } else {
897 downLevels = -1;
898 for (i = 0, winPtr = destPtr; ; i++, winPtr = winPtr->parentPtr) {
899 winPtr->flags &= ~TK_GRAB_FLAG;
900 if (winPtr == ancestorPtr) {
901 downLevels = i;
902 }
903 if (winPtr->flags & TK_TOP_LEVEL) {
904 if (downLevels == -1) {
905 downLevels = i+1;
906 }
907 break;
908 }
909 }
910 }
911
912 /*
913 * Generate enter/leave events and push them back onto the event
914 * queue. This has to be done backwards, since the last event
915 * pushed will be the first one processed.
916 */
917
918 #define PUSH_EVENT(w, t, d) \
919 if (w->window != None) { \
920 eventPtr->type = t; \
921 eventPtr->xcrossing.detail = d; \
922 ChangeEventWindow(eventPtr, w); \
923 XPutBackEvent(w->display, eventPtr); \
924 }
925
926 if (downLevels == 0) {
927
928 /*
929 * SourcePtr is an inferior of destPtr.
930 */
931
932 if (destPtr != NULL) {
933 PUSH_EVENT(destPtr, EnterNotify, NotifyInferior);
934 }
935 for (i = upLevels-1; i > 0; i--) {
936 for (winPtr = sourcePtr, j = 0; j < i;
937 winPtr = winPtr->parentPtr, j++) {
938 if (winPtr == dispPtr->grabWinPtr) {
939 goto nextIteration;
940 }
941 }
942 PUSH_EVENT(winPtr, LeaveNotify, NotifyVirtual);
943 nextIteration: continue;
944 }
945 PUSH_EVENT(sourcePtr, LeaveNotify, NotifyAncestor);
946 } else if (upLevels == 0) {
947
948 /*
949 * DestPtr is an inferior of sourcePtr.
950 */
951
952 if (destPtr != NULL) {
953 PUSH_EVENT(destPtr, EnterNotify, NotifyAncestor);
954 }
955 for (winPtr = destPtr->parentPtr, i = downLevels-1; i > 0;
956 winPtr = winPtr->parentPtr, i--) {
957 PUSH_EVENT(winPtr, EnterNotify, NotifyVirtual);
958 }
959 if (sourcePtr != NULL) {
960 PUSH_EVENT(sourcePtr, LeaveNotify, NotifyInferior);
961 }
962 } else {
963
964 /*
965 * Non-linear: neither window is an inferior of the other.
966 */
967
968 if (destPtr != NULL) {
969 PUSH_EVENT(destPtr, EnterNotify, NotifyNonlinear);
970 }
971 if (destPtr != dispPtr->grabWinPtr) {
972 for (winPtr = destPtr->parentPtr, i = downLevels-1; i > 0;
973 winPtr = winPtr->parentPtr, i--) {
974 PUSH_EVENT(winPtr, EnterNotify, NotifyNonlinearVirtual);
975 if (winPtr == dispPtr->grabWinPtr) {
976 break;
977 }
978 }
979 }
980 for (i = upLevels-1; i > 0; i--) {
981 for (winPtr = sourcePtr, j = 0; j < i;
982 winPtr = winPtr->parentPtr, j++) {
983 if (winPtr == dispPtr->grabWinPtr) {
984 goto nextWindow;
985 }
986 }
987 PUSH_EVENT(winPtr, LeaveNotify, NotifyNonlinearVirtual);
988 nextWindow: continue;
989 }
990 PUSH_EVENT(sourcePtr, LeaveNotify, NotifyNonlinear);
991 }
992 }
993 \f
994 /*
995 *----------------------------------------------------------------------
996 *
997 * MovePointer2 --
998 *
999 * This procedure synthesizes EnterNotify and LeaveNotify events
1000 * to correctly transfer the pointer from one window to another.
1001 * It is different from MovePointer in that no template X event
1002 * needs to be supplied; this procedure generates the template
1003 * event and calls MovePointer.
1004 *
1005 * Results:
1006 * None.
1007 *
1008 * Side effects:
1009 * Synthesized events may be pushed back onto the event queue.
1010 *
1011 *----------------------------------------------------------------------
1012 */
1013
1014 static void
1015 MovePointer2(sourcePtr, destPtr, mode)
1016 TkWindow *sourcePtr; /* Window currently containing pointer (NULL
1017 * means it's not one managed by this
1018 * process). */
1019 TkWindow *destPtr; /* Window that is to end up containing the
1020 * pointer (NULL means it's not one managed
1021 * by this process). */
1022 int mode; /* Mode for enter/leave events, such as
1023 * NotifyNormal or NotifyUngrab. */
1024 {
1025 XEvent event;
1026 Window dummy1, dummy2;
1027 int dummy3, dummy4;
1028 TkWindow *winPtr;
1029
1030 winPtr = sourcePtr;
1031 if ((winPtr == NULL) || (winPtr->window == None)) {
1032 winPtr = destPtr;
1033 if ((winPtr == NULL) || (winPtr->window == None)) {
1034 return;
1035 }
1036 }
1037
1038 event.xcrossing.serial = LastKnownRequestProcessed(winPtr->display);
1039 event.xcrossing.send_event = False;
1040 event.xcrossing.display = winPtr->display;
1041 event.xcrossing.root = RootWindow(winPtr->display, winPtr->screenNum);
1042 event.xcrossing.time = TkCurrentTime(winPtr->dispPtr);
1043 XQueryPointer(winPtr->display, winPtr->window, &dummy1, &dummy2,
1044 &event.xcrossing.x_root, &event.xcrossing.y_root,
1045 &dummy3, &dummy4, &event.xcrossing.state);
1046 event.xcrossing.mode = mode;
1047 event.xcrossing.focus = False;
1048 MovePointer(&event, sourcePtr, destPtr);
1049 }
1050 \f
1051 /*
1052 *----------------------------------------------------------------------
1053 *
1054 * TkGrabDeadWindow --
1055 *
1056 * This procedure is invoked whenever a window is deleted, so that
1057 * grab-related cleanup can be performed.
1058 *
1059 * Results:
1060 * None.
1061 *
1062 * Side effects:
1063 * Various cleanups happen, such as generating events to move the
1064 * pointer back to its "natural" window as if an ungrab had been
1065 * done. See the code.
1066 *
1067 *----------------------------------------------------------------------
1068 */
1069
1070 void
1071 TkGrabDeadWindow(winPtr)
1072 register TkWindow *winPtr; /* Window that is in the process
1073 * of being deleted. */
1074 {
1075 TkDisplay *dispPtr = winPtr->dispPtr;
1076
1077 if (dispPtr->grabWinPtr == winPtr) {
1078 dispPtr->grabWinPtr = NULL;
1079 if (!(dispPtr->grabFlags & GRAB_GLOBAL)) {
1080 /*
1081 * Must generate enter/leave events to move back to the window
1082 * that contains the mouse pointer. We needn't filter events
1083 * here like we do in Tk_Ungrab because there are no children
1084 * of the grab window left in existence.
1085 */
1086
1087 movePointerBack:
1088 if ((dispPtr->ungrabWinPtr != NULL)
1089 && (dispPtr->ungrabWinPtr->mainPtr != winPtr->mainPtr)) {
1090 dispPtr->ungrabWinPtr = NULL;
1091 }
1092 MovePointer2(winPtr, dispPtr->ungrabWinPtr, NotifyUngrab);
1093 }
1094 } else if (dispPtr->buttonWinPtr == winPtr) {
1095 /*
1096 * The window in which a button was pressed was deleted. Simulate
1097 * dropping the button auto-grab by generating Enter and Leave
1098 * events to move the pointer back to the window it's really on
1099 * top of.
1100 */
1101
1102 dispPtr->buttonWinPtr = NULL;
1103 goto movePointerBack;
1104 }
1105 if (dispPtr->ungrabWinPtr == winPtr) {
1106 dispPtr->ungrabWinPtr = NULL;
1107 }
1108 if (dispPtr->pointerWinPtr == winPtr) {
1109 dispPtr->pointerWinPtr = NULL;
1110 }
1111 if (dispPtr->serverWinPtr == winPtr) {
1112 dispPtr->serverWinPtr = NULL;
1113 }
1114 }
Impressum, Datenschutz