Vuo  2.3.2
VuoMouse.m
Go to the documentation of this file.
1 
10 #include "VuoMacOSSDKWorkaround.h"
11 #include <AppKit/AppKit.h>
12 #include <AvailabilityMacros.h>
13 
14 #ifndef MAC_OS_X_VERSION_10_10_3
16 const int NSEventTypePressure = 34;
18 const uint64_t NSEventMaskPressure = 1ULL << NSEventTypePressure;
19 #endif
20 
24 @property (readonly) NSInteger stage;
25 @end
26 
27 #include "VuoMouse.h"
28 #include "VuoGraphicsWindow.h"
29 #include "VuoGraphicsView.h"
30 #include "VuoApp.h"
31 
32 #ifdef VUO_COMPILER
34  "title" : "VuoMouse",
35  "dependencies" : [
36  "VuoBoolean",
37  "VuoGraphicsView",
38  "VuoGraphicsWindow",
39  "VuoModifierKey",
40  "VuoMouseButton",
41  "VuoPoint2d",
42  "VuoWindow",
43  "VuoWindowReference",
44  "AppKit.framework"
45  ]
46  });
47 #endif
48 
49 
55 void VuoMouse_GetScreenDimensions(int64_t *width, int64_t *height)
56 {
57  __block NSSize displayPixelSize;
58  dispatch_sync(dispatch_get_main_queue(), ^{
59  NSDictionary *description = [[NSScreen mainScreen] deviceDescription];
60  displayPixelSize = [description[NSDeviceSize] sizeValue];
61  });
62 
63  *width = (int)displayPixelSize.width;
64  *height = (int)displayPixelSize.height;
65 }
66 
70 static float VuoMouse_getPressure(NSEvent *event)
71 {
72  return event.pressure + MAX(0., event.stage - 1.);
73 }
74 
79 {
80  id monitor;
81  id monitor2;
83 
84  dispatch_queue_t clickQueue;
85  dispatch_group_t clickGroup;
87 
89 
92  void (*zoomed)(VuoReal);
93  void (*swipedLeft)(void);
94  void (*swipedRight)(void);
95 };
96 
101 {
102  // https://b33p.net/kosada/node/11966
103  // Mouse events are only received if the process is in app mode.
104  VuoApp_init(true);
105 
106  struct VuoMouseContext *context = (struct VuoMouseContext *)calloc(1, sizeof(struct VuoMouseContext));
107  VuoRegister(context, free);
108  return (VuoMouse *)context;
109 }
110 
111 
115 static VuoPoint2d VuoMouse_convertWindowToScreenCoordinates(NSPoint pointInWindow, NSWindow *window, bool *shouldFire)
116 {
117  NSRect rectInWindow = NSMakeRect(pointInWindow.x, pointInWindow.y, 0, 0);
118  NSPoint pointInScreen = window ? [window convertRectToScreen:rectInWindow].origin : pointInWindow;
119 
120  *shouldFire = false;
121  NSInteger windowNumberForPoint = [NSWindow windowNumberAtPoint:pointInScreen belowWindowWithWindowNumber:0];
122  for (NSWindow *w in [NSApp windows])
123  {
124  NSRect windowRectInScreen = [w convertRectToScreen:[[w contentView] frame]];
125 
126  // When the mouse is along the very top or right of the screen,
127  // the mouse position reported by +[NSEvent addLocalMonitorForEventsMatchingMask:]
128  // is one pixel _beyond_ the screen bounds.
129  // For example, on a 1920x1200 screen, the mouse can occupy point (0,0) (bottom-left)
130  // through point (1920,1200) (top-right), rather than the expected (1919,1199).
131  // Enlarge the rect to include that extra row/column of pixels.
132  // https://b33p.net/kosada/vuo/vuo/-/issues/18322
133  ++windowRectInScreen.size.width;
134  ++windowRectInScreen.size.height;
135 
136  if ([w windowNumber] == windowNumberForPoint
137  && [w isKindOfClass:[VuoGraphicsWindow class]]
138  && NSPointInRect(pointInScreen, windowRectInScreen))
139  {
140  *shouldFire = true;
141  break;
142  }
143  }
144 
145  pointInScreen.y = [[NSScreen mainScreen] frame].size.height - pointInScreen.y;
146 
147  return VuoPoint2d_make(pointInScreen.x, pointInScreen.y);
148 }
149 
155 static VuoPoint2d VuoMouse_convertViewToVuoCoordinates(NSPoint pointInView, NSView *view)
156 {
157  __block NSRect bounds;
158  __block VuoGraphicsWindow *w;
160  bounds = [view convertRectFromBacking:((VuoGraphicsView *)view).viewport];
161  w = (VuoGraphicsWindow *)[view window];
162  });
163  if (w.isFullScreen)
164  {
165  // If the view is fullscreen, its origin might not line up with the screen's origin
166  // (such as when the window is aspect-locked or size-locked).
167  NSPoint windowOrigin = [w frameRectForContentRect:w.frame].origin;
168  NSPoint screenOrigin = w.screen.frame.origin;
169  bounds.origin.x += windowOrigin.x - screenOrigin.x;
170  bounds.origin.y += windowOrigin.y - screenOrigin.y;
171  }
172 
173  VuoPoint2d pointInVuo;
174  double viewMaxX = NSWidth(bounds) - 1;
175  double viewMaxY = NSHeight(bounds) - 1;
176  pointInVuo.x = ((pointInView.x - NSMinX(bounds) - viewMaxX /2.) * 2.) / viewMaxX;
177  pointInVuo.y = ((pointInView.y - NSMinY(bounds) - viewMaxY/2.) * 2.) / viewMaxX;
178  pointInVuo.y /= (NSWidth(bounds)/viewMaxX) * (viewMaxY/NSHeight(bounds));
179  return pointInVuo;
180 }
181 
185 static VuoPoint2d VuoMouse_convertFullScreenToVuoCoordinates(NSPoint pointInScreen, NSWindow *window, bool *isInScreen)
186 {
187  NSRect fullScreenFrame = [[window screen] frame];
188 
189  // [NSEvent mouseLocation] returns points between [0, screenSize] (inclusive!?)
190  // (i.e., somehow Cocoa invents an extra row and column of pixels).
191  // Scale to [0, screenSize-1],
192  // then quantize to pixels to get exact coordinate values when the mouse hits the edges.
193  pointInScreen.x = VuoReal_snap(pointInScreen.x * fullScreenFrame.size.width / (fullScreenFrame.size.width+1), 0, 1);
194  pointInScreen.y = VuoReal_snap(pointInScreen.y * fullScreenFrame.size.height / (fullScreenFrame.size.height+1), 0, 1);
195 
196  NSView *view = [window contentView];
197  NSPoint pointInView = NSMakePoint(pointInScreen.x - fullScreenFrame.origin.x,
198  pointInScreen.y - fullScreenFrame.origin.y);
199  *isInScreen = NSPointInRect(pointInScreen, fullScreenFrame);
200  return VuoMouse_convertViewToVuoCoordinates(pointInView, view);
201 }
202 
209 VuoPoint2d VuoMouse_convertWindowToVuoCoordinates(NSPoint pointInWindow, NSWindow *window, bool *shouldFire)
210 {
211  // https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Transforms/Transforms.html says:
212  // "Cocoa event objects return y coordinate values that are 1-based (in window coordinates) instead of 0-based.
213  // Thus, a mouse click on the bottom left corner of a window or view would yield the point (0, 1) in Cocoa and not (0, 0).
214  // Only y-coordinates are 1-based."
215  // …so, compensate for that.
216  --pointInWindow.y;
217 
218  // Then quantize to pixels to get exact coordinate values when the mouse hits the edges of the window.
219  pointInWindow.x = VuoReal_snap(pointInWindow.x, 0, 1);
220  pointInWindow.y = VuoReal_snap(pointInWindow.y, 0, 1);
221 
222  __block NSView *view;
223  __block NSPoint pointInView;
224  __block NSRect frame;
226  view = window.contentView;
227  pointInView = [view convertPoint:pointInWindow fromView:nil];
228  frame = view.frame;
229  });
230  *shouldFire = NSPointInRect(pointInView, frame);
231  return VuoMouse_convertViewToVuoCoordinates(pointInView, view);
232 }
233 
237 static VuoPoint2d VuoMouse_convertDeltaToVuoCoordinates(NSPoint delta, NSWindow *window)
238 {
239  NSView *view = [window contentView];
240  NSRect bounds = [view bounds];
241  VuoPoint2d deltaInVuo;
242  deltaInVuo.x = (delta.x * 2.) / bounds.size.width;
243  deltaInVuo.y = -(delta.y * 2.) / bounds.size.width;
244  return deltaInVuo;
245 }
246 
252 {
253  // https://b33p.net/kosada/node/11580
254  // We shouldn't report mouse button presses while resizing the window.
255  // I went through all the data that NSEvent (and the underlying CGEvent) provides,
256  // but couldn't find anything different between normal mouse-down events
257  // and mouse-down events that led to resizing the window.
258  // Disgusting hack: check whether the system has overridden the application's
259  // mouse cursor and turned it into a resize arrow.
260  return !NSEqualPoints([[NSCursor currentCursor] hotSpot], [[NSCursor currentSystemCursor] hotSpot]);
261 }
262 
266 static void VuoMouse_fireScrollDeltaIfNeeded(NSEvent *event, VuoWindowReference windowRef, VuoModifierKey modifierKey, void (^scrolled)(VuoPoint2d))
267 {
268  if (! VuoModifierKey_doMacEventFlagsMatch(CGEventGetFlags([event CGEvent]), modifierKey))
269  return;
270 
271  VuoPoint2d delta = VuoPoint2d_make(-[event deltaX], [event deltaY]);
272  if (fabs(delta.x) < 0.00001 && fabs(delta.y) < 0.00001)
273  return;
274 
275  bool shouldFire = false;
276  NSWindow *targetWindow = (NSWindow *)windowRef;
277  if (targetWindow)
278  {
279  if (targetWindow == [event window])
280  shouldFire = true;
281  }
282  else
283  shouldFire = true;
284 
285  if (shouldFire)
286  scrolled(delta);
287 }
288 
297 static void VuoMouse_fireMousePositionIfNeeded(NSEvent *event, NSPoint fullscreenPoint, VuoWindowReference windowRef, VuoModifierKey modifierKey, bool fireRegardlessOfPosition, bool fireRegardlessOfWindow, void (^fire)(VuoPoint2d))
298 {
299  if (! VuoModifierKey_doMacEventFlagsMatch(CGEventGetFlags([event CGEvent]), modifierKey))
300  return;
301 
302  bool shouldFire = false;
303  VuoPoint2d convertedPoint;
304  NSPoint pointInWindowOrScreen = [event locationInWindow];
305  NSWindow *targetWindow = (NSWindow *)windowRef;
306  if (targetWindow)
307  {
308  __block bool isKeyWindow;
310  isKeyWindow = targetWindow.isKeyWindow;
311  });
312  if (((VuoGraphicsWindow *)targetWindow).isFullScreen)
313  {
314  // https://b33p.net/kosada/node/8489
315  // When in fullscreen mode with multiple displays, -[event locationInWindow] produces inconsistent results,
316  // so use +[NSEvent mouseLocation] which seems to work with both single and multiple displays.
317  pointInWindowOrScreen = fullscreenPoint;
318  convertedPoint = VuoMouse_convertFullScreenToVuoCoordinates(pointInWindowOrScreen, targetWindow, &shouldFire);
319  }
320  else if (targetWindow == [event window])
321  convertedPoint = VuoMouse_convertWindowToVuoCoordinates(pointInWindowOrScreen, targetWindow, &shouldFire);
322  else if ((! [event window] && isKeyWindow) || fireRegardlessOfWindow)
323  {
324  // Workaround (https://b33p.net/kosada/node/7545):
325  // [event window] becomes nil for some reason after mousing over certain other applications.
326  NSRect rectInWindowOrScreen = NSMakeRect(pointInWindowOrScreen.x, pointInWindowOrScreen.y, 0, 0);
327  __block NSPoint pointInWindow;
329  pointInWindow = [targetWindow convertRectFromScreen:rectInWindowOrScreen].origin;
330  });
331  convertedPoint = VuoMouse_convertWindowToVuoCoordinates(pointInWindow, targetWindow, &shouldFire);
332  }
333  else
334  // Our window isn't focused, so ignore this event.
335  return;
336 
337  // Only execute VuoMouse_isResizing if we're going to use the result, since it's expensive.
338  if (!fireRegardlessOfPosition)
339  shouldFire = shouldFire && !VuoMouse_isResizing();
340  }
341  else
342  convertedPoint = VuoMouse_convertWindowToScreenCoordinates(pointInWindowOrScreen, [event window], &shouldFire);
343 
344  if (shouldFire || fireRegardlessOfPosition)
345  fire(convertedPoint);
346 }
347 
356 static void VuoMouse_fireMouseDeltaIfNeeded(NSEvent *event, VuoWindowReference windowRef, VuoModifierKey modifierKey, void (*fire)(VuoPoint2d))
357 {
358  if (! VuoModifierKey_doMacEventFlagsMatch(CGEventGetFlags([event CGEvent]), modifierKey))
359  return;
360 
361  bool shouldFire = false;
362  VuoPoint2d convertedDelta;
363  NSPoint deltaInScreen = NSMakePoint([event deltaX], [event deltaY]);
364  if (fabs(deltaInScreen.x) < 0.0001
365  && fabs(deltaInScreen.y) < 0.0001)
366  return;
367 
368  NSWindow *targetWindow = (NSWindow *)windowRef;
369  if (targetWindow)
370  {
371  if (((VuoGraphicsWindow *)targetWindow).isFullScreen)
372  {
373  // https://b33p.net/kosada/node/8489
374  // When in fullscreen mode with multiple displays, -[event locationInWindow] produces inconsistent results,
375  // so use +[NSEvent mouseLocation] which seems to work with both single and multiple displays.
376  NSPoint pointInWindowOrScreen = [NSEvent mouseLocation];
377  VuoMouse_convertFullScreenToVuoCoordinates(pointInWindowOrScreen, targetWindow, &shouldFire);
378  convertedDelta = VuoMouse_convertDeltaToVuoCoordinates(deltaInScreen, targetWindow);
379  }
380  else if (targetWindow == [event window] || (! [event window] && [targetWindow isKeyWindow]))
381  {
382  // Workaround (https://b33p.net/kosada/node/7545):
383  // [event window] becomes nil for some reason after mousing over certain other applications.
384  shouldFire = true;
385  convertedDelta = VuoMouse_convertDeltaToVuoCoordinates(deltaInScreen, targetWindow);
386  }
387  }
388  else
389  {
390  shouldFire = true;
391  convertedDelta = VuoPoint2d_make(deltaInScreen.x, deltaInScreen.y);
392  }
393 
394  if (shouldFire)
395  fire(convertedDelta);
396 }
397 
406 static void VuoMouse_fireMouseClickIfNeeded(struct VuoMouseContext *context, NSEvent *event, VuoWindowReference windowRef, VuoModifierKey modifierKey,
407  void (*singleClicked)(VuoPoint2d), void (*doubleClicked)(VuoPoint2d), void (*tripleClicked)(VuoPoint2d))
408 {
409  if (! VuoModifierKey_doMacEventFlagsMatch(CGEventGetFlags([event CGEvent]), modifierKey))
410  return;
411 
412  NSInteger clickCount = [event clickCount];
413  NSPoint fullscreenPoint = [NSEvent mouseLocation];
414  NSTimeInterval clickIntervalInSeconds = [NSEvent doubleClickInterval];
415  dispatch_time_t clickInterval = dispatch_time(DISPATCH_TIME_NOW, clickIntervalInSeconds * NSEC_PER_SEC);
416  context->pendingClickCount = clickCount;
417 
418  void (*fired)(VuoPoint2d) = NULL;
419  if (clickCount == 1)
420  fired = singleClicked;
421  else if (clickCount == 2)
422  fired = doubleClicked;
423  else if (clickCount >= 3)
424  fired = tripleClicked;
425  if (! fired)
426  return;
427 
428  dispatch_group_enter(context->clickGroup);
429  dispatch_after(clickInterval, context->clickQueue, ^{
430  if (context->pendingClickCount == clickCount)
431  {
432  VuoMouse_fireMousePositionIfNeeded(event, fullscreenPoint, windowRef, modifierKey, false, false, ^(VuoPoint2d point){ fired(point); });
433  context->pendingClickCount = 0;
434  }
435  dispatch_group_leave(context->clickGroup);
436  });
437 }
438 
439 
445 void VuoMouse_startListeningForScrolls(VuoMouse *mouseListener, void (*scrolled)(VuoPoint2d), VuoWindowReference window, VuoModifierKey modifierKey)
446 {
447  id monitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskScrollWheel handler:^(NSEvent *event) {
448  VuoMouse_fireScrollDeltaIfNeeded(event, window, modifierKey, ^(VuoPoint2d point){ scrolled(point); });
449  return event;
450  }];
451 
452  struct VuoMouseContext *context = (struct VuoMouseContext *)mouseListener;
453  context->monitor = monitor;
454 }
455 
461 void VuoMouse_startListeningForScrollsWithCallback(VuoMouse *mouseListener, void (^scrolled)(VuoPoint2d), VuoWindowReference window, VuoModifierKey modifierKey)
462 {
463  id monitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskScrollWheel handler:^(NSEvent *event) {
464  VuoMouse_fireScrollDeltaIfNeeded(event, window, modifierKey, scrolled);
465  return event;
466  }];
467 
468  struct VuoMouseContext *context = (struct VuoMouseContext *)mouseListener;
469  context->monitor = monitor;
470 }
471 
475 static void VuoMouse_fireInitialEvent(void (^movedTo)(VuoPoint2d), VuoWindowReference window, VuoModifierKey modifierKey)
476 {
477  if (window && (modifierKey == VuoModifierKey_Any || modifierKey == VuoModifierKey_None))
478  {
480 
481  VuoPoint2d position;
482  VuoBoolean isPressed;
483  if (VuoMouse_getStatus(&position, &isPressed, VuoMouseButton_Any, window, VuoModifierKey_Any, false))
484  movedTo(position);
485 
487  }
488 }
489 
497 void VuoMouse_startListeningForMoves(VuoMouse *mouseListener, void (*movedTo)(VuoPoint2d), VuoWindowReference window, VuoModifierKey modifierKey, bool global)
498 {
499  id monitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskMouseMoved|NSEventMaskLeftMouseDragged|NSEventMaskRightMouseDragged|NSEventMaskOtherMouseDragged handler:^(NSEvent *event) {
500  VuoMouse_fireMousePositionIfNeeded(event, [NSEvent mouseLocation], window, modifierKey, true, false, ^(VuoPoint2d point){ movedTo(point); });
501  return event;
502  }];
503 
504  struct VuoMouseContext *context = (struct VuoMouseContext *)mouseListener;
505  context->monitor = monitor;
506 
507  if (global)
508  context->globalMonitor = [NSEvent addGlobalMonitorForEventsMatchingMask:NSEventMaskMouseMoved|NSEventMaskLeftMouseDragged|NSEventMaskRightMouseDragged|NSEventMaskOtherMouseDragged handler:^(NSEvent *event) {
509  VuoMouse_fireMousePositionIfNeeded(event, [NSEvent mouseLocation], window, modifierKey, true, true, ^(VuoPoint2d point){ movedTo(point); });
510  }];
511 
512  VuoMouse_fireInitialEvent(^(VuoPoint2d point){ movedTo(point); }, window, modifierKey);
513 }
514 
522 void VuoMouse_startListeningForMovesWithCallback(VuoMouse *mouseListener, void (^movedTo)(VuoPoint2d),
524 {
525  id monitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskMouseMoved|NSEventMaskLeftMouseDragged|NSEventMaskRightMouseDragged|NSEventMaskOtherMouseDragged handler:^(NSEvent *event) {
526  VuoMouse_fireMousePositionIfNeeded(event, [NSEvent mouseLocation], window, modifierKey, true, false, movedTo);
527  return event;
528  }];
529 
530  struct VuoMouseContext *context = (struct VuoMouseContext *)mouseListener;
531  context->monitor = monitor;
532 
533  VuoMouse_fireInitialEvent(movedTo, window, modifierKey);
534 }
535 
541 void VuoMouse_startListeningForDeltas(VuoMouse *mouseListener, void (*movedBy)(VuoPoint2d), VuoWindowReference window, VuoModifierKey modifierKey)
542 {
543  id monitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskMouseMoved|NSEventMaskLeftMouseDragged|NSEventMaskRightMouseDragged|NSEventMaskOtherMouseDragged handler:^(NSEvent *event) {
544  VuoMouse_fireMouseDeltaIfNeeded(event, window, modifierKey, movedBy);
545  return event;
546  }];
547 
548  struct VuoMouseContext *context = (struct VuoMouseContext *)mouseListener;
549  context->monitor = monitor;
550 }
551 
557 void VuoMouse_startListeningForDragsWithCallback(VuoMouse *mouseListener, void (^dragMovedTo)(VuoPoint2d),
558  VuoMouseButton button, VuoWindowReference window, VuoModifierKey modifierKey, bool fireRegardlessOfPosition)
559 {
560  struct VuoMouseContext *context = (struct VuoMouseContext *)mouseListener;
561 
562  NSEventMask eventMask = 0;
563  switch (button)
564  {
565  case VuoMouseButton_Left:
566  eventMask = NSEventMaskLeftMouseDragged;
567  break;
568  case VuoMouseButton_Middle:
569  eventMask = NSEventMaskOtherMouseDragged;
570  break;
571  case VuoMouseButton_Right:
572  eventMask = NSEventMaskRightMouseDragged;
573 
574  // Also fire events for control-leftclicks.
575  context->monitor2 = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseDragged handler:^(NSEvent *event) {
576  VuoMouse_fireMousePositionIfNeeded(event, [NSEvent mouseLocation], window, VuoModifierKey_Control, fireRegardlessOfPosition, false, dragMovedTo);
577  return event;
578  }];
579 
580  break;
581  case VuoMouseButton_Any:
582  eventMask = NSEventMaskLeftMouseDragged | NSEventMaskOtherMouseDragged | NSEventMaskRightMouseDragged;
583  break;
584  }
585 
586  context->monitor = [NSEvent addLocalMonitorForEventsMatchingMask:eventMask handler:^(NSEvent *event) {
587  VuoMouse_fireMousePositionIfNeeded(event, [NSEvent mouseLocation], window, modifierKey, fireRegardlessOfPosition, false, dragMovedTo);
588  return event;
589  }];
590 }
591 
597 void VuoMouse_startListeningForDrags(VuoMouse *mouseListener, void (*dragMovedTo)(VuoPoint2d), VuoMouseButton button, VuoWindowReference window, VuoModifierKey modifierKey)
598 {
599  VuoMouse_startListeningForDragsWithCallback(mouseListener, ^(VuoPoint2d point){ dragMovedTo(point); }, button, window, modifierKey, true);
600 }
601 
609 void VuoMouse_startListeningForPressesWithCallback(VuoMouse *mouseListener, void (^pressed)(VuoPoint2d), void (^forcePressed)(VuoPoint2d),
611 {
612  struct VuoMouseContext *context = (struct VuoMouseContext *)mouseListener;
613 
614  NSEventMask eventMask = 0;
615  switch (button)
616  {
617  case VuoMouseButton_Left:
618  eventMask = NSEventMaskLeftMouseDown;
619  break;
620  case VuoMouseButton_Middle:
621  eventMask = NSEventMaskOtherMouseDown;
622  break;
623  case VuoMouseButton_Right:
624  eventMask = NSEventMaskRightMouseDown;
625 
626  // Also fire events for control-leftclicks.
627  context->monitor2 = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseDown handler:^(NSEvent *event) {
628  VuoMouse_fireMousePositionIfNeeded(event, [NSEvent mouseLocation], window, VuoModifierKey_Control, false, false, pressed);
629  return event;
630  }];
631 
632  break;
633  case VuoMouseButton_Any:
634  eventMask = NSEventMaskLeftMouseDown | NSEventMaskOtherMouseDown | NSEventMaskRightMouseDown;
635  break;
636  }
637 
638  eventMask |= NSEventMaskPressure;
639 
640  context->monitor = [NSEvent addLocalMonitorForEventsMatchingMask:eventMask handler:^(NSEvent *event) {
641  if (event.type == NSEventTypePressure)
642  {
643  if (context->priorPressureStage == 1 && event.stage == 2 && forcePressed)
644  VuoMouse_fireMousePositionIfNeeded(event, [NSEvent mouseLocation], window, modifierKey, false, false, forcePressed);
645  context->priorPressureStage = event.stage;
646  }
647  else
648  VuoMouse_fireMousePositionIfNeeded(event, [NSEvent mouseLocation], window, modifierKey, false, false, pressed);
649  return event;
650  }];
651 }
652 
660 void VuoMouse_startListeningForPresses(VuoMouse *mouseListener, void (*pressed)(VuoPoint2d), void (*forcePressed)(VuoPoint2d),
662 {
664  ^(VuoPoint2d point){ pressed(point); },
665  ^(VuoPoint2d point){ forcePressed(point); },
666  button, window, modifierKey);
667 }
668 
676 void VuoMouse_startListeningForPressureChanges(VuoMouse *mouseListener, void (*pressureChanged)(VuoReal), VuoMouseButton button, VuoModifierKey modifierKey)
677 {
678  struct VuoMouseContext *context = (struct VuoMouseContext *)mouseListener;
679  context->monitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskPressure handler:^(NSEvent *event) {
680  if (VuoModifierKey_doMacEventFlagsMatch(CGEventGetFlags([event CGEvent]), modifierKey)
681  && (button == VuoMouseButton_Any
682  || (button == VuoMouseButton_Left && NSEvent.pressedMouseButtons & 1)
683  || (button == VuoMouseButton_Right && NSEvent.pressedMouseButtons & 2)
684  || (button == VuoMouseButton_Middle && NSEvent.pressedMouseButtons & 4)))
685  pressureChanged(VuoMouse_getPressure(event));
686  return event;
687  }];
688 }
689 
695 void VuoMouse_startListeningForReleasesWithCallback(VuoMouse *mouseListener, void (^released)(VuoPoint2d),
696  VuoMouseButton button, VuoWindowReference window, VuoModifierKey modifierKey, bool fireRegardlessOfPosition)
697 {
698  struct VuoMouseContext *context = (struct VuoMouseContext *)mouseListener;
699 
700  NSEventMask eventMask = 0;
701  switch (button)
702  {
703  case VuoMouseButton_Left:
704  eventMask = NSEventMaskLeftMouseUp;
705  break;
706  case VuoMouseButton_Middle:
707  eventMask = NSEventMaskOtherMouseUp;
708  break;
709  case VuoMouseButton_Right:
710  eventMask = NSEventMaskRightMouseUp;
711 
712  // Also fire events for control-leftclicks.
713  context->monitor2 = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseUp handler:^(NSEvent *event) {
714  VuoMouse_fireMousePositionIfNeeded(event, [NSEvent mouseLocation], window, VuoModifierKey_Control, fireRegardlessOfPosition, false, released);
715  return event;
716  }];
717 
718  break;
719  case VuoMouseButton_Any:
720  eventMask = NSEventMaskLeftMouseUp | NSEventMaskOtherMouseUp | NSEventMaskRightMouseUp;
721  break;
722  }
723 
724  context->monitor = [NSEvent addLocalMonitorForEventsMatchingMask:eventMask handler:^(NSEvent *event) {
725  VuoMouse_fireMousePositionIfNeeded(event, [NSEvent mouseLocation], window, modifierKey, fireRegardlessOfPosition, false, released);
726  return event;
727  }];
728 }
729 
735 void VuoMouse_startListeningForReleases(VuoMouse *mouseListener, void (*released)(VuoPoint2d), VuoMouseButton button, VuoWindowReference window, VuoModifierKey modifierKey, bool fireRegardlessOfPosition)
736 {
737  VuoMouse_startListeningForReleasesWithCallback(mouseListener, ^(VuoPoint2d point){ released(point); }, button, window, modifierKey, fireRegardlessOfPosition);
738 }
739 
745 void VuoMouse_startListeningForClicks(VuoMouse *mouseListener, void (*singleClicked)(VuoPoint2d), void (*doubleClicked)(VuoPoint2d), void (*tripleClicked)(VuoPoint2d),
747 {
748  struct VuoMouseContext *context = (struct VuoMouseContext *)mouseListener;
749  context->clickQueue = dispatch_queue_create("vuo.mouse.click", 0);
750  context->clickGroup = dispatch_group_create();
751  context->pendingClickCount = 0;
752 
753  NSEventMask eventMask = 0;
754  switch (button)
755  {
756  case VuoMouseButton_Left:
757  eventMask = NSEventMaskLeftMouseUp;
758  break;
759  case VuoMouseButton_Middle:
760  eventMask = NSEventMaskOtherMouseUp;
761  break;
762  case VuoMouseButton_Right:
763  eventMask = NSEventMaskRightMouseUp;
764 
765  // Also fire events for control-leftclicks.
766  context->monitor2 = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskLeftMouseUp handler:^(NSEvent *event) {
767  VuoMouse_fireMouseClickIfNeeded(context, event, window, VuoModifierKey_Control, singleClicked, doubleClicked, tripleClicked);
768  return event;
769  }];
770 
771  break;
772  case VuoMouseButton_Any:
773  eventMask = NSEventMaskLeftMouseUp | NSEventMaskOtherMouseUp | NSEventMaskRightMouseUp;
774  break;
775  }
776 
777  context->monitor = [NSEvent addLocalMonitorForEventsMatchingMask:eventMask handler:^(NSEvent *event) {
778  VuoMouse_fireMouseClickIfNeeded(context, event, window, modifierKey, singleClicked, doubleClicked, tripleClicked);
779  return event;
780  }];
781 }
782 
792  void (*zoomed)(VuoReal),
793  void (*swipedLeft)(void),
794  void (*swipedRight)(void),
795  VuoWindowReference windowRef)
796 {
797  struct VuoMouseContext *context = (struct VuoMouseContext *)mouseListener;
798  context->touchesMoved = touchesMoved;
799  context->zoomed = zoomed;
800  context->swipedLeft = swipedLeft;
801  context->swipedRight = swipedRight;
802  context->window = windowRef;
804  VuoGraphicsView *view = (VuoGraphicsView *)window.contentView;
805  [view addTouchesMovedTrigger:touchesMoved zoomed:zoomed swipedLeft:swipedLeft swipedRight:swipedRight];
806 }
807 
813 void VuoMouse_stopListening(VuoMouse *mouseListener)
814 {
815  struct VuoMouseContext *context = (struct VuoMouseContext *)mouseListener;
816 
817  VUOLOG_PROFILE_BEGIN(mainQueue);
818  VuoApp_executeOnMainThread(^{ // wait for any in-progress monitor handlers to complete
819  VUOLOG_PROFILE_END(mainQueue);
820  if (context->monitor)
821  {
822  [NSEvent removeMonitor:context->monitor];
823  context->monitor = nil;
824 
825  if (context->monitor2)
826  {
827  [NSEvent removeMonitor:context->monitor2];
828  context->monitor2 = nil;
829  }
830 
831  if (context->globalMonitor)
832  {
833  [NSEvent removeMonitor:context->globalMonitor];
834  context->globalMonitor = nil;
835  }
836  }
837  });
838 
839  if (context->clickGroup)
840  {
841  dispatch_group_wait(context->clickGroup, DISPATCH_TIME_FOREVER); // wait for any pending click events to complete
842  dispatch_release(context->clickQueue);
843  dispatch_release(context->clickGroup);
844  context->clickQueue = NULL;
845  context->clickGroup = NULL;
846  }
847 
848  if (context->touchesMoved)
849  {
851  VuoGraphicsView *view = (VuoGraphicsView *)window.contentView;
852  [view removeTouchesMovedTrigger:context->touchesMoved zoomed:context->zoomed swipedLeft:context->swipedLeft swipedRight:context->swipedRight];
853  context->window = NULL;
854  context->touchesMoved = NULL;
855  context->zoomed = NULL;
856  context->swipedLeft = NULL;
857  context->swipedRight = NULL;
858  }
859 }
860 
861 static unsigned int VuoMouseStatus_useCount = 0;
862 static id VuoMouseStatus_monitor = nil;
863 static NSPoint VuoMouseStatus_position;
864 static bool VuoMouseStatus_leftButton = false;
865 static bool VuoMouseStatus_middleButton = false;
866 static bool VuoMouseStatus_rightButton = false;
867 
874 {
875  if (__sync_add_and_fetch(&VuoMouseStatus_useCount, 1) == 1)
876  {
877  // https://b33p.net/kosada/node/11966
878  // Mouse events are only received if the process is in app mode.
879  VuoApp_init(true);
880 
881  // Ensure +[NSEvent mouseLocation] is called for the first time on the main thread, else we get console message
882  // "!!! BUG: The current event queue and the main event queue are not the same. Events will not be handled correctly. This is probably because _TSGetMainThread was called for the first time off the main thread."
884  VuoMouseStatus_position = [NSEvent mouseLocation];
885  });
889  VuoMouseStatus_monitor = [NSEvent addLocalMonitorForEventsMatchingMask:
890  NSEventMaskMouseMoved
891  |NSEventMaskLeftMouseDragged|NSEventMaskRightMouseDragged|NSEventMaskOtherMouseDragged
892  |NSEventMaskLeftMouseDown|NSEventMaskOtherMouseDown|NSEventMaskRightMouseDown
893  |NSEventMaskLeftMouseUp|NSEventMaskOtherMouseUp|NSEventMaskRightMouseUp
894  handler:^(NSEvent *event) {
895  // Despite the name, -locationInWindow sometimes returns screen coordinates.
896  NSPoint p = [event locationInWindow];
897  NSWindow *window = [event window];
898  if (window)
899  {
900  NSRect rectInWindow = NSMakeRect(p.x, p.y, 0, 0);
901  VuoMouseStatus_position = [window convertRectToScreen:rectInWindow].origin;
902  }
903  else
904  VuoMouseStatus_position = p;
905 
906  switch ([event type])
907  {
908  case NSEventTypeLeftMouseDown: if (!VuoMouse_isResizing()) VuoMouseStatus_leftButton = true; break;
909  case NSEventTypeLeftMouseUp: VuoMouseStatus_leftButton = false; break;
910  case NSEventTypeOtherMouseDown: if (!VuoMouse_isResizing()) VuoMouseStatus_middleButton = true; break;
911  case NSEventTypeOtherMouseUp: VuoMouseStatus_middleButton = false; break;
912  case NSEventTypeRightMouseDown: if (!VuoMouse_isResizing()) VuoMouseStatus_rightButton = true; break;
913  case NSEventTypeRightMouseUp: VuoMouseStatus_rightButton = false; break;
914  default: break;
915  }
916 
917  return event;
918  }];
919  }
920 }
921 
928 {
929  if (__sync_sub_and_fetch(&VuoMouseStatus_useCount, 1) == 0)
930  {
931  [NSEvent removeMonitor:VuoMouseStatus_monitor];
933  }
934 }
935 
951 bool VuoMouse_getStatus(VuoPoint2d *position, VuoBoolean *isPressed,
952  VuoMouseButton button, VuoWindowReference windowRef, VuoModifierKey modifierKey,
953  bool onlyUpdateWhenActive)
954 {
955  if (onlyUpdateWhenActive)
956  {
957  __block bool isActive;
959  isActive = [NSApp isActive];
960  });
961  if (!isActive)
962  return false;
963  }
964 
965  bool shouldTrackPresses;
966  if (windowRef)
967  {
968  VuoGraphicsWindow *targetWindow = (VuoGraphicsWindow *)windowRef;
969  NSPoint pointInScreen = VuoMouseStatus_position;
970 
971  if (((VuoGraphicsWindow *)targetWindow).isFullScreen)
972  *position = VuoMouse_convertFullScreenToVuoCoordinates(pointInScreen, targetWindow, &shouldTrackPresses);
973  else
974  {
975  NSRect rectInScreen = NSMakeRect(pointInScreen.x, pointInScreen.y, 0, 0);
976  __block NSPoint pointInWindow;
977  __block bool isKeyWindow;
979  pointInWindow = [targetWindow convertRectFromScreen:rectInScreen].origin;
980  isKeyWindow = targetWindow.isKeyWindow;
981  });
982  *position = VuoMouse_convertWindowToVuoCoordinates(pointInWindow, targetWindow, &shouldTrackPresses);
983  shouldTrackPresses = shouldTrackPresses && isKeyWindow;
984  }
985  }
986  else
987  {
988  NSPoint pointInScreen = VuoMouseStatus_position;
989  pointInScreen.y = CGDisplayPixelsHigh(CGMainDisplayID()) - pointInScreen.y;
990  *position = VuoPoint2d_make(pointInScreen.x, pointInScreen.y);
991 
992  shouldTrackPresses = true;
993  }
994 
995  if ( !shouldTrackPresses)
996  {
997  if (*isPressed)
998  {
999  // The existing button state is pressed.
1000  // If the button been released, we should update isPressed to reflect that release (even though we're ignoring presses).
1001  }
1002  else
1003  // The existing button state is not-pressed. Since we're ignoring presses, we shouldn't change it.
1004  return true;
1005  }
1006 
1007  *isPressed = false;
1008  switch (button)
1009  {
1010  case VuoMouseButton_Left:
1011  *isPressed = VuoMouseStatus_leftButton;
1012  break;
1013  case VuoMouseButton_Right:
1014  *isPressed = VuoMouseStatus_rightButton;
1015 
1016  // Also return true for control-leftclicks.
1017  if (VuoMouseStatus_leftButton && VuoModifierKey_doMacEventFlagsMatch([NSEvent modifierFlags], VuoModifierKey_Control))
1018  {
1019  *isPressed = true;
1020  return true;
1021  }
1022 
1023  break;
1024  case VuoMouseButton_Middle:
1025  *isPressed = VuoMouseStatus_middleButton;
1026  break;
1027  case VuoMouseButton_Any:
1029  break;
1030  }
1031  *isPressed = *isPressed && VuoModifierKey_doMacEventFlagsMatch([NSEvent modifierFlags], modifierKey);
1032 
1033  return true;
1034 }