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