Vuo 2.4.4
Loading...
Searching...
No Matches
VuoEventLoop.m
Go to the documentation of this file.
1
10#include "VuoEventLoop.h"
11#include "VuoLog.h"
12#include "VuoCompositionState.h"
13
14#import <AppKit/AppKit.h>
15#import <IOKit/pwr_mgt/IOPMLib.h>
16#import <IOKit/pwr_mgt/IOPM.h>
17
18#include <objc/objc-runtime.h>
19
20#include <dlfcn.h>
21
23
24static id *nsAppGlobal;
25static void __attribute__((constructor)) VuoEventLoop_init()
26{
27 // Can't just use `NSApp` directly, since the composition might not link to AppKit.
28 nsAppGlobal = (id *)dlsym(RTLD_DEFAULT, "NSApp");
29}
30
35{
36 static void **mainThread;
37 static dispatch_once_t once = 0;
38 dispatch_once(&once, ^{
39 mainThread = (void **)dlsym(RTLD_SELF, "VuoApp_mainThread");
40 if (!mainThread)
41 mainThread = (void **)dlsym(RTLD_DEFAULT, "VuoApp_mainThread");
42
43 if (!mainThread)
44 {
45 VUserLog("Error: Couldn't find VuoApp_mainThread.");
47 exit(1);
48 }
49
50 if (!*mainThread)
51 {
52 VUserLog("Error: VuoApp_mainThread isn't set.");
54 exit(1);
55 }
56 });
57
58 return *mainThread == (void *)pthread_self();
59}
60
79{
81 {
82 VUserLog("Error: VuoEventLoop_processEvent must be called from the main thread.");
84 exit(1);
85 }
86
87 NSAutoreleasePool *pool = [NSAutoreleasePool new];
88
89 // Support running either with or without an NSApplication.
91 {
92 // http://www.cocoawithlove.com/2009/01/demystifying-nsapplication-by.html
93 // https://stackoverflow.com/questions/6732400/cocoa-integrate-nsapplication-into-an-existing-c-mainloop
94
95 // When the composition is ready to stop, it posts a killswitch NSEvent,
96 // to ensure that this function returns immediately.
97 NSEvent *event = [*nsAppGlobal nextEventMatchingMask:NSEventMaskAny
98 untilDate:(mode == VuoEventLoop_WaitIndefinitely ? [NSDate distantFuture] : [NSDate distantPast])
99 inMode:NSDefaultRunLoopMode
100 dequeue:YES];
101 [*nsAppGlobal sendEvent:event];
102 [*nsAppGlobal updateWindows];
103 }
104 else
105 {
106 // This blocks until CFRunLoopStop() is called.
107 // When the composition is ready to stop (or switch into NSApplication mode), it calls CFRunLoopStop().
108 if (mode == VuoEventLoop_WaitIndefinitely)
109 CFRunLoopRun();
110 else
111 CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, false);
112 }
113
114 [pool drain];
115}
116
123{
124 if (nsAppGlobal && *nsAppGlobal)
125 {
126 // Send an event, to ensure VuoEventLoop_processEvent()'s call to `nextEventMatchingMask:…` returns immediately.
127 // -[NSApplication postEvent:atStart:] must be called from the main thread.
128 dispatch_async(dispatch_get_main_queue(), ^{
129 NSEvent *killswitch = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
130 location:NSMakePoint(0,0)
131 modifierFlags:0
132 timestamp:0
133 windowNumber:0
134 context:nil
135 subtype:0
136 data1:0
137 data2:0];
138 [*nsAppGlobal postEvent:killswitch atStart:NO];
139 });
140 }
141 else
142 // https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW26
143 // says this can be called from any thread.
144 CFRunLoopStop(CFRunLoopGetMain());
145}
146
153{
154 // https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html#//apple_ref/doc/uid/10000057i-CH16-SW26
155 // says this can be called from any thread.
156 CFRunLoopStop(CFRunLoopGetMain());
157}
158
165{
167 {
168 VUserLog("Error: VuoEventLoop_mayBeTerminated must be called from the main thread.");
170 exit(1);
171 }
172
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
212{
213 // Increase the open-files limit (macOS defaults to 256).
214 struct rlimit rl = {OPEN_MAX, OPEN_MAX};
215 getrlimit(RLIMIT_NOFILE, &rl);
216 rl.rlim_cur = MIN(OPEN_MAX, rl.rlim_max);
217 if (setrlimit(RLIMIT_NOFILE, &rl))
218 VUserLog("Warning: Couldn't set open-files limit: %s", strerror(errno));
219}
220
227{
228 typedef void (*vuoStopCompositionType)(struct VuoCompositionState *);
229 vuoStopCompositionType vuoStopComposition = (vuoStopCompositionType)dlsym(RTLD_SELF, "vuoStopComposition");
231 vuoStopComposition = (vuoStopCompositionType)dlsym(RTLD_DEFAULT, "vuoStopComposition");
233 {
234 VUserLog("Warning: Couldn't find vuoStopComposition symbol; not installing clean-shutdown signal handlers.");
235 return;
236 }
237
238 // Disable default signal handlers so libdispatch can catch them.
239 signal(SIGINT, SIG_IGN);
240 signal(SIGQUIT, SIG_IGN);
241 signal(SIGTERM, SIG_IGN);
242
243 // Use libdispatch to handle signals instead of `signal`/`sigaction`
244 // since `vuoStopComposition()` uses non-signal-safe functions such as `malloc`.
245 void (^stop)(void) = ^{
246 vuoStopComposition(NULL);
247 };
248 dispatch_source_t sigintSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGINT, 0, dispatch_get_main_queue());
249 dispatch_source_t sigquitSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGQUIT, 0, dispatch_get_main_queue());
250 dispatch_source_t sigtermSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_SIGNAL, SIGTERM, 0, dispatch_get_main_queue());
251 dispatch_source_set_event_handler(sigintSource, stop);
252 dispatch_source_set_event_handler(sigquitSource, stop);
253 dispatch_source_set_event_handler(sigtermSource, stop);
254 dispatch_resume(sigintSource);
255 dispatch_resume(sigquitSource);
256 dispatch_resume(sigtermSource);
257}
258
262static void VuoShowSystemPowerEvent(void *refcon, io_service_t root_domain, natural_t messageType, void *messageArgument)
263{
264 if (messageType != kIOPMMessageSystemPowerEventOccurred)
265 return;
266
267 CFDictionaryRef d;
268 IOPMCopyCPUPowerStatus(&d);
269 CFNumberRef cpuSpeedLimitCF = CFDictionaryGetValue(d, CFSTR(kIOPMCPUPowerLimitProcessorSpeedKey));
270 int cpuSpeedLimit = -1;
271 CFNumberGetValue(cpuSpeedLimitCF, kCFNumberIntType, &cpuSpeedLimit);
272 CFRelease(d);
273
274 if (cpuSpeedLimit >= 0)
275 VUserLog("The system changed the CPU speed limit to %d%%.", cpuSpeedLimit);
276}
277
282static void VuoThermalState(void)
283{
284 void (^logThermalState)(int thermalState) = ^(int thermalState){
285 if (thermalState == 0)
286 VUserLog("thermalState = nominal");
287 else if (thermalState == 1)
288 VUserLog("thermalState = fair (\"fans audible\")");
289 else if (thermalState == 2)
290 VUserLog("thermalState = serious (\"fans at maximum speed\")");
291 else if (thermalState == 3)
292 VUserLog("thermalState = critical (\"system needs to cool down\")");
293 };
294 if ([NSProcessInfo.processInfo respondsToSelector:@selector(thermalState)])
295 {
296 logThermalState(((int (*)(id, SEL))objc_msgSend)(NSProcessInfo.processInfo, @selector(thermalState)));
297 [NSNotificationCenter.defaultCenter addObserverForName:@"NSProcessInfoThermalStateDidChangeNotification" object:nil queue:nil usingBlock:^(NSNotification *note){
298 logThermalState(((int (*)(id, SEL))objc_msgSend)(note.object, @selector(thermalState)));
299 }];
300 }
301
302
303 // Start listening for system power events.
304 io_service_t rootDomain = IORegistryEntryFromPath(kIOMasterPortDefault, kIOPowerPlane ":/IOPowerConnection/IOPMrootDomain");
305 IONotificationPortRef notePort = IONotificationPortCreate(MACH_PORT_NULL);
306 if (rootDomain && notePort)
307 {
308 io_object_t notification_object = MACH_PORT_NULL;
309 IOReturn ret = IOServiceAddInterestNotification(notePort, rootDomain, kIOGeneralInterest, VuoShowSystemPowerEvent, NULL, &notification_object);
310 if (ret == kIOReturnSuccess)
311 {
312 CFRunLoopSourceRef runLoopSrc = IONotificationPortGetRunLoopSource(notePort);
313 if (runLoopSrc)
314 CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSrc, kCFRunLoopDefaultMode);
315 }
316 }
317}
318
323static void VuoMemoryPressure(void)
324{
325 dispatch_source_t memoryPressureWatcher = dispatch_source_create(DISPATCH_SOURCE_TYPE_MEMORYPRESSURE, 0, DISPATCH_MEMORYPRESSURE_NORMAL|DISPATCH_MEMORYPRESSURE_WARN|DISPATCH_MEMORYPRESSURE_CRITICAL, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0));
326 dispatch_source_set_event_handler(memoryPressureWatcher, ^{
327 int pressure = dispatch_source_get_data(memoryPressureWatcher);
328 if (pressure == DISPATCH_MEMORYPRESSURE_NORMAL)
329 VUserLog("memoryPressure = normal");
330 else if (pressure == DISPATCH_MEMORYPRESSURE_WARN)
331 VUserLog("memoryPressure = warning");
332 else if (pressure == DISPATCH_MEMORYPRESSURE_CRITICAL)
333 VUserLog("memoryPressure = critical");
334 });
335 dispatch_resume(memoryPressureWatcher);
336}
337
341static void VuoWorkspaceState(void)
342{
343 [NSWorkspace.sharedWorkspace.notificationCenter addObserverForName:NSWorkspaceWillSleepNotification object:nil queue:nil usingBlock:^(NSNotification *note){
344 VUserLog("The system is going to sleep.");
345 VuoEventLoop_systemAsleep = true;
346 }];
347 [NSWorkspace.sharedWorkspace.notificationCenter addObserverForName:NSWorkspaceDidWakeNotification object:nil queue:nil usingBlock:^(NSNotification *note){
348 VUserLog("The system is waking up.");
349 VuoEventLoop_systemAsleep = false;
350 }];
351 [NSWorkspace.sharedWorkspace.notificationCenter addObserverForName:NSWorkspaceWillPowerOffNotification object:nil queue:nil usingBlock:^(NSNotification *note){
352 VUserLog("The system is powering off");
353 }];
354 [NSWorkspace.sharedWorkspace.notificationCenter addObserverForName:NSWorkspaceScreensDidSleepNotification object:nil queue:nil usingBlock:^(NSNotification *note){
355 VUserLog("The screens are going to sleep.");
356 }];
357 [NSWorkspace.sharedWorkspace.notificationCenter addObserverForName:NSWorkspaceScreensDidWakeNotification object:nil queue:nil usingBlock:^(NSNotification *note){
358 VUserLog("The screens are waking up.");
359 }];
360 [NSWorkspace.sharedWorkspace.notificationCenter addObserverForName:NSWorkspaceSessionDidBecomeActiveNotification object:nil queue:nil usingBlock:^(NSNotification *note){
361 VUserLog("The system is switching back to this user account.");
362 }];
363 [NSWorkspace.sharedWorkspace.notificationCenter addObserverForName:NSWorkspaceSessionDidResignActiveNotification object:nil queue:nil usingBlock:^(NSNotification *note){
364 VUserLog("The system is switching away to another user account.");
365 }];
366 [NSNotificationCenter.defaultCenter addObserverForName:NSSystemClockDidChangeNotification object:nil queue:nil usingBlock:^(NSNotification *note){
367 VUserLog("The system's clock changed to %s", NSDate.date.description.UTF8String);
368 }];
369}
370
379
394
403{
404 id activityToken = [NSProcessInfo.processInfo beginActivityWithOptions:NSActivityUserInitiated & ~NSActivitySuddenTerminationDisabled
405 reason: @"Many Vuo compositions need to process input and send output even when the app's window is not visible."];
406
407 [activityToken retain];
408}