Vuo  2.0.0
VuoApp.m
Go to the documentation of this file.
1 
10 #include "module.h"
11 
12 #include "VuoApp.h"
13 
16 #ifndef NS_RETURNS_INNER_POINTER
17 #define NS_RETURNS_INNER_POINTER
18 #endif
19 #import <AppKit/AppKit.h>
20 #undef NS_RETURNS_INNER_POINTER
21 
22 #include <dlfcn.h>
23 #include <pthread.h>
24 #include <libproc.h>
25 #include <mach-o/dyld.h>
26 #import <libgen.h>
27 #import <dirent.h>
28 
29 #include "VuoCompositionState.h"
30 #include "VuoEventLoop.h"
31 #import "VuoAppDelegate.h"
32 #import "VuoAppSplashWindow.h"
33 
38 const double VuoApp_windowFadeSeconds = 0.45;
39 
44 {
45  static void **VuoApp_mainThread;
46  static dispatch_once_t once = 0;
47  dispatch_once(&once, ^{
48  VuoApp_mainThread = (void **)dlsym(RTLD_SELF, "VuoApp_mainThread");
49  if (!VuoApp_mainThread)
50  VuoApp_mainThread = (void **)dlsym(RTLD_DEFAULT, "VuoApp_mainThread");
51 
52  if (!VuoApp_mainThread)
53  {
54  VUserLog("Error: Couldn't find VuoApp_mainThread.");
56  exit(1);
57  }
58 
59  if (!*VuoApp_mainThread)
60  {
61  VUserLog("Error: VuoApp_mainThread isn't set.");
63  exit(1);
64  }
65  });
66 
67  return *VuoApp_mainThread == (void *)pthread_self();
68 }
69 
79 void VuoApp_executeOnMainThread(void (^block)(void))
80 {
81  if (!block)
82  return;
83 
84  if (VuoApp_isMainThread())
85  block();
86  else
87  {
88  VUOLOG_PROFILE_BEGIN(mainQueue);
89  dispatch_sync(dispatch_get_main_queue(), ^{
90  VUOLOG_PROFILE_END(mainQueue);
91  block();
92  });
93  }
94 }
95 
117 char *VuoApp_getName(void)
118 {
119  char **dylibPath = (char **)dlsym(RTLD_SELF, "VuoApp_dylibPath");
120  if (!dylibPath)
121  dylibPath = (char **)dlsym(RTLD_DEFAULT, "VuoApp_dylibPath");
122  if (dylibPath)
123  {
124  char *filename = strrchr(*dylibPath, '/');
125  if (filename)
126  {
127  char *name = strdup(filename + 1); // Trim leading slash.
128  name[strlen(name) - strlen("-XXXXXX.dylib")] = 0;
129 
130  if (strcmp(name, "VuoComposition") == 0)
131  {
132  free(name);
133  return strdup("Vuo Composition");
134  }
135 
136  return name;
137  }
138  }
139 
140  pid_t runnerPid = VuoGetRunnerPid();
141  if (runnerPid > 0)
142  {
143  char *runnerName = (char *)malloc(2*MAXCOMLEN);
144  proc_name(runnerPid, runnerName, 2*MAXCOMLEN);
145  return runnerName;
146  }
147 
148  NSBundle *mainBundle = [NSBundle mainBundle];
149  NSString *name = [mainBundle objectForInfoDictionaryKey:@"CFBundleDisplayName"];
150  if (!name)
151  name = [mainBundle objectForInfoDictionaryKey:@"CFBundleName"];
152  if (!name)
153  name = [mainBundle objectForInfoDictionaryKey:@"CFBundleExecutable"];
154  if (!name)
155  name = [[[mainBundle executablePath] stringByDeletingPathExtension] lastPathComponent];
156 
157  if (name)
158  return strdup([name UTF8String]);
159  else
160  return strdup("");
161 }
162 
168 const char *VuoApp_getVuoFrameworkPath(void)
169 {
170  static char frameworkPath[PATH_MAX+1] = "";
171  static dispatch_once_t once = 0;
172  dispatch_once(&once, ^{
173  uint32_t imageCount = _dyld_image_count();
174  for (uint32_t i = 0; i < imageCount; ++i)
175  {
176  const char *dylibPath = _dyld_get_image_name(i);
177  char *pos;
178  if ( (pos = strstr(dylibPath, "/Vuo.framework/")) )
179  {
180  strncpy(frameworkPath, dylibPath, pos-dylibPath);
181  break;
182  }
183  }
184  });
185  return frameworkPath;
186 }
187 
195 {
196  static char frameworkPath[PATH_MAX+1] = "";
197  static dispatch_once_t once = 0;
198  dispatch_once(&once, ^{
199  uint32_t imageCount = _dyld_image_count();
200  for (uint32_t i = 0; i < imageCount; ++i)
201  {
202  const char *dylibPath = _dyld_get_image_name(i);
203  char *pos;
204  if ( (pos = strstr(dylibPath, "/VuoRunner.framework/")) )
205  {
206  strncpy(frameworkPath, dylibPath, pos-dylibPath);
207  break;
208  }
209  }
210  });
211  return frameworkPath;
212 }
213 
219 static void VuoApp_initNSApplication(bool requiresDockIcon)
220 {
221  NSAutoreleasePool *pool = [NSAutoreleasePool new];
222 
223  // https://stackoverflow.com/a/11010614/238387
224  NSApplication *app = [NSApplication sharedApplication];
225 
226  if (![app delegate])
227  [app setDelegate:[VuoAppDelegate new]];
228 
229  // When there's no host app present,
230  // create the default menu with the About and Quit menu items,
231  // to be overridden if/when any windows get focus.
232  VuoApp_setMenuItems(NULL);
233 
234  [app setActivationPolicy:requiresDockIcon ? NSApplicationActivationPolicyRegular : NSApplicationActivationPolicyAccessory];
235 
236  // Stop bouncing in the dock.
237  [app finishLaunching];
238 
240 
243 
244  [pool drain];
245 }
246 
247 #ifndef DOXYGEN
248 void VuoApp_fini(void);
249 #endif
250 
268 void VuoApp_init(bool requiresDockIcon)
269 {
270  if (NSApp)
271  {
273  if (requiresDockIcon
274  && NSApplication.sharedApplication.activationPolicy != NSApplicationActivationPolicyRegular)
275  [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
276  });
277  return;
278  }
279 
280  static dispatch_once_t once = 0;
281  dispatch_once(&once, ^{
283  VuoApp_initNSApplication(requiresDockIcon);
284  });
286  });
287 }
288 
294 static void VuoApp_finiWindows(uint64_t compositionUid)
295 {
296  SEL stopRecording = @selector(stopRecording);
297  SEL compositionUidSel = @selector(compositionUid);
298  for (NSWindow *window in [NSApp windows])
299  // Stop any window recordings currently in progress.
300  // This prompts the user for the save destination,
301  // so make sure these complete before shutting the composition down.
302  if ([window respondsToSelector:stopRecording]
303  && [window respondsToSelector:compositionUidSel]
304  && (uint64_t)[window performSelector:compositionUidSel] == compositionUid)
305  [window performSelector:stopRecording];
306 
307  if ([NSApp windows].count)
308  {
309  // Animate removing the app from the dock while the window is fading out (instead of waiting until after).
310  [NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited];
311 
312  double fudge = .1; // Wait a little longer, to ensure the animation's completionHandler gets called.
313  [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:VuoApp_windowFadeSeconds + fudge]];
314  }
315 
316  // Avoid leaving menubar remnants behind.
317  // https://b33p.net/kosada/node/13384
318  [NSApp hide:nil];
319 }
320 
328 void VuoApp_fini(void)
329 {
330  if (!NSApp)
331  return;
332 
333  void *compositionState = vuoCopyCompositionStateFromThreadLocalStorage();
334  uint64_t compositionUid = vuoGetCompositionUniqueIdentifier(compositionState);
335  free(compositionState);
336 
337  if (VuoApp_isMainThread())
338  VuoApp_finiWindows(compositionUid);
339  else
340  {
341  VUOLOG_PROFILE_BEGIN(mainQueue);
342  dispatch_sync(dispatch_get_main_queue(), ^{
343  VUOLOG_PROFILE_END(mainQueue);
344  VuoApp_finiWindows(compositionUid);
345  });
346  }
347 }