Vuo  2.0.0
VuoMouse.m
Go to the documentation of this file.
1 
10 #ifndef NS_RETURNS_INNER_POINTER
11 #define NS_RETURNS_INNER_POINTER
12 #endif
13 #include <AppKit/AppKit.h>
14 #include <AvailabilityMacros.h>
15 
16 #ifndef MAC_OS_X_VERSION_10_10_3
17 const int NSEventTypePressure = 34;
20 const uint64_t NSEventMaskPressure = 1ULL << NSEventTypePressure;
21 #endif
22 
26 @property (readonly) NSInteger stage;
27 @end
28 
29 #include "VuoMouse.h"
30 #include "VuoGraphicsWindow.h"
31 #include "VuoGraphicsView.h"
32 #include "VuoApp.h"
33 
34 #ifdef VUO_COMPILER
36  "title" : "VuoMouse",
37  "dependencies" : [
38  "VuoBoolean",
39  "VuoGraphicsView",
40  "VuoGraphicsWindow",
41  "VuoModifierKey",
42  "VuoMouseButton",
43  "VuoPoint2d",
44  "VuoWindow",
45  "VuoWindowReference",
46  "AppKit.framework"
47  ]
48  });
49 #endif
50 
51 
57 void VuoMouse_GetScreenDimensions(int64_t *width, int64_t *height)
58 {
59  __block NSSize displayPixelSize;
60  dispatch_sync(dispatch_get_main_queue(), ^{
61  NSDictionary *description = [[NSScreen mainScreen] deviceDescription];
62  displayPixelSize = [description[NSDeviceSize] sizeValue];
63  });
64 
65  *width = (int)displayPixelSize.width;
66  *height = (int)displayPixelSize.height;
67 }
68 
72 static float VuoMouse_getPressure(NSEvent *event)
73 {
74  return event.pressure + MAX(0., event.stage - 1.);
75 }
76 
81 {
82  id monitor;
83  id monitor2;
84 
85  dispatch_queue_t clickQueue;
86  dispatch_group_t clickGroup;
88 
90 
93  void (*zoomed)(VuoReal);
94  void (*swipedLeft)(void);
95  void (*swipedRight)(void);
96 };
97 
102 {
103  // https://b33p.net/kosada/node/11966
104  // Mouse events are only received if the process is in app mode.
105  VuoApp_init(true);
106 
107  struct VuoMouseContext *context = (struct VuoMouseContext *)calloc(1, sizeof(struct VuoMouseContext));
108  VuoRegister(context, free);
109  return (VuoMouse *)context;
110 }
111 
112 
116 static VuoPoint2d VuoMouse_convertWindowToScreenCoordinates(NSPoint pointInWindow, NSWindow *window, bool *shouldFire)
117 {
118  NSRect rectInWindow = NSMakeRect(pointInWindow.x, pointInWindow.y, 0, 0);
119  NSPoint pointInScreen = window ? [window convertRectToScreen:rectInWindow].origin : pointInWindow;
120 
121  *shouldFire = false;
122  NSInteger windowNumberForPoint = [NSWindow windowNumberAtPoint:pointInScreen belowWindowWithWindowNumber:0];
123  for (NSWindow *w in [NSApp windows])
124  if ([w windowNumber] == windowNumberForPoint
125  && [w isKindOfClass:[VuoGraphicsWindow class]]
126  && NSPointInRect(pointInScreen, [w convertRectToScreen:[[w contentView] frame]]))
127  {
128  *shouldFire = true;
129  break;
130  }
131 
132  pointInScreen.y = [[NSScreen mainScreen] frame].size.height - pointInScreen.y;
133 
134  return VuoPoint2d_make(pointInScreen.x, pointInScreen.y);
135 }
136 
142 static VuoPoint2d VuoMouse_convertViewToVuoCoordinates(NSPoint pointInView, NSView *view)
143 {
144  __block NSRect bounds;
145  __block VuoGraphicsWindow *w;
147  bounds = [view convertRectFromBacking:((VuoGraphicsView *)view).viewport];
148  w = (VuoGraphicsWindow *)[view window];
149  });
150  if (w.isFullScreen)
151  {
152  // If the view is fullscreen, its origin might not line up with the screen's origin
153  // (such as when the window is aspect-locked or size-locked).
154  NSPoint windowOrigin = [w frameRectForContentRect:w.frame].origin;
155  NSPoint screenOrigin = w.screen.frame.origin;
156  bounds.origin.x += windowOrigin.x - screenOrigin.x;
157  bounds.origin.y += windowOrigin.y - screenOrigin.y;
158  }
159 
160  VuoPoint2d pointInVuo;
161  double viewMaxX = NSWidth(bounds) - 1;
162  double viewMaxY = NSHeight(bounds) - 1;
163  pointInVuo.x = ((pointInView.x - NSMinX(bounds) - viewMaxX /2.) * 2.) / viewMaxX;
164  pointInVuo.y = ((pointInView.y - NSMinY(bounds) - viewMaxY/2.) * 2.) / viewMaxX;
165  pointInVuo.y /= (NSWidth(bounds)/viewMaxX) * (viewMaxY/NSHeight(bounds));
166  return pointInVuo;
167 }
168 
172 static VuoPoint2d VuoMouse_convertFullScreenToVuoCoordinates(NSPoint pointInScreen, NSWindow *window, bool *isInScreen)
173 {
174  NSRect fullScreenFrame = [[window screen] frame];
175 
176  // [NSEvent mouseLocation] returns points between [0, screenSize] (inclusive!?)
177  // (i.e., somehow Cocoa invents an extra row and column of pixels).
178  // Scale to [0, screenSize-1],
179  // then quantize to pixels to get exact coordinate values when the mouse hits the edges.
180  pointInScreen.x = VuoReal_snap(pointInScreen.x * fullScreenFrame.size.width / (fullScreenFrame.size.width+1), 0, 1);
181  pointInScreen.y = VuoReal_snap(pointInScreen.y * fullScreenFrame.size.height / (fullScreenFrame.size.height+1), 0, 1);
182 
183  NSView *view = [window contentView];
184  NSPoint pointInView = NSMakePoint(pointInScreen.x - fullScreenFrame.origin.x,
185  pointInScreen.y - fullScreenFrame.origin.y);
186  *isInScreen = NSPointInRect(pointInScreen, fullScreenFrame);
187  return VuoMouse_convertViewToVuoCoordinates(pointInView, view);
188 }
189 
196 VuoPoint2d VuoMouse_convertWindowToVuoCoordinates(NSPoint pointInWindow, NSWindow *window, bool *shouldFire)
197 {
198  // https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/CocoaDrawingGuide/Transforms/Transforms.html says:
199  // "Cocoa event objects return y coordinate values that are 1-based (in window coordinates) instead of 0-based.
200  // 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).
201  // Only y-coordinates are 1-based."
202  // …so, compensate for that.
203  --pointInWindow.y;
204 
205  // Then quantize to pixels to get exact coordinate values when the mouse hits the edges of the window.
206  pointInWindow.x = VuoReal_snap(pointInWindow.x, 0, 1);
207  pointInWindow.y = VuoReal_snap(pointInWindow.y, 0, 1);
208 
209  __block NSView *view;
210  __block NSPoint pointInView;
211  __block NSRect frame;
213  view = window.contentView;
214  pointInView = [view convertPoint:pointInWindow fromView:nil];
215  frame = view.frame;
216  });
217  *shouldFire = NSPointInRect(pointInView, frame);
218  return VuoMouse_convertViewToVuoCoordinates(pointInView, view);
219 }
220 
224 static VuoPoint2d VuoMouse_convertDeltaToVuoCoordinates(NSPoint delta, NSWindow *window)
225 {
226  NSView *view = [window contentView];
227  NSRect bounds = [view bounds];
228  VuoPoint2d deltaInVuo;
229  deltaInVuo.x = (delta.x * 2.) / bounds.size.width;
230  deltaInVuo.y = -(delta.y * 2.) / bounds.size.width;
231  return deltaInVuo;
232 }
233 
239 {
240  // https://b33p.net/kosada/node/11580
241  // We shouldn't report mouse button presses while resizing the window.
242  // I went through all the data that NSEvent (and the underlying CGEvent) provides,
243  // but couldn't find anything different between normal mouse-down events
244  // and mouse-down events that led to resizing the window.
245  // Disgusting hack: check whether the system has overridden the application's
246  // mouse cursor and turned it into a resize arrow.
247  return !NSEqualPoints([[NSCursor currentCursor] hotSpot], [[NSCursor currentSystemCursor] hotSpot]);
248 }
249 
253 static void VuoMouse_fireScrollDeltaIfNeeded(NSEvent *event, VuoWindowReference windowRef, VuoModifierKey modifierKey, void (^scrolled)(VuoPoint2d))
254 {
255  if (! VuoModifierKey_doMacEventFlagsMatch(CGEventGetFlags([event CGEvent]), modifierKey))
256  return;
257 
258  VuoPoint2d delta = VuoPoint2d_make(-[event deltaX], [event deltaY]);
259  if (fabs(delta.x) < 0.00001 && fabs(delta.y) < 0.00001)
260  return;
261 
262  bool shouldFire = false;
263  NSWindow *targetWindow = (NSWindow *)windowRef;
264  if (targetWindow)
265  {
266  if (targetWindow == [event window])
267  shouldFire = true;
268  }
269  else
270  shouldFire = true;
271 
272  if (shouldFire)
273  scrolled(delta);
274 }
275 
284 static void VuoMouse_fireMousePositionIfNeeded(NSEvent *event, NSPoint fullscreenPoint, VuoWindowReference windowRef, VuoModifierKey modifierKey, bool fireRegardlessOfPosition, void (^fire)(VuoPoint2d))
285 {
286  if (! VuoModifierKey_doMacEventFlagsMatch(CGEventGetFlags([event CGEvent]), modifierKey))
287  return;
288 
289  bool shouldFire = false;
290  VuoPoint2d convertedPoint;
291  NSPoint pointInWindowOrScreen = [event locationInWindow];
292  NSWindow *targetWindow = (NSWindow *)windowRef;
293  if (targetWindow)
294  {
295  __block bool isKeyWindow;
297  isKeyWindow = targetWindow.isKeyWindow;
298  });
299  if (((VuoGraphicsWindow *)targetWindow).isFullScreen)
300  {
301  // https://b33p.net/kosada/node/8489
302  // When in fullscreen mode with multiple displays, -[event locationInWindow] produces inconsistent results,
303  // so use +[NSEvent mouseLocation] which seems to work with both single and multiple displays.
304  pointInWindowOrScreen = fullscreenPoint;
305  convertedPoint = VuoMouse_convertFullScreenToVuoCoordinates(pointInWindowOrScreen, targetWindow, &shouldFire);
306  }
307  else if (targetWindow == [event window])
308  convertedPoint = VuoMouse_convertWindowToVuoCoordinates(pointInWindowOrScreen, targetWindow, &shouldFire);
309  else if (! [event window] && isKeyWindow)
310  {
311  // Workaround (https://b33p.net/kosada/node/7545):
312  // [event window] becomes nil for some reason after mousing over certain other applications.
313  NSRect rectInWindowOrScreen = NSMakeRect(pointInWindowOrScreen.x, pointInWindowOrScreen.y, 0, 0);
314  __block NSPoint pointInWindow;
316  pointInWindow = [targetWindow convertRectFromScreen:rectInWindowOrScreen].origin;
317  });
318  convertedPoint = VuoMouse_convertWindowToVuoCoordinates(pointInWindow, targetWindow, &shouldFire);
319  }
320  else
321  // Our window isn't focused, so ignore this event.
322  return;
323 
324  // Only execute VuoMouse_isResizing if we're going to use the result, since it's expensive.
325  if (!fireRegardlessOfPosition)
326  shouldFire = shouldFire && !VuoMouse_isResizing();
327  }
328  else
329  convertedPoint = VuoMouse_convertWindowToScreenCoordinates(pointInWindowOrScreen, [event window], &shouldFire);
330 
331  if (shouldFire || fireRegardlessOfPosition)
332  fire(convertedPoint);
333 }
334 
343 static void VuoMouse_fireMouseDeltaIfNeeded(NSEvent *event, VuoWindowReference windowRef, VuoModifierKey modifierKey, void (*fire)(VuoPoint2d))
344 {
345  if (! VuoModifierKey_doMacEventFlagsMatch(CGEventGetFlags([event CGEvent]), modifierKey))
346  return;
347 
348  bool shouldFire = false;
349  VuoPoint2d convertedDelta;
350  NSPoint deltaInScreen = NSMakePoint([event deltaX], [event deltaY]);
351  if (fabs(deltaInScreen.x) < 0.0001
352  && fabs(deltaInScreen.y) < 0.0001)
353  return;
354 
355  NSWindow *targetWindow = (NSWindow *)windowRef;
356  if (targetWindow)
357  {
358  if (((VuoGraphicsWindow *)targetWindow).isFullScreen)
359  {
360  // https://b33p.net/kosada/node/8489
361  // When in fullscreen mode with multiple displays, -[event locationInWindow] produces inconsistent results,
362  // so use +[NSEvent mouseLocation] which seems to work with both single and multiple displays.
363  NSPoint pointInWindowOrScreen = [NSEvent mouseLocation];
364  VuoMouse_convertFullScreenToVuoCoordinates(pointInWindowOrScreen, targetWindow, &shouldFire);
365  convertedDelta = VuoMouse_convertDeltaToVuoCoordinates(deltaInScreen, targetWindow);
366  }
367  else if (targetWindow == [event window] || (! [event window] && [targetWindow isKeyWindow]))
368  {
369  // Workaround (https://b33p.net/kosada/node/7545):
370  // [event window] becomes nil for some reason after mousing over certain other applications.
371  shouldFire = true;
372  convertedDelta = VuoMouse_convertDeltaToVuoCoordinates(deltaInScreen, targetWindow);
373  }
374  }
375  else
376  {
377  shouldFire = true;
378  convertedDelta = VuoPoint2d_make(deltaInScreen.x, deltaInScreen.y);
379  }
380 
381  if (shouldFire)
382  fire(convertedDelta);
383 }
384 
393 static void VuoMouse_fireMouseClickIfNeeded(struct VuoMouseContext *context, NSEvent *event, VuoWindowReference windowRef, VuoModifierKey modifierKey,
394  void (*singleClicked)(VuoPoint2d), void (*doubleClicked)(VuoPoint2d), void (*tripleClicked)(VuoPoint2d))
395 {
396  if (! VuoModifierKey_doMacEventFlagsMatch(CGEventGetFlags([event CGEvent]), modifierKey))
397  return;
398 
399  NSInteger clickCount = [event clickCount];
400  NSPoint fullscreenPoint = [NSEvent mouseLocation];
401  NSTimeInterval clickIntervalInSeconds = [NSEvent doubleClickInterval];
402  dispatch_time_t clickInterval = dispatch_time(DISPATCH_TIME_NOW, clickIntervalInSeconds * NSEC_PER_SEC);
403  context->pendingClickCount = clickCount;
404 
405  void (*fired)(VuoPoint2d) = NULL;
406  if (clickCount == 1)
407  fired = singleClicked;
408  else if (clickCount == 2)
409  fired = doubleClicked;
410  else if (clickCount >= 3)
411  fired = tripleClicked;
412  if (! fired)
413  return;
414 
415  dispatch_group_enter(context->clickGroup);
416  dispatch_after(clickInterval, context->clickQueue, ^{
417  if (context->pendingClickCount == clickCount)
418  {
419  VuoMouse_fireMousePositionIfNeeded(event, fullscreenPoint, windowRef, modifierKey, false, ^(VuoPoint2d point){ fired(point); });
420  context->pendingClickCount = 0;
421  }
422  dispatch_group_leave(context->clickGroup);
423  });
424 }
425 
426 
432 void VuoMouse_startListeningForScrolls(VuoMouse *mouseListener, void (*scrolled)(VuoPoint2d), VuoWindowReference window, VuoModifierKey modifierKey)
433 {
434  id monitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSScrollWheelMask handler:^(NSEvent *event) {
435  VuoMouse_fireScrollDeltaIfNeeded(event, window, modifierKey, ^(VuoPoint2d point){ scrolled(point); });
436  return event;
437  }];
438 
439  struct VuoMouseContext *context = (struct VuoMouseContext *)mouseListener;
440  context->monitor = monitor;
441 }
442 
448 void VuoMouse_startListeningForScrollsWithCallback(VuoMouse *mouseListener, void (^scrolled)(VuoPoint2d), VuoWindowReference window, VuoModifierKey modifierKey)
449 {
450  id monitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSScrollWheelMask handler:^(NSEvent *event) {
451  VuoMouse_fireScrollDeltaIfNeeded(event, window, modifierKey, scrolled);
452  return event;
453  }];
454 
455  struct VuoMouseContext *context = (struct VuoMouseContext *)mouseListener;
456  context->monitor = monitor;
457 }
458 
462 static void VuoMouse_fireInitialEvent(void (^movedTo)(VuoPoint2d), VuoWindowReference window, VuoModifierKey modifierKey)
463 {
464  if (window && (modifierKey == VuoModifierKey_Any || modifierKey == VuoModifierKey_None))
465  {
467 
468  VuoPoint2d position;
469  VuoBoolean isPressed;
470  if (VuoMouse_getStatus(&position, &isPressed, VuoMouseButton_Any, window, VuoModifierKey_Any, false))
471  movedTo(position);
472 
474  }
475 }
476 
484 void VuoMouse_startListeningForMoves(VuoMouse *mouseListener, void (*movedTo)(VuoPoint2d), VuoWindowReference window, VuoModifierKey modifierKey)
485 {
486  id monitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSMouseMovedMask|NSLeftMouseDraggedMask|NSRightMouseDraggedMask|NSOtherMouseDraggedMask handler:^(NSEvent *event) {
487  VuoMouse_fireMousePositionIfNeeded(event, [NSEvent mouseLocation], window, modifierKey, true, ^(VuoPoint2d point){ movedTo(point); });
488  return event;
489  }];
490 
491  struct VuoMouseContext *context = (struct VuoMouseContext *)mouseListener;
492  context->monitor = monitor;
493 
494  VuoMouse_fireInitialEvent(^(VuoPoint2d point){ movedTo(point); }, window, modifierKey);
495 }
496 
504 void VuoMouse_startListeningForMovesWithCallback(VuoMouse *mouseListener, void (^movedTo)(VuoPoint2d),
506 {
507  id monitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSMouseMovedMask|NSLeftMouseDraggedMask|NSRightMouseDraggedMask|NSOtherMouseDraggedMask handler:^(NSEvent *event) {
508  VuoMouse_fireMousePositionIfNeeded(event, [NSEvent mouseLocation], window, modifierKey, true, movedTo);
509  return event;
510  }];
511 
512  struct VuoMouseContext *context = (struct VuoMouseContext *)mouseListener;
513  context->monitor = monitor;
514 
515  VuoMouse_fireInitialEvent(movedTo, window, modifierKey);
516 }
517 
523 void VuoMouse_startListeningForDeltas(VuoMouse *mouseListener, void (*movedBy)(VuoPoint2d), VuoWindowReference window, VuoModifierKey modifierKey)
524 {
525  id monitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSMouseMovedMask|NSLeftMouseDraggedMask|NSRightMouseDraggedMask|NSOtherMouseDraggedMask handler:^(NSEvent *event) {
526  VuoMouse_fireMouseDeltaIfNeeded(event, window, modifierKey, movedBy);
527  return event;
528  }];
529 
530  struct VuoMouseContext *context = (struct VuoMouseContext *)mouseListener;
531  context->monitor = monitor;
532 }
533 
539 void VuoMouse_startListeningForDragsWithCallback(VuoMouse *mouseListener, void (^dragMovedTo)(VuoPoint2d),
540  VuoMouseButton button, VuoWindowReference window, VuoModifierKey modifierKey, bool fireRegardlessOfPosition)
541 {
542  struct VuoMouseContext *context = (struct VuoMouseContext *)mouseListener;
543 
544  NSEventMask eventMask = 0;
545  switch (button)
546  {
547  case VuoMouseButton_Left:
548  eventMask = NSLeftMouseDraggedMask;
549  break;
550  case VuoMouseButton_Middle:
551  eventMask = NSOtherMouseDraggedMask;
552  break;
553  case VuoMouseButton_Right:
554  eventMask = NSRightMouseDraggedMask;
555 
556  // Also fire events for control-leftclicks.
557  context->monitor2 = [NSEvent addLocalMonitorForEventsMatchingMask:NSLeftMouseDraggedMask handler:^(NSEvent *event) {
558  VuoMouse_fireMousePositionIfNeeded(event, [NSEvent mouseLocation], window, VuoModifierKey_Control, fireRegardlessOfPosition, dragMovedTo);
559  return event;
560  }];
561 
562  break;
563  case VuoMouseButton_Any:
564  eventMask = NSLeftMouseDraggedMask | NSOtherMouseDraggedMask | NSRightMouseDraggedMask;
565  break;
566  }
567 
568  context->monitor = [NSEvent addLocalMonitorForEventsMatchingMask:eventMask handler:^(NSEvent *event) {
569  VuoMouse_fireMousePositionIfNeeded(event, [NSEvent mouseLocation], window, modifierKey, fireRegardlessOfPosition, dragMovedTo);
570  return event;
571  }];
572 }
573 
579 void VuoMouse_startListeningForDrags(VuoMouse *mouseListener, void (*dragMovedTo)(VuoPoint2d), VuoMouseButton button, VuoWindowReference window, VuoModifierKey modifierKey)
580 {
581  VuoMouse_startListeningForDragsWithCallback(mouseListener, ^(VuoPoint2d point){ dragMovedTo(point); }, button, window, modifierKey, true);
582 }
583 
591 void VuoMouse_startListeningForPressesWithCallback(VuoMouse *mouseListener, void (^pressed)(VuoPoint2d), void (^forcePressed)(VuoPoint2d),
593 {
594  struct VuoMouseContext *context = (struct VuoMouseContext *)mouseListener;
595 
596  NSEventMask eventMask = 0;
597  switch (button)
598  {
599  case VuoMouseButton_Left:
600  eventMask = NSLeftMouseDownMask;
601  break;
602  case VuoMouseButton_Middle:
603  eventMask = NSOtherMouseDownMask;
604  break;
605  case VuoMouseButton_Right:
606  eventMask = NSRightMouseDownMask;
607 
608  // Also fire events for control-leftclicks.
609  context->monitor2 = [NSEvent addLocalMonitorForEventsMatchingMask:NSLeftMouseDownMask handler:^(NSEvent *event) {
610  VuoMouse_fireMousePositionIfNeeded(event, [NSEvent mouseLocation], window, VuoModifierKey_Control, false, pressed);
611  return event;
612  }];
613 
614  break;
615  case VuoMouseButton_Any:
616  eventMask = NSLeftMouseDownMask | NSOtherMouseDownMask | NSRightMouseDownMask;
617  break;
618  }
619 
620  eventMask |= NSEventMaskPressure;
621 
622  context->monitor = [NSEvent addLocalMonitorForEventsMatchingMask:eventMask handler:^(NSEvent *event) {
623  if (event.type == NSEventTypePressure)
624  {
625  if (context->priorPressureStage == 1 && event.stage == 2 && forcePressed)
626  VuoMouse_fireMousePositionIfNeeded(event, [NSEvent mouseLocation], window, modifierKey, false, forcePressed);
627  context->priorPressureStage = event.stage;
628  }
629  else
630  VuoMouse_fireMousePositionIfNeeded(event, [NSEvent mouseLocation], window, modifierKey, false, pressed);
631  return event;
632  }];
633 }
634 
642 void VuoMouse_startListeningForPresses(VuoMouse *mouseListener, void (*pressed)(VuoPoint2d), void (*forcePressed)(VuoPoint2d),
644 {
646  ^(VuoPoint2d point){ pressed(point); },
647  ^(VuoPoint2d point){ forcePressed(point); },
648  button, window, modifierKey);
649 }
650 
658 void VuoMouse_startListeningForPressureChanges(VuoMouse *mouseListener, void (*pressureChanged)(VuoReal), VuoMouseButton button, VuoModifierKey modifierKey)
659 {
660  struct VuoMouseContext *context = (struct VuoMouseContext *)mouseListener;
661  context->monitor = [NSEvent addLocalMonitorForEventsMatchingMask:NSEventMaskPressure handler:^(NSEvent *event) {
662  if (VuoModifierKey_doMacEventFlagsMatch(CGEventGetFlags([event CGEvent]), modifierKey)
663  && (button == VuoMouseButton_Any
664  || (button == VuoMouseButton_Left && NSEvent.pressedMouseButtons & 1)
665  || (button == VuoMouseButton_Right && NSEvent.pressedMouseButtons & 2)
666  || (button == VuoMouseButton_Middle && NSEvent.pressedMouseButtons & 4)))
667  pressureChanged(VuoMouse_getPressure(event));
668  return event;
669  }];
670 }
671 
677 void VuoMouse_startListeningForReleasesWithCallback(VuoMouse *mouseListener, void (^released)(VuoPoint2d),
678  VuoMouseButton button, VuoWindowReference window, VuoModifierKey modifierKey, bool fireRegardlessOfPosition)
679 {
680  struct VuoMouseContext *context = (struct VuoMouseContext *)mouseListener;
681 
682  NSEventMask eventMask = 0;
683  switch (button)
684  {
685  case VuoMouseButton_Left:
686  eventMask = NSLeftMouseUpMask;
687  break;
688  case VuoMouseButton_Middle:
689  eventMask = NSOtherMouseUpMask;
690  break;
691  case VuoMouseButton_Right:
692  eventMask = NSRightMouseUpMask;
693 
694  // Also fire events for control-leftclicks.
695  context->monitor2 = [NSEvent addLocalMonitorForEventsMatchingMask:NSLeftMouseUpMask handler:^(NSEvent *event) {
696  VuoMouse_fireMousePositionIfNeeded(event, [NSEvent mouseLocation], window, VuoModifierKey_Control, fireRegardlessOfPosition, released);
697  return event;
698  }];
699 
700  break;
701  case VuoMouseButton_Any:
702  eventMask = NSLeftMouseUpMask | NSOtherMouseUpMask | NSRightMouseUpMask;
703  break;
704  }
705 
706  context->monitor = [NSEvent addLocalMonitorForEventsMatchingMask:eventMask handler:^(NSEvent *event) {
707  VuoMouse_fireMousePositionIfNeeded(event, [NSEvent mouseLocation], window, modifierKey, fireRegardlessOfPosition, released);
708  return event;
709  }];
710 }
711 
717 void VuoMouse_startListeningForReleases(VuoMouse *mouseListener, void (*released)(VuoPoint2d), VuoMouseButton button, VuoWindowReference window, VuoModifierKey modifierKey, bool fireRegardlessOfPosition)
718 {
719  VuoMouse_startListeningForReleasesWithCallback(mouseListener, ^(VuoPoint2d point){ released(point); }, button, window, modifierKey, fireRegardlessOfPosition);
720 }
721 
727 void VuoMouse_startListeningForClicks(VuoMouse *mouseListener, void (*singleClicked)(VuoPoint2d), void (*doubleClicked)(VuoPoint2d), void (*tripleClicked)(VuoPoint2d),
729 {
730  struct VuoMouseContext *context = (struct VuoMouseContext *)mouseListener;
731  context->clickQueue = dispatch_queue_create("vuo.mouse.click", 0);
732  context->clickGroup = dispatch_group_create();
733  context->pendingClickCount = 0;
734 
735  NSEventMask eventMask = 0;
736  switch (button)
737  {
738  case VuoMouseButton_Left:
739  eventMask = NSLeftMouseUpMask;
740  break;
741  case VuoMouseButton_Middle:
742  eventMask = NSOtherMouseUpMask;
743  break;
744  case VuoMouseButton_Right:
745  eventMask = NSRightMouseUpMask;
746 
747  // Also fire events for control-leftclicks.
748  context->monitor2 = [NSEvent addLocalMonitorForEventsMatchingMask:NSLeftMouseUpMask handler:^(NSEvent *event) {
749  VuoMouse_fireMouseClickIfNeeded(context, event, window, VuoModifierKey_Control, singleClicked, doubleClicked, tripleClicked);
750  return event;
751  }];
752 
753  break;
754  case VuoMouseButton_Any:
755  eventMask = NSLeftMouseUpMask | NSOtherMouseUpMask | NSRightMouseUpMask;
756  break;
757  }
758 
759  context->monitor = [NSEvent addLocalMonitorForEventsMatchingMask:eventMask handler:^(NSEvent *event) {
760  VuoMouse_fireMouseClickIfNeeded(context, event, window, modifierKey, singleClicked, doubleClicked, tripleClicked);
761  return event;
762  }];
763 }
764 
774  void (*zoomed)(VuoReal),
775  void (*swipedLeft)(void),
776  void (*swipedRight)(void),
777  VuoWindowReference windowRef)
778 {
779  struct VuoMouseContext *context = (struct VuoMouseContext *)mouseListener;
780  context->touchesMoved = touchesMoved;
781  context->zoomed = zoomed;
782  context->swipedLeft = swipedLeft;
783  context->swipedRight = swipedRight;
784  context->window = windowRef;
786  VuoGraphicsView *view = (VuoGraphicsView *)window.contentView;
787  [view addTouchesMovedTrigger:touchesMoved zoomed:zoomed swipedLeft:swipedLeft swipedRight:swipedRight];
788 }
789 
795 void VuoMouse_stopListening(VuoMouse *mouseListener)
796 {
797  struct VuoMouseContext *context = (struct VuoMouseContext *)mouseListener;
798 
799  VUOLOG_PROFILE_BEGIN(mainQueue);
800  VuoApp_executeOnMainThread(^{ // wait for any in-progress monitor handlers to complete
801  VUOLOG_PROFILE_END(mainQueue);
802  if (context->monitor)
803  {
804  [NSEvent removeMonitor:context->monitor];
805  context->monitor = nil;
806 
807  if (context->monitor2)
808  {
809  [NSEvent removeMonitor:context->monitor2];
810  context->monitor2 = nil;
811  }
812  }
813  });
814 
815  if (context->clickGroup)
816  {
817  dispatch_group_wait(context->clickGroup, DISPATCH_TIME_FOREVER); // wait for any pending click events to complete
818  dispatch_release(context->clickQueue);
819  dispatch_release(context->clickGroup);
820  context->clickQueue = NULL;
821  context->clickGroup = NULL;
822  }
823 
824  if (context->touchesMoved)
825  {
827  VuoGraphicsView *view = (VuoGraphicsView *)window.contentView;
828  [view removeTouchesMovedTrigger:context->touchesMoved zoomed:context->zoomed swipedLeft:context->swipedLeft swipedRight:context->swipedRight];
829  context->window = NULL;
830  context->touchesMoved = NULL;
831  context->zoomed = NULL;
832  context->swipedLeft = NULL;
833  context->swipedRight = NULL;
834  }
835 }
836 
837 static unsigned int VuoMouseStatus_useCount = 0;
838 static id VuoMouseStatus_monitor = nil;
839 static NSPoint VuoMouseStatus_position;
840 static bool VuoMouseStatus_leftButton = false;
841 static bool VuoMouseStatus_middleButton = false;
842 static bool VuoMouseStatus_rightButton = false;
843 
850 {
851  if (__sync_add_and_fetch(&VuoMouseStatus_useCount, 1) == 1)
852  {
853  // https://b33p.net/kosada/node/11966
854  // Mouse events are only received if the process is in app mode.
855  VuoApp_init(true);
856 
857  // Ensure +[NSEvent mouseLocation] is called for the first time on the main thread, else we get console message
858  // "!!! 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."
860  VuoMouseStatus_position = [NSEvent mouseLocation];
861  });
865  VuoMouseStatus_monitor = [NSEvent addLocalMonitorForEventsMatchingMask:
866  NSMouseMovedMask
867  |NSLeftMouseDraggedMask|NSRightMouseDraggedMask|NSOtherMouseDraggedMask
868  |NSLeftMouseDownMask|NSOtherMouseDownMask|NSRightMouseDownMask
869  |NSLeftMouseUpMask|NSOtherMouseUpMask|NSRightMouseUpMask
870  handler:^(NSEvent *event) {
871  // Despite the name, -locationInWindow sometimes returns screen coordinates.
872  NSPoint p = [event locationInWindow];
873  NSWindow *window = [event window];
874  if (window)
875  {
876  NSRect rectInWindow = NSMakeRect(p.x, p.y, 0, 0);
877  VuoMouseStatus_position = [window convertRectToScreen:rectInWindow].origin;
878  }
879  else
880  VuoMouseStatus_position = p;
881 
882  switch ([event type])
883  {
884  case NSLeftMouseDown: if (!VuoMouse_isResizing()) VuoMouseStatus_leftButton = true; break;
885  case NSLeftMouseUp: VuoMouseStatus_leftButton = false; break;
886  case NSOtherMouseDown: if (!VuoMouse_isResizing()) VuoMouseStatus_middleButton = true; break;
887  case NSOtherMouseUp: VuoMouseStatus_middleButton = false; break;
888  case NSRightMouseDown: if (!VuoMouse_isResizing()) VuoMouseStatus_rightButton = true; break;
889  case NSRightMouseUp: VuoMouseStatus_rightButton = false; break;
890  default: ;
891  }
892 
893  return event;
894  }];
895  }
896 }
897 
904 {
905  if (__sync_sub_and_fetch(&VuoMouseStatus_useCount, 1) == 0)
906  {
907  [NSEvent removeMonitor:VuoMouseStatus_monitor];
909  }
910 }
911 
927 bool VuoMouse_getStatus(VuoPoint2d *position, VuoBoolean *isPressed,
928  VuoMouseButton button, VuoWindowReference windowRef, VuoModifierKey modifierKey,
929  bool onlyUpdateWhenActive)
930 {
931  if (onlyUpdateWhenActive)
932  {
933  __block bool isActive;
935  isActive = [NSApp isActive];
936  });
937  if (!isActive)
938  return false;
939  }
940 
941  bool shouldTrackPresses;
942  if (windowRef)
943  {
944  VuoGraphicsWindow *targetWindow = (VuoGraphicsWindow *)windowRef;
945  NSPoint pointInScreen = VuoMouseStatus_position;
946 
947  if (((VuoGraphicsWindow *)targetWindow).isFullScreen)
948  *position = VuoMouse_convertFullScreenToVuoCoordinates(pointInScreen, targetWindow, &shouldTrackPresses);
949  else
950  {
951  NSRect rectInScreen = NSMakeRect(pointInScreen.x, pointInScreen.y, 0, 0);
952  __block NSPoint pointInWindow;
953  __block bool isKeyWindow;
955  pointInWindow = [targetWindow convertRectFromScreen:rectInScreen].origin;
956  isKeyWindow = targetWindow.isKeyWindow;
957  });
958  *position = VuoMouse_convertWindowToVuoCoordinates(pointInWindow, targetWindow, &shouldTrackPresses);
959  shouldTrackPresses = shouldTrackPresses && isKeyWindow;
960  }
961  }
962  else
963  {
964  NSPoint pointInScreen = VuoMouseStatus_position;
965  pointInScreen.y = CGDisplayPixelsHigh(CGMainDisplayID()) - pointInScreen.y;
966  *position = VuoPoint2d_make(pointInScreen.x, pointInScreen.y);
967 
968  shouldTrackPresses = true;
969  }
970 
971  if ( !shouldTrackPresses)
972  {
973  if (*isPressed)
974  {
975  // The existing button state is pressed.
976  // If the button been released, we should update isPressed to reflect that release (even though we're ignoring presses).
977  }
978  else
979  // The existing button state is not-pressed. Since we're ignoring presses, we shouldn't change it.
980  return true;
981  }
982 
983  *isPressed = false;
984  switch (button)
985  {
986  case VuoMouseButton_Left:
987  *isPressed = VuoMouseStatus_leftButton;
988  break;
989  case VuoMouseButton_Right:
990  *isPressed = VuoMouseStatus_rightButton;
991 
992  // Also return true for control-leftclicks.
993  if (VuoMouseStatus_leftButton && VuoModifierKey_doMacEventFlagsMatch([NSEvent modifierFlags], VuoModifierKey_Control))
994  {
995  *isPressed = true;
996  return true;
997  }
998 
999  break;
1000  case VuoMouseButton_Middle:
1001  *isPressed = VuoMouseStatus_middleButton;
1002  break;
1003  case VuoMouseButton_Any:
1005  break;
1006  }
1007  *isPressed = *isPressed && VuoModifierKey_doMacEventFlagsMatch([NSEvent modifierFlags], modifierKey);
1008 
1009  return true;
1010 }