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 #include "tkMacOSXConstants.h"
19
20 typedef struct {
21 unsigned int state;
22 long delta;
23 Window window;
24 Point global;
25 Point local;
26 } MouseEventData;
27
28 static Tk_Window captureWinPtr = NULL; /* Current capture window; may be
29 * NULL. */
30
31 static int GenerateButtonEvent(MouseEventData *medPtr);
32 static unsigned int ButtonModifiers2State(UInt32 buttonState,
33 UInt32 keyModifiers);
34
35 #pragma mark TKApplication(TKMouseEvent)
36
37 enum {
38 NSWindowWillMoveEventType = 20
39 };
40
41 /*
42 * In OS X 10.6 an NSEvent of type NSMouseMoved would always have a non-Nil
43 * window attribute pointing to the active window. As of 10.8 this behavior
44 * had changed. The new behavior was that if the mouse were ever moved outside
45 * of a window, all subsequent NSMouseMoved NSEvents would have a Nil window
46 * attribute until the mouse returned to the window. In 11.1 it changed again.
47 * The window attribute can be non-nil, but referencing a window which does not
48 * belong to the application.
49 */
50
51 @implementation TKApplication(TKMouseEvent)
52 - (NSEvent *) tkProcessMouseEvent: (NSEvent *) theEvent
53 {
54 NSWindow *eventWindow = [theEvent window];
55 NSEventType eventType = [theEvent type];
56 NSRect viewFrame = [[eventWindow contentView] frame];
57 NSPoint location = [theEvent locationInWindow];
58 TkWindow *winPtr = NULL, *grabWinPtr;
59 Tk_Window tkwin = None, capture, target;
60 NSPoint local, global;
61 NSInteger button;
62 Bool inTitleBar = NO;
63 int win_x, win_y;
64 unsigned int buttonState = 0;
65 static int validPresses = 0, ignoredPresses = 0;
66
67 #ifdef TK_MAC_DEBUG_EVENTS
68 TKLog(@"-[%@(%p) %s] %@", [self class], self, _cmd, theEvent);
69 #endif
70
71 /*
72 * If this event is not for a Tk toplevel, it should just be passed up the
73 * responder chain. However, there is an exception for synthesized events,
74 * which are used in testing. Those events are recognized by having their
75 * both the windowNumber and the eventNumber set to -1.
76 */
77
78 if (eventWindow && ![eventWindow isMemberOfClass:[TKWindow class]]) {
79 if ([theEvent windowNumber] != -1 || [theEvent eventNumber] != -1)
80 return theEvent;
81 }
82
83 /*
84 * Check if the event is located in the titlebar.
85 */
86
87 if (eventWindow) {
88 inTitleBar = viewFrame.size.height < location.y;
89 }
90
91 button = [theEvent buttonNumber] + Button1;
92 switch (eventType) {
93 case NSRightMouseUp:
94 case NSOtherMouseUp:
95 buttonState &= ~TkGetButtonMask(button);
96 break;
97 case NSLeftMouseDragged:
98 case NSRightMouseDragged:
99 case NSOtherMouseDragged:
100 case NSRightMouseDown:
101 case NSOtherMouseDown:
102 buttonState |= TkGetButtonMask(button);
103 break;
104 case NSMouseEntered:
105 if ([eventWindow respondsToSelector:@selector(mouseInResizeArea)] &&
106 !inTitleBar) {
107 [(TKWindow *)eventWindow setMouseInResizeArea:YES];
108 }
109 break;
110 case NSMouseExited:
111 if ([eventWindow respondsToSelector:@selector(mouseInResizeArea)]) {
112 [(TKWindow *)eventWindow setMouseInResizeArea:NO];
113 break;
114 }
115 case NSLeftMouseUp:
116 case NSLeftMouseDown:
117
118 /*
119 * Ignore mouse button events which arrive while the app is inactive.
120 * These events will be resent after activation, causing duplicate
121 * actions when an app is activated by a bound mouse event. See ticket
122 * [7bda9882cb].
123 */
124
125 if (! [NSApp isActive]) {
126 return theEvent;
127 }
128 case NSMouseMoved:
129 case NSScrollWheel:
130 #if 0
131 case NSCursorUpdate:
132 case NSTabletPoint:
133 case NSTabletProximity:
134 #endif
135 break;
136 default: /* This type of event is ignored. */
137 return theEvent;
138 }
139
140 /*
141 * Update the button state. We ignore left button presses that start a
142 * resize or occur in the title bar. See tickets [d72abe6b54] and
143 * [39cbacb9e8].
144 */
145
146 if (eventType == NSLeftMouseDown) {
147 if ([eventWindow respondsToSelector:@selector(mouseInResizeArea)] &&
148 [(TKWindow *) eventWindow mouseInResizeArea]) {
149
150 /*
151 * When the left button is pressed in the resize area, we receive
152 * NSMouseDown, but when it is released we do not receive
153 * NSMouseUp. So ignore the event and clear the button state but
154 * do not change the ignoredPresses count.
155 */
156
157 buttonState &= ~TkGetButtonMask(Button1);
158 return theEvent;
159 }
160 if (inTitleBar) {
161 ignoredPresses++;
162 return theEvent;
163 }
164 validPresses++;
165 buttonState |= TkGetButtonMask(Button1);
166 }
167 if (eventType == NSLeftMouseUp) {
168 if (ignoredPresses > 0) {
169 ignoredPresses--;
170 } else if (validPresses > 0) {
171 validPresses--;
172 }
173 if (validPresses == 0) {
174 buttonState &= ~TkGetButtonMask(Button1);
175 }
176 }
177
178 /*
179 * Find an appropriate NSWindow to attach to this event, and its
180 * associated Tk window.
181 */
182
183 capture = TkMacOSXGetCapture();
184 if (eventWindow) {
185 winPtr = TkMacOSXGetTkWindow(eventWindow);
186 } else if (capture) {
187 winPtr = (TkWindow *) capture;
188 eventWindow = TkMacOSXGetNSWindowForDrawable(winPtr->window);
189 if (!eventWindow) {
190 return theEvent;
191 }
192 }
193 if (!winPtr) {
194 eventWindow = [NSApp mainWindow];
195 winPtr = TkMacOSXGetTkWindow(eventWindow);
196 }
197 if (!winPtr) {
198
199 /*
200 * We couldn't find a Tk window for this event. We have to ignore it.
201 */
202
203 #ifdef TK_MAC_DEBUG_EVENTS
204 TkMacOSXDbgMsg("Event received with no Tk window.");
205 #endif
206 return theEvent;
207 }
208 tkwin = (Tk_Window) winPtr;
209
210 /*
211 * Compute the mouse position in local (window) and global (screen)
212 * coordinates. These are Tk coordinates, meaning that the local origin is
213 * at the top left corner of the containing toplevel and the global origin
214 * is at top left corner of the primary screen.
215 */
216
217 global = [NSEvent mouseLocation];
218 local = [eventWindow tkConvertPointFromScreen: global];
219 global.x = floor(global.x);
220 global.y = floor(TkMacOSXZeroScreenHeight() - global.y);
221 local.x = floor(local.x);
222 local.y = floor([eventWindow frame].size.height - local.y);
223 if (Tk_IsEmbedded(winPtr)) {
224 TkWindow *contPtr = TkpGetOtherWindow(winPtr);
225 if (Tk_IsTopLevel(contPtr)) {
226 local.x -= contPtr->wmInfoPtr->xInParent;
227 local.y -= contPtr->wmInfoPtr->yInParent;
228 } else {
229 TkWindow *topPtr = TkMacOSXGetHostToplevel(winPtr)->winPtr;
230 local.x -= (topPtr->wmInfoPtr->xInParent + contPtr->changes.x);
231 local.y -= (topPtr->wmInfoPtr->yInParent + contPtr->changes.y);
232 }
233 } else {
234 local.x -= winPtr->wmInfoPtr->xInParent;
235 local.y -= winPtr->wmInfoPtr->yInParent;
236 }
237
238 /*
239 * Use the local coordinates to find the Tk window which should receive
240 * this event. Also convert local into the coordinates of that window.
241 * (The converted local coordinates are only needed for scrollwheel
242 * events.)
243 */
244
245 target = Tk_TopCoordsToWindow(tkwin, local.x, local.y, &win_x, &win_y);
246
247 /*
248 * Ignore the event if a local grab is in effect and the Tk window is
249 * not in the grabber's subtree.
250 */
251
252 grabWinPtr = winPtr->dispPtr->grabWinPtr;
253 if (grabWinPtr && /* There is a grab in effect ... */
254 !winPtr->dispPtr->grabFlags && /* and it is a local grab ... */
255 grabWinPtr->mainPtr == winPtr->mainPtr){ /* in the same application. */
256 Tk_Window tkwin2;
257 if (!target) {
258 return theEvent;
259 }
260 for (tkwin2 = target;
261 !Tk_IsTopLevel(tkwin2);
262 tkwin2 = Tk_Parent(tkwin2)) {
263 if (tkwin2 == (Tk_Window)grabWinPtr) {
264 break;
265 }
266 }
267 if (tkwin2 != (Tk_Window)grabWinPtr) {
268 return theEvent;
269 }
270 }
271
272 /*
273 * Generate an XEvent for this mouse event.
274 */
275
276 unsigned int state = buttonState;
277 NSUInteger modifiers = [theEvent modifierFlags];
278
279 if (modifiers & NSAlphaShiftKeyMask) {
280 state |= LockMask;
281 }
282 if (modifiers & NSShiftKeyMask) {
283 state |= ShiftMask;
284 }
285 if (modifiers & NSControlKeyMask) {
286 state |= ControlMask;
287 }
288 if (modifiers & NSCommandKeyMask) {
289 state |= Mod1Mask; /* command key */
290 }
291 if (modifiers & NSAlternateKeyMask) {
292 state |= Mod2Mask; /* option key */
293 }
294 if (modifiers & NSNumericPadKeyMask) {
295 state |= Mod3Mask;
296 }
297 if (modifiers & NSFunctionKeyMask) {
298 state |= Mod4Mask;
299 }
300
301 if (eventType != NSScrollWheel) {
302
303 /*
304 * For normal mouse events, Tk_UpdatePointer will send the appropriate
305 * XEvents using its cached state information. Unfortunately, it will
306 * also recompute the local coordinates.
307 */
308
309 #ifdef TK_MAC_DEBUG_EVENTS
310 TKLog(@"UpdatePointer %p x %.1f y %.1f %d",
311 target, global.x, global.y, state);
312 #endif
313
314 Tk_UpdatePointer(target, global.x, global.y, state);
315 } else {
316 CGFloat delta;
317 int coarseDelta;
318 XEvent xEvent;
319
320 /*
321 * For scroll wheel events we need to send the XEvent here.
322 */
323
324 xEvent.type = MouseWheelEvent;
325 xEvent.xbutton.x = win_x;
326 xEvent.xbutton.y = win_y;
327 xEvent.xbutton.x_root = global.x;
328 xEvent.xbutton.y_root = global.y;
329 xEvent.xany.send_event = false;
330 xEvent.xany.display = Tk_Display(target);
331 xEvent.xany.window = Tk_WindowId(target);
332
333 delta = [theEvent deltaY];
334 if (delta != 0.0) {
335 coarseDelta = (delta > -1.0 && delta < 1.0) ?
336 (signbit(delta) ? -1 : 1) : lround(delta);
337 xEvent.xbutton.state = state;
338 xEvent.xkey.keycode = coarseDelta;
339 xEvent.xany.serial = LastKnownRequestProcessed(Tk_Display(tkwin));
340 Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL);
341 }
342 delta = [theEvent deltaX];
343 if (delta != 0.0) {
344 coarseDelta = (delta > -1.0 && delta < 1.0) ?
345 (signbit(delta) ? -1 : 1) : lround(delta);
346 xEvent.xbutton.state = state | ShiftMask;
347 xEvent.xkey.keycode = coarseDelta;
348 xEvent.xany.serial = LastKnownRequestProcessed(Tk_Display(tkwin));
349 Tk_QueueWindowEvent(&xEvent, TCL_QUEUE_TAIL);
350 }
351 }
352 return theEvent;
353 }
354 @end
355
356 #pragma mark -
357
358 /*
359 *----------------------------------------------------------------------
360 *
361 * TkMacOSXButtonKeyState --
362 *
363 * Returns the current state of the button & modifier keys.
364 *
365 * Results:
366 * A bitwise inclusive OR of a subset of the following: Button1Mask,
367 * ShiftMask, LockMask, ControlMask, Mod*Mask.
368 *
369 * Side effects:
370 * None.
371 *
372 *----------------------------------------------------------------------
373 */
374
375 unsigned int
TkMacOSXButtonKeyState(void)376 TkMacOSXButtonKeyState(void)
377 {
378 UInt32 buttonState = 0, keyModifiers;
379 int isFrontProcess = (GetCurrentEvent() && Tk_MacOSXIsAppInFront());
380
381 buttonState = isFrontProcess ? GetCurrentEventButtonState() :
382 GetCurrentButtonState();
383 keyModifiers = isFrontProcess ? GetCurrentEventKeyModifiers() :
384 GetCurrentKeyModifiers();
385
386 return ButtonModifiers2State(buttonState, keyModifiers);
387 }
388
389 /*
390 *----------------------------------------------------------------------
391 *
392 * ButtonModifiers2State --
393 *
394 * Converts Carbon mouse button state and modifier values into a Tk
395 * button/modifier state.
396 *
397 * Results:
398 * None.
399 *
400 * Side effects:
401 * None.
402 *
403 *----------------------------------------------------------------------
404 */
405
406 static unsigned int
ButtonModifiers2State(UInt32 buttonState,UInt32 keyModifiers)407 ButtonModifiers2State(
408 UInt32 buttonState,
409 UInt32 keyModifiers)
410 {
411 unsigned int state;
412
413 /*
414 * Tk on OSX supports at most 5 buttons.
415 */
416
417 state = (buttonState & 0x1F) * Button1Mask;
418
419 if (keyModifiers & alphaLock) {
420 state |= LockMask;
421 }
422 if (keyModifiers & shiftKey) {
423 state |= ShiftMask;
424 }
425 if (keyModifiers & controlKey) {
426 state |= ControlMask;
427 }
428 if (keyModifiers & cmdKey) {
429 state |= Mod1Mask; /* command key */
430 }
431 if (keyModifiers & optionKey) {
432 state |= Mod2Mask; /* option key */
433 }
434 if (keyModifiers & kEventKeyModifierNumLockMask) {
435 state |= Mod3Mask;
436 }
437 if (keyModifiers & kEventKeyModifierFnMask) {
438 state |= Mod4Mask;
439 }
440
441 return state;
442 }
443
444 /*
445 *----------------------------------------------------------------------
446 *
447 * XQueryPointer --
448 *
449 * Check the current state of the mouse. This is not a complete
450 * implementation of this function. It only computes the root coordinates
451 * and the current mask.
452 *
453 * Results:
454 * Sets root_x_return, root_y_return, and mask_return. Returns true on
455 * success.
456 *
457 * Side effects:
458 * None.
459 *
460 *----------------------------------------------------------------------
461 */
462
463 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)464 XQueryPointer(
465 Display *display,
466 Window w,
467 Window *root_return,
468 Window *child_return,
469 int *root_x_return,
470 int *root_y_return,
471 int *win_x_return,
472 int *win_y_return,
473 unsigned int *mask_return)
474 {
475 int getGlobal = (root_x_return && root_y_return);
476 int getLocal = (win_x_return && win_y_return && w != None);
477
478 if (getGlobal || getLocal) {
479 NSPoint global = [NSEvent mouseLocation];
480
481 if (getLocal) {
482 MacDrawable *macWin = (MacDrawable *)w;
483 NSWindow *win = TkMacOSXGetNSWindowForDrawable(w);
484
485 if (win) {
486 NSPoint local;
487
488 local = [win tkConvertPointFromScreen:global];
489 local.y = [win frame].size.height - local.y;
490 if (macWin->winPtr && macWin->winPtr->wmInfoPtr) {
491 local.x -= macWin->winPtr->wmInfoPtr->xInParent;
492 local.y -= macWin->winPtr->wmInfoPtr->yInParent;
493 }
494 *win_x_return = local.x;
495 *win_y_return = local.y;
496 }
497 }
498 if (getGlobal) {
499 *root_x_return = global.x;
500 *root_y_return = TkMacOSXZeroScreenHeight() - global.y;
501 }
502 }
503 if (mask_return) {
504 *mask_return = TkMacOSXButtonKeyState();
505 }
506 return True;
507 }
508
509 /*
510 *----------------------------------------------------------------------
511 *
512 * TkGenerateButtonEventForXPointer --
513 *
514 * This procedure generates an X button event for the current pointer
515 * state as reported by XQueryPointer().
516 *
517 * Results:
518 * True if event(s) are generated - false otherwise.
519 *
520 * Side effects:
521 * Additional events may be placed on the Tk event queue. Grab state may
522 * also change.
523 *
524 *----------------------------------------------------------------------
525 */
526
527 MODULE_SCOPE int
TkGenerateButtonEventForXPointer(Window window)528 TkGenerateButtonEventForXPointer(
529 Window window) /* X Window containing button event. */
530 {
531 MouseEventData med;
532 int global_x, global_y, local_x, local_y;
533
534 bzero(&med, sizeof(MouseEventData));
535 XQueryPointer(NULL, window, NULL, NULL, &global_x, &global_y,
536 &local_x, &local_y, &med.state);
537 med.global.h = global_x;
538 med.global.v = global_y;
539 med.local.h = local_x;
540 med.local.v = local_y;
541 med.window = window;
542
543 return GenerateButtonEvent(&med);
544 }
545
546 /*
547 *----------------------------------------------------------------------
548 *
549 * TkGenerateButtonEvent --
550 *
551 * Given a global x & y position and the button key status this procedure
552 * generates the appropriate X button event. It also handles the state
553 * changes needed to implement implicit grabs.
554 *
555 * Results:
556 * True if event(s) are generated, false otherwise.
557 *
558 * Side effects:
559 * Additional events may be placed on the Tk event queue. Grab state may
560 * also change.
561 *
562 *----------------------------------------------------------------------
563 */
564
565 int
TkGenerateButtonEvent(int x,int y,Window window,unsigned int state)566 TkGenerateButtonEvent(
567 int x, /* X location of mouse, */
568 int y, /* Y location of mouse. */
569 Window window, /* X Window containing button event. */
570 unsigned int state) /* Button Key state suitable for X event. */
571 {
572 MacDrawable *macWin = (MacDrawable *)window;
573 NSWindow *win = TkMacOSXGetNSWindowForDrawable(window);
574 MouseEventData med;
575
576 bzero(&med, sizeof(MouseEventData));
577 med.state = state;
578 med.window = window;
579 med.global.h = x;
580 med.global.v = y;
581 med.local = med.global;
582
583 if (win) {
584 NSPoint local = NSMakePoint(x, TkMacOSXZeroScreenHeight() - y);
585
586 local = [win tkConvertPointFromScreen:local];
587 local.y = [win frame].size.height - local.y;
588 if (macWin->winPtr && macWin->winPtr->wmInfoPtr) {
589 local.x -= macWin->winPtr->wmInfoPtr->xInParent;
590 local.y -= macWin->winPtr->wmInfoPtr->yInParent;
591 }
592 med.local.h = local.x;
593 med.local.v = TkMacOSXZeroScreenHeight() - local.y;
594 }
595
596 return GenerateButtonEvent(&med);
597 }
598
599 /*
600 *----------------------------------------------------------------------
601 *
602 * GenerateButtonEvent --
603 *
604 * Generate an X button event from a MouseEventData structure. Handles
605 * the state changes needed to implement implicit grabs.
606 *
607 * Results:
608 * True if event(s) are generated - false otherwise.
609 *
610 * Side effects:
611 * Additional events may be placed on the Tk event queue. Grab state may
612 * also change.
613 *
614 *----------------------------------------------------------------------
615 */
616
617 static int
GenerateButtonEvent(MouseEventData * medPtr)618 GenerateButtonEvent(
619 MouseEventData *medPtr)
620 {
621 Tk_Window tkwin;
622 int dummy;
623 TkDisplay *dispPtr;
624
625 #if UNUSED
626
627 /*
628 * ButtonDown events will always occur in the front window. ButtonUp
629 * events, however, may occur anywhere on the screen. ButtonUp events
630 * should only be sent to Tk if in the front window or during an implicit
631 * grab.
632 */
633
634 if ((medPtr->activeNonFloating == NULL)
635 || ((!(TkpIsWindowFloating(medPtr->whichWin))
636 && (medPtr->activeNonFloating != medPtr->whichWin))
637 && TkMacOSXGetCapture() == NULL)) {
638 return false;
639 }
640 #endif
641
642 dispPtr = TkGetDisplayList();
643 tkwin = Tk_IdToWindow(dispPtr->display, medPtr->window);
644
645 if (tkwin != NULL) {
646 tkwin = Tk_TopCoordsToWindow(tkwin, medPtr->local.h, medPtr->local.v,
647 &dummy, &dummy);
648 }
649
650 Tk_UpdatePointer(tkwin, medPtr->global.h, medPtr->global.v, medPtr->state);
651 return true;
652 }
653
654 /*
655 *----------------------------------------------------------------------
656 *
657 * TkpWarpPointer --
658 *
659 * Move the mouse cursor to the screen location specified by the warpX and
660 * warpY fields of a TkDisplay.
661 *
662 * Results:
663 * None
664 *
665 * Side effects:
666 * The mouse cursor is moved.
667 *
668 *----------------------------------------------------------------------
669 */
670
671 void
TkpWarpPointer(TkDisplay * dispPtr)672 TkpWarpPointer(
673 TkDisplay *dispPtr)
674 {
675 CGPoint pt;
676
677 if (dispPtr->warpWindow) {
678 int x, y;
679 Tk_GetRootCoords(dispPtr->warpWindow, &x, &y);
680 pt.x = x + dispPtr->warpX;
681 pt.y = y + dispPtr->warpY;
682 } else {
683 pt.x = dispPtr->warpX;
684 pt.y = dispPtr->warpY;
685 }
686
687 CGWarpMouseCursorPosition(pt);
688
689 if (dispPtr->warpWindow) {
690 TkGenerateButtonEventForXPointer(Tk_WindowId(dispPtr->warpWindow));
691 } else {
692 TkGenerateButtonEventForXPointer(None);
693 }
694 }
695
696 /*
697 *----------------------------------------------------------------------
698 *
699 * TkpSetCapture --
700 *
701 * This function captures the mouse so that all future events will be
702 * reported to this window, even if the mouse is outside the window. If
703 * the specified window is NULL, then the mouse is released.
704 *
705 * Results:
706 * None.
707 *
708 * Side effects:
709 * Sets the capture flag and captures the mouse.
710 *
711 *----------------------------------------------------------------------
712 */
713
714 void
TkpSetCapture(TkWindow * winPtr)715 TkpSetCapture(
716 TkWindow *winPtr) /* Capture window, or NULL. */
717 {
718 while (winPtr && !Tk_IsTopLevel(winPtr)) {
719 winPtr = winPtr->parentPtr;
720 }
721 captureWinPtr = (Tk_Window)winPtr;
722 }
723
724 /*
725 *----------------------------------------------------------------------
726 *
727 * TkMacOSXGetCapture --
728 *
729 * Results:
730 * Returns the current grab window
731 *
732 * Side effects:
733 * None.
734 *
735 *----------------------------------------------------------------------
736 */
737
738 Tk_Window
TkMacOSXGetCapture(void)739 TkMacOSXGetCapture(void)
740 {
741 return captureWinPtr;
742 }
743
744 /*
745 * Local Variables:
746 * mode: objc
747 * c-basic-offset: 4
748 * fill-column: 79
749 * coding: utf-8
750 * End:
751 */
752