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