Vuo  2.0.0
VuoEventLoop.m
Go to the documentation of this file.
1 
10 #include "VuoEventLoop.h"
11 #include "VuoLog.h"
12 #include "VuoCompositionState.h"
13 
14 #ifndef NS_RETURNS_INNER_POINTER
15 #define NS_RETURNS_INNER_POINTER
16 #endif
17 #import <AppKit/AppKit.h>
18 
19 #include <dlfcn.h>
20 
24 static bool VuoEventLoop_isMainThread(void)
25 {
26  static void **mainThread;
27  static dispatch_once_t once = 0;
28  dispatch_once(&once, ^{
29  mainThread = (void **)dlsym(RTLD_SELF, "VuoApp_mainThread");
30  if (!mainThread)
31  mainThread = (void **)dlsym(RTLD_DEFAULT, "VuoApp_mainThread");
32 
33  if (!mainThread)
34  {
35  VUserLog("Error: Couldn't find VuoApp_mainThread.");
37  exit(1);
38  }
39 
40  if (!*mainThread)
41  {
42  VUserLog("Error: VuoApp_mainThread isn't set.");
44  exit(1);
45  }
46  });
47 
48  return *mainThread == (void *)pthread_self();
49 }
50 
69 {
71  {
72  VUserLog("Error: VuoEventLoop_processEvent must be called from the main thread.");
74  exit(1);
75  }
76 
77  NSAutoreleasePool *pool = [NSAutoreleasePool new];
78 
79  // Can't just use `NSApp` directly, since the composition might not link to AppKit.
80  id *nsAppGlobal = (id *)dlsym(RTLD_DEFAULT, "NSApp");
81 
82  // Support running either with or without an NSApplication.
83  if (nsAppGlobal && *nsAppGlobal)
84  {
85  // http://www.cocoawithlove.com/2009/01/demystifying-nsapplication-by.html
86  // https://stackoverflow.com/questions/6732400/cocoa-integrate-nsapplication-into-an-existing-c-mainloop
87 
88  // When the composition is ready to stop, it posts a killswitch NSEvent,
89  // to ensure that this function returns immediately.
90  NSEvent *event = [*nsAppGlobal nextEventMatchingMask:NSAnyEventMask
91  untilDate:(mode == VuoEventLoop_WaitIndefinitely ? [NSDate distantFuture] : [NSDate distantPast])
92  inMode:NSDefaultRunLoopMode
93  dequeue:YES];
94  [*nsAppGlobal sendEvent:event];
95  [*nsAppGlobal updateWindows];
96  }
97  else
98  {
99  // This blocks until CFRunLoopStop() is called.
100  // When the composition is ready to stop (or switch into NSApplication mode), it calls CFRunLoopStop().
101  if (mode == VuoEventLoop_WaitIndefinitely)
102  CFRunLoopRun();
103  else
104  CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
105  }
106 
107  [pool drain];
108 }
109 
116 {
117  // Can't just use `NSApp` directly, since the composition might not link to AppKit.
118  id *nsAppGlobal = (id *)dlsym(RTLD_DEFAULT, "NSApp");
119 
120  if (nsAppGlobal && *nsAppGlobal)
121  {
122  // Send an event, to ensure VuoEventLoop_processEvent()'s call to `nextEventMatchingMask:…` returns immediately.
123  // -[NSApplication postEvent:atStart:] must be called from the main thread.
124  dispatch_async(dispatch_get_main_queue(), ^{
125  NSEvent *killswitch = [NSEvent otherEventWithType:NSApplicationDefined
126  location:NSMakePoint(0,0)
127  modifierFlags:0
128  timestamp:0
129  windowNumber:0
130  context:nil
131  subtype:0
132  data1:0
133  data2:0];
134  [*nsAppGlobal postEvent:killswitch atStart:NO];
135  });
136  }
137  else
138  // https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW26
139  // says this can be called from any thread.
140  CFRunLoopStop(CFRunLoopGetMain());
141 }
142 
149 {
150  // https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW26
151  // says this can be called from any thread.
152  CFRunLoopStop(CFRunLoopGetMain());
153 }
154 
161 {
163  {
164  VUserLog("Error: VuoEventLoop_mayBeTerminated must be called from the main thread.");
166  exit(1);
167  }
168 
169  // Can't just use `NSApp` directly, since the composition might not link to AppKit.
170  id *nsAppGlobal = (id *)dlsym(RTLD_DEFAULT, "NSApp");
171  if (!nsAppGlobal || !*nsAppGlobal)
172  return true;
173 
174  if ([*nsAppGlobal modalWindow])
175  return false;
176 
177  for (NSWindow *window in [*nsAppGlobal windows])
178  if ([window attachedSheet])
179  return false;
180 
181  return true;
182 }
183 
193 {
194  static dispatch_queue_attr_t attr = 0;
195  static dispatch_once_t once = 0;
196  dispatch_once(&once, ^{
197  attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0);
198  });
199  return attr;
200 }
201 
208 {
209  typedef void (*vuoStopCompositionType)(struct VuoCompositionState *);
210  vuoStopCompositionType vuoStopComposition = (vuoStopCompositionType)dlsym(RTLD_SELF, "vuoStopComposition");
211  if (!vuoStopComposition)
212  vuoStopComposition = (vuoStopCompositionType)dlsym(RTLD_DEFAULT, "vuoStopComposition");
213  if (!vuoStopComposition)
214  {
215  VUserLog("Warning: Couldn't find vuoStopComposition symbol; not installing clean-shutdown signal handlers.");
216  return;
217  }
218 
219  // Disable default signal handlers so libdispatch can catch them.
220  signal(SIGINT, SIG_IGN);
221  signal(SIGQUIT, SIG_IGN);
222  signal(SIGTERM, SIG_IGN);
223 
224  // Use libdispatch to handle signals instead of `signal`/`sigaction`
225  // since `vuoStopComposition()` uses non-signal-safe functions such as `malloc`.
226  void (^stop)(void) = ^{
227  vuoStopComposition(NULL);
228  };
229  dispatch_source_t sigintSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGINT, 0, dispatch_get_main_queue());
230  dispatch_source_t sigquitSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGQUIT, 0, dispatch_get_main_queue());
231  dispatch_source_t sigtermSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGTERM, 0, dispatch_get_main_queue());
232  dispatch_source_set_event_handler(sigintSource, stop);
233  dispatch_source_set_event_handler(sigquitSource, stop);
234  dispatch_source_set_event_handler(sigtermSource, stop);
235  dispatch_resume(sigintSource);
236  dispatch_resume(sigquitSource);
237  dispatch_resume(sigtermSource);
238 }
239 
248 {
249  id activityToken = [[NSProcessInfo processInfo] performSelector:@selector(beginActivityWithOptions:reason:)
250  withObject: (id)((0x00FFFFFFULL | (1ULL << 20)) & ~(1ULL << 14)) // NSActivityUserInitiated & ~NSActivitySuddenTerminationDisabled
251  withObject: @"Many Vuo compositions need to process input and send output even when the app's window is not visible."];
252 
253  [activityToken retain];
254 }