]>
Commit | Line | Data |
---|---|---|
6a5fa4e0 MG |
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 | } |