Vuo  2.0.3
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 <objc/objc-runtime.h>
20 
21 #include <dlfcn.h>
22 
26 static bool VuoEventLoop_isMainThread(void)
27 {
28  static void **mainThread;
29  static dispatch_once_t once = 0;
30  dispatch_once(&once, ^{
31  mainThread = (void **)dlsym(RTLD_SELF, "VuoApp_mainThread");
32  if (!mainThread)
33  mainThread = (void **)dlsym(RTLD_DEFAULT, "VuoApp_mainThread");
34 
35  if (!mainThread)
36  {
37  VUserLog("Error: Couldn't find VuoApp_mainThread.");
39  exit(1);
40  }
41 
42  if (!*mainThread)
43  {
44  VUserLog("Error: VuoApp_mainThread isn't set.");
46  exit(1);
47  }
48  });
49 
50  return *mainThread == (void *)pthread_self();
51 }
52 
71 {
73  {
74  VUserLog("Error: VuoEventLoop_processEvent must be called from the main thread.");
76  exit(1);
77  }
78 
79  NSAutoreleasePool *pool = [NSAutoreleasePool new];
80 
81  // Can't just use `NSApp` directly, since the composition might not link to AppKit.
82  id *nsAppGlobal = (id *)dlsym(RTLD_DEFAULT, "NSApp");
83 
84  // Support running either with or without an NSApplication.
85  if (nsAppGlobal && *nsAppGlobal)
86  {
87  // http://www.cocoawithlove.com/2009/01/demystifying-nsapplication-by.html
88  // https://stackoverflow.com/questions/6732400/cocoa-integrate-nsapplication-into-an-existing-c-mainloop
89 
90  // When the composition is ready to stop, it posts a killswitch NSEvent,
91  // to ensure that this function returns immediately.
92  NSEvent *event = [*nsAppGlobal nextEventMatchingMask:NSAnyEventMask
93  untilDate:(mode == VuoEventLoop_WaitIndefinitely ? [NSDate distantFuture] : [NSDate distantPast])
94  inMode:NSDefaultRunLoopMode
95  dequeue:YES];
96  [*nsAppGlobal sendEvent:event];
97  [*nsAppGlobal updateWindows];
98  }
99  else
100  {
101  // This blocks until CFRunLoopStop() is called.
102  // When the composition is ready to stop (or switch into NSApplication mode), it calls CFRunLoopStop().
103  if (mode == VuoEventLoop_WaitIndefinitely)
104  CFRunLoopRun();
105  else
106  CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
107  }
108 
109  [pool drain];
110 }
111 
118 {
119  // Can't just use `NSApp` directly, since the composition might not link to AppKit.
120  id *nsAppGlobal = (id *)dlsym(RTLD_DEFAULT, "NSApp");
121 
122  if (nsAppGlobal && *nsAppGlobal)
123  {
124  // Send an event, to ensure VuoEventLoop_processEvent()'s call to `nextEventMatchingMask:…` returns immediately.
125  // -[NSApplication postEvent:atStart:] must be called from the main thread.
126  dispatch_async(dispatch_get_main_queue(), ^{
127  NSEvent *killswitch = [NSEvent otherEventWithType:NSApplicationDefined
128  location:NSMakePoint(0,0)
129  modifierFlags:0
130  timestamp:0
131  windowNumber:0
132  context:nil
133  subtype:0
134  data1:0
135  data2:0];
136  [*nsAppGlobal postEvent:killswitch atStart:NO];
137  });
138  }
139  else
140  // https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW26
141  // says this can be called from any thread.
142  CFRunLoopStop(CFRunLoopGetMain());
143 }
144 
151 {
152  // https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW26
153  // says this can be called from any thread.
154  CFRunLoopStop(CFRunLoopGetMain());
155 }
156 
163 {
165  {
166  VUserLog("Error: VuoEventLoop_mayBeTerminated must be called from the main thread.");
168  exit(1);
169  }
170 
171  // Can't just use `NSApp` directly, since the composition might not link to AppKit.
172  id *nsAppGlobal = (id *)dlsym(RTLD_DEFAULT, "NSApp");
173  if (!nsAppGlobal || !*nsAppGlobal)
174  return true;
175 
176  if ([*nsAppGlobal modalWindow])
177  return false;
178 
179  for (NSWindow *window in [*nsAppGlobal windows])
180  if ([window attachedSheet])
181  return false;
182 
183  return true;
184 }
185 
195 {
196  static dispatch_queue_attr_t attr = 0;
197  static dispatch_once_t once = 0;
198  dispatch_once(&once, ^{
199  attr = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0);
200  });
201  return attr;
202 }
203 
210 {
211  typedef void (*vuoStopCompositionType)(struct VuoCompositionState *);
212  vuoStopCompositionType vuoStopComposition = (vuoStopCompositionType)dlsym(RTLD_SELF, "vuoStopComposition");
213  if (!vuoStopComposition)
214  vuoStopComposition = (vuoStopCompositionType)dlsym(RTLD_DEFAULT, "vuoStopComposition");
215  if (!vuoStopComposition)
216  {
217  VUserLog("Warning: Couldn't find vuoStopComposition symbol; not installing clean-shutdown signal handlers.");
218  return;
219  }
220 
221  // Disable default signal handlers so libdispatch can catch them.
222  signal(SIGINT, SIG_IGN);
223  signal(SIGQUIT, SIG_IGN);
224  signal(SIGTERM, SIG_IGN);
225 
226  // Use libdispatch to handle signals instead of `signal`/`sigaction`
227  // since `vuoStopComposition()` uses non-signal-safe functions such as `malloc`.
228  void (^stop)(void) = ^{
229  vuoStopComposition(NULL);
230  };
231  dispatch_source_t sigintSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGINT, 0, dispatch_get_main_queue());
232  dispatch_source_t sigquitSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGQUIT, 0, dispatch_get_main_queue());
233  dispatch_source_t sigtermSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGTERM, 0, dispatch_get_main_queue());
234  dispatch_source_set_event_handler(sigintSource, stop);
235  dispatch_source_set_event_handler(sigquitSource, stop);
236  dispatch_source_set_event_handler(sigtermSource, stop);
237  dispatch_resume(sigintSource);
238  dispatch_resume(sigquitSource);
239  dispatch_resume(sigtermSource);
240 }
241 
246 void VuoThermalState(void)
247 {
248  void (^logThermalState)(int thermalState) = ^(int thermalState){
249  if (thermalState == 0)
250  VDebugLog("thermalState = nominal");
251  else if (thermalState == 1)
252  VDebugLog("thermalState = fair (\"fans audible\")");
253  else if (thermalState == 2)
254  VDebugLog("thermalState = serious (\"fans at maximum speed\")");
255  else if (thermalState == 3)
256  VDebugLog("thermalState = critical (\"system needs to cool down\")");
257  };
258  if ([NSProcessInfo.processInfo respondsToSelector:@selector(thermalState)])
259  {
260  logThermalState((int)objc_msgSend(NSProcessInfo.processInfo, @selector(thermalState)));
261  [NSNotificationCenter.defaultCenter addObserverForName:@"NSProcessInfoThermalStateDidChangeNotification" object:nil queue:nil usingBlock:^(NSNotification *note){
262  logThermalState((int)objc_msgSend(note.object, @selector(thermalState)));
263  }];
264  }
265 }
266 
275 {
276  id activityToken = [[NSProcessInfo processInfo] performSelector:@selector(beginActivityWithOptions:reason:)
277  withObject: (id)((0x00FFFFFFULL | (1ULL << 20)) & ~(1ULL << 14)) // NSActivityUserInitiated & ~NSActivitySuddenTerminationDisabled
278  withObject: @"Many Vuo compositions need to process input and send output even when the app's window is not visible."];
279 
280  [activityToken retain];
281 
282  VuoThermalState();
283 }