1 /*
2  * tkMacOSXMouseEvent.c --
3  *
4  *	This file implements functions that decode & handle mouse events on
5  *	MacOS X.
6  *
7  * Copyright 2001-2009, Apple Inc.
8  * Copyright (c) 2005-2009 Daniel A. Steffen <das@users.sourceforge.net>
9  *
10  * See the file "license.terms" for information on usage and redistribution of
11  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
12  */
13 
14 #include "tkMacOSXPrivate.h"
15 #include "tkMacOSXWm.h"
16 #include "tkMacOSXEvent.h"
17 #include "tkMacOSXDebug.h"
18 
19 typedef struct {
20     unsigned int state;
21     long delta;
22     Window window;
23     Point global;
24     Point local;
25 } MouseEventData;
26 
27 static int		GenerateButtonEvent(MouseEventData *medPtr);
28 static unsigned int	ButtonModifiers2State(UInt32 buttonState,
29 					      UInt32 keyModifiers);
30 
31 #pragma mark TKApplication(TKMouseEvent)
32 
33 enum {
34     NSWindowWillMoveEventType = 20
35 };
36 
37 /*
38  * In OS X 10.6 an NSEvent of type NSMouseMoved would always have a non-Nil
39  * window attribute pointing to the active window.  As of 10.8 this behavior
40  * had changed.  The new behavior was that if the mouse were ever moved outside
41  * of a window, all subsequent NSMouseMoved NSEvents would have a Nil window
42  * attribute.  To work around this the TKApplication remembers the last non-Nil
43  * window that it received in a mouse event. If it receives an NSEvent with a
44  * Nil window attribute then the saved window is used.
45  */
46 
47 @implementation TKApplication(TKMouseEvent)
48 - (NSEvent *)tkProcessMouseEvent:(NSEvent *)theEvent {
49 #ifdef TK_MAC_DEBUG_EVENTS
50     TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, theEvent);
51 #endif
52     NSWindow*    eventWindow = [theEvent window];
53     NSEventType	 eventType = [theEvent type];
54 #if 0
55     NSTrackingArea  *trackingArea = nil;
56     NSInteger eventNumber, clickCount, buttonNumber;
57 #endif
58 
59     switch (eventType) {
60     case NSMouseEntered:
61 	/* Remember which window has the mouse. */
62 	if (_windowWithMouse) {
63 	    [_windowWithMouse release];
64 	}
65 	_windowWithMouse = [theEvent window];
66 	if (_windowWithMouse) {
67 	    [_windowWithMouse retain];
68 	}
69 	break;
70     case NSMouseExited:
71     case NSCursorUpdate:
72     case NSLeftMouseDown:
73     case NSLeftMouseUp:
74     case NSRightMouseDown:
75     case NSRightMouseUp:
76     case NSOtherMouseDown:
77     case NSOtherMouseUp:
78     case NSLeftMouseDragged:
79     case NSRightMouseDragged:
80     case NSOtherMouseDragged:
81     case NSMouseMoved:
82 #if 0
83 	eventNumber = [theEvent eventNumber];
84 	if (!trackingArea) {
85 	    clickCount = [theEvent clickCount];
86 	    buttonNumber = [theEvent buttonNumber];
87 	}
88 #endif
89     case NSTabletPoint:
90     case NSTabletProximity:
91     case NSScrollWheel:
92         break;
93     default: /* Unrecognized mouse event. */
94 	return theEvent;
95     }
96 
97     /* Remember the window in case we need it next time. */
98     if (eventWindow && eventWindow != _windowWithMouse) {
99 	if (_windowWithMouse) {
100 	    [_windowWithMouse release];
101 	}
102 	_windowWithMouse = eventWindow;
103 	[_windowWithMouse retain];
104     }
105 
106     /* Create an Xevent to add to the Tk queue. */
107     NSPoint global, local = [theEvent locationInWindow];
108     if (eventWindow) { /* local will be in window coordinates. */
109 	global = [eventWindow convertPointToScreen: local];
110 	local.y = [eventWindow frame].size.height - local.y;
111 	global.y = tkMacOSXZeroScreenHeight - global.y;
112     } else { /* local will be in screen coordinates. */
113 	if (_windowWithMouse ) {
114 	    eventWindow = _windowWithMouse;
115 	    global = local;
116 	    local = [eventWindow convertPointFromScreen: local];
117 	    local.y = [eventWindow frame].size.height - local.y;
118 	    global.y = tkMacOSXZeroScreenHeight - global.y;
119 	} else { /* We have no window. Use the screen???*/
120 	    local.y = tkMacOSXZeroScreenHeight - local.y;
121 	    global = local;
122 	}
123     }
124 
125     Window window = TkMacOSXGetXWindow(eventWindow);
126     Tk_Window tkwin = window ? Tk_IdToWindow(TkGetDisplayList()->display,
127 	    window) : NULL;
128     if (!tkwin) {
129 	tkwin = TkMacOSXGetCapture();
130     }
131     if (!tkwin) {
132 	return theEvent; /* Give up.  No window for this event. */
133     }
134 
135     TkWindow  *winPtr = (TkWindow *) tkwin;
136     local.x -= winPtr->wmInfoPtr->xInParent;
137     local.y -= winPtr->wmInfoPtr->yInParent;
138 
139     int win_x, win_y;
140     tkwin = Tk_TopCoordsToWindow(tkwin, local.x, local.y,
141 		&win_x, &win_y);
142 
143     unsigned int state = 0;
144     NSInteger button = [theEvent buttonNumber];
145     EventRef eventRef = (EventRef)[theEvent eventRef];
146     UInt32 buttons;
147     OSStatus err = GetEventParameter(eventRef, kEventParamMouseChord,
148 	    typeUInt32, NULL, sizeof(UInt32), NULL, &buttons);
149     if (err == noErr) {
150 	state |= (buttons & ((1<<5) - 1)) << 8;
151     } else {
152 	if (button < 5) {
153 	    switch (eventType) {
154 	    case NSLeftMouseDown:
155 	    case NSRightMouseDown:
156 	    case NSLeftMouseDragged:
157 	    case NSRightMouseDragged:
158 	    case NSOtherMouseDown:
159 		state |= 1 << (button + 8);
160 		break;
161 	    default:
162 		break;
163 	    }
164 	}
165     }
166     NSUInteger modifiers = [theEvent modifierFlags];
167 
168     if (modifiers & NSAlphaShiftKeyMask) {
169 	state |= LockMask;
170     }
171     if (modifiers & NSShiftKeyMask) {
172 	state |= ShiftMask;
173     }
174     if (modifiers & NSControlKeyMask) {
175 	state |= ControlMask;
176     }
177     if (modifiers & NSCommandKeyMask) {
178 	state |= Mod1Mask;		/* command key */
179     }
180     if (modifiers & NSAlternateKeyMask) {
181 	state |= Mod2Mask;		/* option key */
182     }
183     if (modifiers & NSNumericPadKeyMask) {
184 	state |= Mod3Mask;
185     }
186     if (modifiers & NSFunctionKeyMask) {
187 	state |= Mod4Mask;
188     }
189 
190     if (eventType != NSScrollWheel) {
191 #ifdef TK_MAC_DEBUG_EVENTS
192 	TKLog(@"UpdatePointer %p x %f.0 y %f.0 %d", tkwin, global.x, global.y, state);
193 #endif
194 	Tk_UpdatePointer(tkwin, global.x, global.y, state);
195     } else { /* handle scroll wheel event */
196 	CGFloat delta;
197 	int coarseDelta;
198 	XEvent xEvent;
199 
200 	xEvent.type = MouseWheelEvent;
201 	xEvent.xbutton.x = local.x;
202 	xEvent.xbutton.y = local.y;
203 	xEvent.xbutton.x_root = global.x;
204 	xEvent.xbutton.y_root = global.y;
205 	xEvent.xany.send_event = false;
206 	xEvent.xany.display = Tk_Display(tkwin);
207 	xEvent.xany.window = Tk_WindowId(tkwin);
208 
209 	delta = [theEvent deltaY];
210 	if (delta != 0.0) {
211 	    coarseDelta = (delta > -1.0 && delta < 1.0) ?
212 		(signbit(delta) ? -1 : 1) : lround(delta);
213 	    xEvent.xbutton.state = state;
214 	    xEvent.xkey.keycode = coarseDelta;
215 	    xEvent.xany.serial = LastKnownRequestProcessed(Tk_Display(tkwin));
216 	    Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL);
217 	}
218 	delta = [theEvent deltaX];
219 	if (delta != 0.0) {
220 	    coarseDelta = (delta > -1.0 && delta < 1.0) ?
221 		(signbit(delta) ? -1 : 1) : lround(delta);
222 	    xEvent.xbutton.state = state | ShiftMask;
223 	    xEvent.xkey.keycode = coarseDelta;
224 	    xEvent.xany.serial = LastKnownRequestProcessed(Tk_Display(tkwin));
225 	    Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL);
226 	}
227     }
228     return theEvent;
229 }
230 @end
231 
232 #pragma mark -
233 
234 /*
235  *----------------------------------------------------------------------
236  *
237  * TkMacOSXKeyModifiers --
238  *
239  *	Returns the current state of the modifier keys.
240  *
241  * Results:
242  *	An OS Modifier state.
243  *
244  * Side effects:
245  *	None.
246  *
247  *----------------------------------------------------------------------
248  */
249 
250 EventModifiers
TkMacOSXModifierState(void)251 TkMacOSXModifierState(void)
252 {
253     UInt32 keyModifiers;
254     int isFrontProcess = (GetCurrentEvent() && Tk_MacOSXIsAppInFront());
255 
256     keyModifiers = isFrontProcess ? GetCurrentEventKeyModifiers() :
257 	    GetCurrentKeyModifiers();
258 
259     return (EventModifiers) (keyModifiers & USHRT_MAX);
260 }
261 
262 /*
263  *----------------------------------------------------------------------
264  *
265  * TkMacOSXButtonKeyState --
266  *
267  *	Returns the current state of the button & modifier keys.
268  *
269  * Results:
270  *	A bitwise inclusive OR of a subset of the following: Button1Mask,
271  *	ShiftMask, LockMask, ControlMask, Mod*Mask.
272  *
273  * Side effects:
274  *	None.
275  *
276  *----------------------------------------------------------------------
277  */
278 
279 unsigned int
TkMacOSXButtonKeyState(void)280 TkMacOSXButtonKeyState(void)
281 {
282     UInt32 buttonState = 0, keyModifiers;
283     int isFrontProcess = (GetCurrentEvent() && Tk_MacOSXIsAppInFront());
284 
285     buttonState = isFrontProcess ? GetCurrentEventButtonState() :
286 	    GetCurrentButtonState();
287     keyModifiers = isFrontProcess ? GetCurrentEventKeyModifiers() :
288 	    GetCurrentKeyModifiers();
289 
290     return ButtonModifiers2State(buttonState, keyModifiers);
291 }
292 
293 /*
294  *----------------------------------------------------------------------
295  *
296  * ButtonModifiers2State --
297  *
298  *	Converts Carbon mouse button state and modifier values into a Tk
299  *	button/modifier state.
300  *
301  * Results:
302  *	None.
303  *
304  * Side effects:
305  *	None.
306  *
307  *----------------------------------------------------------------------
308  */
309 
310 static unsigned int
ButtonModifiers2State(UInt32 buttonState,UInt32 keyModifiers)311 ButtonModifiers2State(
312     UInt32 buttonState,
313     UInt32 keyModifiers)
314 {
315     unsigned int state;
316 
317     /*
318      * Tk supports at most 5 buttons.
319      */
320 
321     state = (buttonState & ((1<<5) - 1)) << 8;
322 
323     if (keyModifiers & alphaLock) {
324 	state |= LockMask;
325     }
326     if (keyModifiers & shiftKey) {
327 	state |= ShiftMask;
328     }
329     if (keyModifiers & controlKey) {
330 	state |= ControlMask;
331     }
332     if (keyModifiers & cmdKey) {
333 	state |= Mod1Mask;		/* command key */
334     }
335     if (keyModifiers & optionKey) {
336 	state |= Mod2Mask;		/* option key */
337     }
338     if (keyModifiers & kEventKeyModifierNumLockMask) {
339 	state |= Mod3Mask;
340     }
341     if (keyModifiers & kEventKeyModifierFnMask) {
342 	state |= Mod4Mask;
343     }
344 
345     return state;
346 }
347 
348 /*
349  *----------------------------------------------------------------------
350  *
351  * XQueryPointer --
352  *
353  *	Check the current state of the mouse. This is not a complete
354  *	implementation of this function. It only computes the root coordinates
355  *	and the current mask.
356  *
357  * Results:
358  *	Sets root_x_return, root_y_return, and mask_return. Returns true on
359  *	success.
360  *
361  * Side effects:
362  *	None.
363  *
364  *----------------------------------------------------------------------
365  */
366 
367 Bool
XQueryPointer(Display * display,Window w,Window * root_return,Window * child_return,int * root_x_return,int * root_y_return,int * win_x_return,int * win_y_return,unsigned int * mask_return)368 XQueryPointer(
369     Display *display,
370     Window w,
371     Window *root_return,
372     Window *child_return,
373     int *root_x_return,
374     int *root_y_return,
375     int *win_x_return,
376     int *win_y_return,
377     unsigned int *mask_return)
378 {
379     int getGlobal = (root_x_return && root_y_return);
380     int getLocal = (win_x_return && win_y_return && w != None);
381 
382     if (getGlobal || getLocal) {
383 	NSPoint global = [NSEvent mouseLocation];
384 
385 	if (getLocal) {
386 	    MacDrawable *macWin = (MacDrawable *) w;
387 	    NSWindow *win = TkMacOSXDrawableWindow(w);
388 
389 	    if (win) {
390 		NSPoint local;
391 
392 		local = [win convertPointFromScreen:global];
393 		local.y = [win frame].size.height - local.y;
394 		if (macWin->winPtr && macWin->winPtr->wmInfoPtr) {
395 		    local.x -= macWin->winPtr->wmInfoPtr->xInParent;
396 		    local.y -= macWin->winPtr->wmInfoPtr->yInParent;
397 		}
398 		*win_x_return = local.x;
399 		*win_y_return = local.y;
400 	    }
401 	}
402 	if (getGlobal) {
403 	    *root_x_return = global.x;
404 	    *root_y_return = tkMacOSXZeroScreenHeight - global.y;
405 	}
406     }
407     if (mask_return) {
408 	*mask_return = TkMacOSXButtonKeyState();
409     }
410     return True;
411 }
412 
413 /*
414  *----------------------------------------------------------------------
415  *
416  * TkGenerateButtonEventForXPointer --
417  *
418  *	This procedure generates an X button event for the current pointer
419  *	state as reported by XQueryPointer().
420  *
421  * Results:
422  *	True if event(s) are generated - false otherwise.
423  *
424  * Side effects:
425  *	Additional events may be place on the Tk event queue. Grab state may
426  *	also change.
427  *
428  *----------------------------------------------------------------------
429  */
430 
431 MODULE_SCOPE int
TkGenerateButtonEventForXPointer(Window window)432 TkGenerateButtonEventForXPointer(
433     Window window)		/* X Window containing button event. */
434 {
435     MouseEventData med;
436     int global_x, global_y, local_x, local_y;
437 
438     bzero(&med, sizeof(MouseEventData));
439     XQueryPointer(NULL, window, NULL, NULL, &global_x, &global_y,
440 	    &local_x, &local_y, &med.state);
441     med.global.h = global_x;
442     med.global.v = global_y;
443     med.local.h = local_x;
444     med.local.v = local_y;
445     med.window = window;
446 
447     return GenerateButtonEvent(&med);
448 }
449 
450 /*
451  *----------------------------------------------------------------------
452  *
453  * TkGenerateButtonEvent --
454  *
455  *	Given a global x & y position and the button key status this procedure
456  *	generates the appropiate X button event. It also handles the state
457  *	changes needed to implement implicit grabs.
458  *
459  * Results:
460  *	True if event(s) are generated, false otherwise.
461  *
462  * Side effects:
463  *	Additional events may be place on the Tk event queue. Grab state may
464  *	also change.
465  *
466  *----------------------------------------------------------------------
467  */
468 
469 int
TkGenerateButtonEvent(int x,int y,Window window,unsigned int state)470 TkGenerateButtonEvent(
471     int x,			/* X location of mouse, */
472     int y,			/* Y location of mouse. */
473     Window window,		/* X Window containing button event. */
474     unsigned int state)		/* Button Key state suitable for X event. */
475 {
476     MacDrawable *macWin = (MacDrawable *) window;
477     NSWindow *win = TkMacOSXDrawableWindow(window);
478     MouseEventData med;
479 
480     bzero(&med, sizeof(MouseEventData));
481     med.state = state;
482     med.window = window;
483     med.global.h = x;
484     med.global.v = y;
485     med.local = med.global;
486 
487     if (win) {
488 	NSPoint local = NSMakePoint(x, tkMacOSXZeroScreenHeight - y);
489 
490 	local = [win convertPointFromScreen:local];
491 	local.y = [win frame].size.height - local.y;
492 	if (macWin->winPtr && macWin->winPtr->wmInfoPtr) {
493 	    local.x -= macWin->winPtr->wmInfoPtr->xInParent;
494 	    local.y -= macWin->winPtr->wmInfoPtr->yInParent;
495 	}
496 	med.local.h = local.x;
497 	med.local.v = tkMacOSXZeroScreenHeight - local.y;
498     }
499 
500     return GenerateButtonEvent(&med);
501 }
502 
503 /*
504  *----------------------------------------------------------------------
505  *
506  * GenerateButtonEvent --
507  *
508  *	Generate an X button event from a MouseEventData structure. Handles
509  *	the state changes needed to implement implicit grabs.
510  *
511  * Results:
512  *	True if event(s) are generated - false otherwise.
513  *
514  * Side effects:
515  *	Additional events may be place on the Tk event queue. Grab state may
516  *	also change.
517  *
518  *----------------------------------------------------------------------
519  */
520 
521 static int
GenerateButtonEvent(MouseEventData * medPtr)522 GenerateButtonEvent(
523     MouseEventData *medPtr)
524 {
525     Tk_Window tkwin;
526     int dummy;
527     TkDisplay *dispPtr;
528 
529 #if UNUSED
530     /*
531      * ButtonDown events will always occur in the front window. ButtonUp
532      * events, however, may occur anywhere on the screen. ButtonUp events
533      * should only be sent to Tk if in the front window or during an implicit
534      * grab.
535      */
536 
537     if ((medPtr->activeNonFloating == NULL)
538 	    || ((!(TkpIsWindowFloating(medPtr->whichWin))
539 	    && (medPtr->activeNonFloating != medPtr->whichWin))
540 	    && TkMacOSXGetCapture() == NULL)) {
541 	return false;
542     }
543 #endif
544 
545     dispPtr = TkGetDisplayList();
546     tkwin = Tk_IdToWindow(dispPtr->display, medPtr->window);
547 
548     if (tkwin != NULL) {
549 	tkwin = Tk_TopCoordsToWindow(tkwin, medPtr->local.h, medPtr->local.v,
550 		&dummy, &dummy);
551     }
552 
553     Tk_UpdatePointer(tkwin, medPtr->global.h, medPtr->global.v, medPtr->state);
554     return true;
555 }
556 
557 /*
558  * Local Variables:
559  * mode: objc
560  * c-basic-offset: 4
561  * fill-column: 79
562  * coding: utf-8
563  * End:
564  */
565