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