1 /*
2 * tkPointer.c --
3 *
4 * This file contains functions for emulating the X server pointer and
5 * grab state machine. This file is used by the Mac and Windows platforms
6 * to generate appropriate enter/leave events, and to update the global
7 * grab window information.
8 *
9 * Copyright © 1996 Sun Microsystems, Inc.
10 *
11 * See the file "license.terms" for information on usage and redistribution of
12 * this file, and for a DISCLAIMER OF ALL WARRANTIES.
13 */
14
15 #include "tkInt.h"
16
17 #ifdef _WIN32
18 #include "tkWinInt.h"
19 #endif
20
21 #if defined(MAC_OSX_TK)
22 #include "tkMacOSXInt.h"
23 #endif
24
25 typedef struct {
26 TkWindow *grabWinPtr; /* Window that defines the top of the grab
27 * tree in a global grab. */
28 unsigned lastState; /* Last known state flags. */
29 XPoint lastPos; /* Last reported mouse position. */
30 TkWindow *lastWinPtr; /* Last reported mouse window. */
31 TkWindow *restrictWinPtr; /* Window to which all mouse events will be
32 * reported. */
33 TkWindow *cursorWinPtr; /* Window that is currently controlling the
34 * global cursor. */
35 } ThreadSpecificData;
36 static Tcl_ThreadDataKey dataKey;
37
38 /*
39 * Forward declarations of procedures used in this file.
40 */
41
42 static int GenerateEnterLeave(TkWindow *winPtr, int x, int y,
43 int state);
44 static void InitializeEvent(XEvent *eventPtr, TkWindow *winPtr,
45 int type, int x, int y, int state, int detail);
46 static void UpdateCursor(TkWindow *winPtr);
47
48 /*
49 *----------------------------------------------------------------------
50 *
51 * InitializeEvent --
52 *
53 * Initializes the common fields for several X events.
54 *
55 * Results:
56 * None.
57 *
58 * Side effects:
59 * Fills in the specified event structure.
60 *
61 *----------------------------------------------------------------------
62 */
63
64 static void
InitializeEvent(XEvent * eventPtr,TkWindow * winPtr,int type,int x,int y,int state,int detail)65 InitializeEvent(
66 XEvent *eventPtr, /* Event structure to initialize. */
67 TkWindow *winPtr, /* Window to make event relative to. */
68 int type, /* Message type. */
69 int x, int y, /* Root coords of event. */
70 int state, /* State flags. */
71 int detail) /* Detail value. */
72 {
73 eventPtr->type = type;
74 eventPtr->xany.serial = LastKnownRequestProcessed(winPtr->display);
75 eventPtr->xany.send_event = False;
76 eventPtr->xany.display = winPtr->display;
77
78 eventPtr->xcrossing.root = RootWindow(winPtr->display, winPtr->screenNum);
79 eventPtr->xcrossing.time = TkpGetMS();
80 eventPtr->xcrossing.x_root = x;
81 eventPtr->xcrossing.y_root = y;
82
83 switch (type) {
84 case EnterNotify:
85 case LeaveNotify:
86 eventPtr->xcrossing.mode = NotifyNormal;
87 eventPtr->xcrossing.state = state;
88 eventPtr->xcrossing.detail = detail;
89 eventPtr->xcrossing.focus = False;
90 break;
91 case MotionNotify:
92 eventPtr->xmotion.state = state;
93 eventPtr->xmotion.is_hint = detail;
94 break;
95 case ButtonPress:
96 case ButtonRelease:
97 eventPtr->xbutton.state = state;
98 eventPtr->xbutton.button = detail;
99 break;
100 }
101 TkChangeEventWindow(eventPtr, winPtr);
102 }
103
104 /*
105 *----------------------------------------------------------------------
106 *
107 * GenerateEnterLeave --
108 *
109 * Update the current mouse window and position, and generate any
110 * enter/leave events that are needed.
111 *
112 * Results:
113 * Returns 1 if enter/leave events were generated.
114 *
115 * Side effects:
116 * May insert events into the Tk event queue.
117 *
118 *----------------------------------------------------------------------
119 */
120
121 static int
GenerateEnterLeave(TkWindow * winPtr,int x,int y,int state)122 GenerateEnterLeave(
123 TkWindow *winPtr, /* Current Tk window (or NULL). */
124 int x, int y, /* Current mouse position in root coords. */
125 int state) /* State flags. */
126 {
127 int crossed = 0; /* 1 if mouse crossed a window boundary */
128 ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
129 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
130 TkWindow *restrictWinPtr = tsdPtr->restrictWinPtr;
131 TkWindow *lastWinPtr = tsdPtr->lastWinPtr;
132
133 if (winPtr != tsdPtr->lastWinPtr) {
134 if (restrictWinPtr) {
135 int newPos, oldPos;
136
137 newPos = TkPositionInTree(winPtr, restrictWinPtr);
138 oldPos = TkPositionInTree(lastWinPtr, restrictWinPtr);
139
140 /*
141 * Check if the mouse crossed into or out of the restrict window.
142 * If so, we need to generate an Enter or Leave event.
143 */
144
145 if ((newPos != oldPos) && ((newPos == TK_GRAB_IN_TREE)
146 || (oldPos == TK_GRAB_IN_TREE))) {
147 XEvent event;
148 int type, detail;
149
150 if (newPos == TK_GRAB_IN_TREE) {
151 type = EnterNotify;
152 } else {
153 type = LeaveNotify;
154 }
155 if ((oldPos == TK_GRAB_ANCESTOR)
156 || (newPos == TK_GRAB_ANCESTOR)) {
157 detail = NotifyAncestor;
158 } else {
159 detail = NotifyVirtual;
160 }
161 InitializeEvent(&event, restrictWinPtr, type, x, y,
162 state, detail);
163 Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL);
164 }
165
166 } else {
167 TkWindow *targetPtr;
168
169 if ((lastWinPtr == NULL)
170 || (lastWinPtr->window == None)) {
171 targetPtr = winPtr;
172 } else {
173 targetPtr = lastWinPtr;
174 }
175
176 if (targetPtr && (targetPtr->window != None)) {
177 XEvent event;
178
179 /*
180 * Generate appropriate Enter/Leave events.
181 */
182
183 InitializeEvent(&event, targetPtr, LeaveNotify, x, y, state,
184 NotifyNormal);
185
186 TkInOutEvents(&event, lastWinPtr, winPtr, LeaveNotify,
187 EnterNotify, TCL_QUEUE_TAIL);
188 crossed = 1;
189 }
190 }
191 tsdPtr->lastWinPtr = winPtr;
192 }
193
194 return crossed;
195 }
196
197 /*
198 *----------------------------------------------------------------------
199 *
200 * Tk_UpdatePointer --
201 *
202 * This function updates the pointer state machine given an the current
203 * window, position and modifier state.
204 *
205 * Results:
206 * None.
207 *
208 * Side effects:
209 * May queue new events and update the grab state.
210 *
211 *----------------------------------------------------------------------
212 */
213
214 void
Tk_UpdatePointer(Tk_Window tkwin,int x,int y,int state)215 Tk_UpdatePointer(
216 Tk_Window tkwin, /* Window to which pointer event is reported.
217 * May be NULL. */
218 int x, int y, /* Pointer location in root coords. */
219 int state) /* Modifier state mask. */
220 {
221 ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
222 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
223 TkWindow *winPtr = (TkWindow *)tkwin;
224 TkWindow *targetWinPtr;
225 XPoint pos;
226 XEvent event;
227 unsigned changes = (state ^ tsdPtr->lastState) & ALL_BUTTONS;
228 int type, b;
229 unsigned mask;
230
231 pos.x = x;
232 pos.y = y;
233
234 /*
235 * Use the current keyboard state, but the old mouse button state since we
236 * haven't generated the button events yet.
237 */
238
239 tsdPtr->lastState = (state & ~ALL_BUTTONS) | (tsdPtr->lastState
240 & ALL_BUTTONS);
241
242 /*
243 * Generate Enter/Leave events. If the pointer has crossed window
244 * boundaries, update the current mouse position so we don't generate
245 * redundant motion events.
246 */
247
248 if (GenerateEnterLeave(winPtr, x, y, tsdPtr->lastState)) {
249 tsdPtr->lastPos = pos;
250 }
251
252 /*
253 * Generate ButtonPress/ButtonRelease events based on the differences
254 * between the current button state and the last known button state.
255 */
256
257 for (b = Button1; b <= Button9; b++) {
258 mask = Tk_GetButtonMask(b);
259 if (changes & mask) {
260 if (state & mask) {
261 type = ButtonPress;
262
263 /*
264 * ButtonPress - Set restrict window if we aren't grabbed, or
265 * if this is the first button down.
266 */
267
268 if (!tsdPtr->restrictWinPtr) {
269 if (!tsdPtr->grabWinPtr) {
270 /*
271 * Mouse is not grabbed, so set a button grab.
272 */
273
274 tsdPtr->restrictWinPtr = winPtr;
275 TkpSetCapture(tsdPtr->restrictWinPtr);
276
277 } else if (!(tsdPtr->lastState & ALL_BUTTONS)) {
278 /*
279 * Mouse is in a non-button grab, so ensure the button
280 * grab is inside the grab tree.
281 */
282
283 if (TkPositionInTree(winPtr, tsdPtr->grabWinPtr)
284 == TK_GRAB_IN_TREE) {
285 tsdPtr->restrictWinPtr = winPtr;
286 } else {
287 tsdPtr->restrictWinPtr = tsdPtr->grabWinPtr;
288 }
289 TkpSetCapture(tsdPtr->restrictWinPtr);
290 }
291 }
292
293 } else {
294 type = ButtonRelease;
295
296 /*
297 * ButtonRelease - Release the mouse capture and clear the
298 * restrict window when the last button is released. If we
299 * are in a global grab, restore the grab window capture.
300 */
301
302 if ((tsdPtr->lastState & ALL_BUTTONS) == mask) {
303 TkpSetCapture(tsdPtr->grabWinPtr);
304 }
305
306 /*
307 * If we are releasing a restrict window, then we need to send
308 * the button event followed by mouse motion from the restrict
309 * window to the current mouse position.
310 */
311
312 if (tsdPtr->restrictWinPtr) {
313 InitializeEvent(&event, tsdPtr->restrictWinPtr, type, x, y,
314 tsdPtr->lastState, b);
315 Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL);
316 tsdPtr->lastState &= ~mask;
317 tsdPtr->lastWinPtr = tsdPtr->restrictWinPtr;
318 tsdPtr->restrictWinPtr = NULL;
319
320 GenerateEnterLeave(winPtr, x, y, tsdPtr->lastState);
321 tsdPtr->lastPos = pos;
322 continue;
323 }
324 }
325
326 /*
327 * If a restrict window is set, make sure the pointer event is
328 * reported relative to that window. Otherwise, if a global grab
329 * is in effect then events outside of windows managed by Tk
330 * should be reported to the grab window.
331 */
332
333 if (tsdPtr->restrictWinPtr) {
334 targetWinPtr = tsdPtr->restrictWinPtr;
335 } else if (tsdPtr->grabWinPtr && !winPtr) {
336 targetWinPtr = tsdPtr->grabWinPtr;
337 } else {
338 targetWinPtr = winPtr;
339 }
340
341 /*
342 * If we still have a target window, send the event.
343 */
344
345 if (targetWinPtr != NULL) {
346 InitializeEvent(&event, targetWinPtr, type, x, y,
347 tsdPtr->lastState, b);
348 Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL);
349 }
350
351 /*
352 * Update the state for the next iteration.
353 */
354
355 tsdPtr->lastState = (type == ButtonPress)
356 ? (tsdPtr->lastState | mask) : (tsdPtr->lastState & ~mask);
357 tsdPtr->lastPos = pos;
358 }
359 }
360
361 /*
362 * Make sure the cursor window is up to date.
363 */
364
365 if (tsdPtr->restrictWinPtr) {
366 targetWinPtr = tsdPtr->restrictWinPtr;
367 } else if (tsdPtr->grabWinPtr) {
368 targetWinPtr = (TkPositionInTree(winPtr, tsdPtr->grabWinPtr)
369 == TK_GRAB_IN_TREE) ? winPtr : tsdPtr->grabWinPtr;
370 } else {
371 targetWinPtr = winPtr;
372 }
373 UpdateCursor(targetWinPtr);
374
375 /*
376 * If no other events caused the position to be updated, generate a motion
377 * event.
378 */
379
380 if (tsdPtr->lastPos.x != pos.x || tsdPtr->lastPos.y != pos.y) {
381 if (tsdPtr->restrictWinPtr) {
382 targetWinPtr = tsdPtr->restrictWinPtr;
383 } else if (tsdPtr->grabWinPtr && !winPtr) {
384 targetWinPtr = tsdPtr->grabWinPtr;
385 }
386
387 if (targetWinPtr != NULL) {
388 InitializeEvent(&event, targetWinPtr, MotionNotify, x, y,
389 tsdPtr->lastState, NotifyNormal);
390 Tk_QueueWindowEvent(&event, TCL_QUEUE_TAIL);
391 }
392 tsdPtr->lastPos = pos;
393 }
394 }
395
396 /*
397 *----------------------------------------------------------------------
398 *
399 * XGrabPointer --
400 *
401 * Capture the mouse so event are reported outside of toplevels. Note
402 * that this is a very limited implementation that only supports
403 * GrabModeAsync and owner_events True.
404 *
405 * Results:
406 * Always returns GrabSuccess.
407 *
408 * Side effects:
409 * Turns on mouse capture, sets the global grab pointer, and clears any
410 * window restrictions.
411 *
412 *----------------------------------------------------------------------
413 */
414
415 int
XGrabPointer(Display * display,Window grab_window,Bool owner_events,unsigned int event_mask,int pointer_mode,int keyboard_mode,Window confine_to,Cursor cursor,Time time)416 XGrabPointer(
417 Display *display,
418 Window grab_window,
419 Bool owner_events,
420 unsigned int event_mask,
421 int pointer_mode,
422 int keyboard_mode,
423 Window confine_to,
424 Cursor cursor,
425 Time time)
426 {
427 ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
428 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
429 (void)owner_events;
430 (void)event_mask;
431 (void)pointer_mode;
432 (void)keyboard_mode;
433 (void)confine_to;
434 (void)cursor;
435 (void)time;
436
437 display->request++;
438 tsdPtr->grabWinPtr = (TkWindow *) Tk_IdToWindow(display, grab_window);
439 tsdPtr->restrictWinPtr = NULL;
440 TkpSetCapture(tsdPtr->grabWinPtr);
441 if (TkPositionInTree(tsdPtr->lastWinPtr, tsdPtr->grabWinPtr)
442 != TK_GRAB_IN_TREE) {
443 UpdateCursor(tsdPtr->grabWinPtr);
444 }
445 return GrabSuccess;
446 }
447
448 /*
449 *----------------------------------------------------------------------
450 *
451 * XUngrabPointer --
452 *
453 * Release the current grab.
454 *
455 * Results:
456 * None.
457 *
458 * Side effects:
459 * Releases the mouse capture.
460 *
461 *----------------------------------------------------------------------
462 */
463
464 int
XUngrabPointer(Display * display,Time time)465 XUngrabPointer(
466 Display *display,
467 Time time)
468 {
469 ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
470 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
471 (void)time;
472
473 display->request++;
474 tsdPtr->grabWinPtr = NULL;
475 tsdPtr->restrictWinPtr = NULL;
476 TkpSetCapture(NULL);
477 UpdateCursor(tsdPtr->lastWinPtr);
478 return Success;
479 }
480
481 /*
482 *----------------------------------------------------------------------
483 *
484 * TkPointerDeadWindow --
485 *
486 * Clean up pointer module state when a window is destroyed.
487 *
488 * Results:
489 * None.
490 *
491 * Side effects:
492 * May release the current capture window.
493 *
494 *----------------------------------------------------------------------
495 */
496
497 void
TkPointerDeadWindow(TkWindow * winPtr)498 TkPointerDeadWindow(
499 TkWindow *winPtr)
500 {
501 ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
502 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
503
504 if (winPtr == tsdPtr->lastWinPtr) {
505 tsdPtr->lastWinPtr = TkGetContainer(winPtr);
506 }
507 if (winPtr == tsdPtr->grabWinPtr) {
508 tsdPtr->grabWinPtr = NULL;
509 }
510 if (winPtr == tsdPtr->restrictWinPtr) {
511 tsdPtr->restrictWinPtr = NULL;
512 }
513 if (!(tsdPtr->restrictWinPtr || tsdPtr->grabWinPtr)) {
514
515 /*
516 * Release mouse capture only if the dead window is the capturing
517 * window.
518 */
519
520 if (winPtr == (TkWindow *)TkpGetCapture()) {
521 TkpSetCapture(NULL);
522 }
523 }
524 }
525
526 /*
527 *----------------------------------------------------------------------
528 *
529 * UpdateCursor --
530 *
531 * Set the windows global cursor to the cursor associated with the given
532 * Tk window.
533 *
534 * Results:
535 * None.
536 *
537 * Side effects:
538 * Changes the mouse cursor.
539 *
540 *----------------------------------------------------------------------
541 */
542
543 static void
UpdateCursor(TkWindow * winPtr)544 UpdateCursor(
545 TkWindow *winPtr)
546 {
547 Cursor cursor = None;
548 ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
549 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
550
551 /*
552 * A window inherits its cursor from its parent if it doesn't have one of
553 * its own. Top level windows inherit the default cursor.
554 */
555
556 tsdPtr->cursorWinPtr = winPtr;
557 while (winPtr != NULL) {
558 if (winPtr->atts.cursor != None) {
559 cursor = winPtr->atts.cursor;
560 break;
561 } else if (winPtr->flags & TK_TOP_HIERARCHY) {
562 break;
563 }
564 winPtr = winPtr->parentPtr;
565 }
566 TkpSetCursor((TkpCursor) cursor);
567 }
568
569 /*
570 *----------------------------------------------------------------------
571 *
572 * XDefineCursor --
573 *
574 * This function is called to update the cursor on a window. Since the
575 * mouse might be in the specified window, we need to check the specified
576 * window against the current mouse position and grab state.
577 *
578 * Results:
579 * None.
580 *
581 * Side effects:
582 * May update the cursor.
583 *
584 *----------------------------------------------------------------------
585 */
586
587 int
XDefineCursor(Display * display,Window w,Cursor cursor)588 XDefineCursor(
589 Display *display,
590 Window w,
591 Cursor cursor)
592 {
593 TkWindow *winPtr = (TkWindow *) Tk_IdToWindow(display, w);
594 ThreadSpecificData *tsdPtr = (ThreadSpecificData *)
595 Tcl_GetThreadData(&dataKey, sizeof(ThreadSpecificData));
596 (void)cursor;
597
598 if (tsdPtr->cursorWinPtr == winPtr) {
599 UpdateCursor(winPtr);
600 }
601 display->request++;
602 return Success;
603 }
604
605 /*
606 * Local Variables:
607 * mode: c
608 * c-basic-offset: 4
609 * fill-column: 78
610 * End:
611 */
612