Vuo  2.3.2
VuoApp.m
Go to the documentation of this file.
1 
10 #include "module.h"
11 
12 #include "VuoApp.h"
13 
14 #include "VuoMacOSSDKWorkaround.h"
15 #import <AppKit/AppKit.h>
16 
17 #include <dlfcn.h>
18 #include <pthread.h>
19 #include <libproc.h>
20 #include <mach-o/dyld.h>
21 #import <libgen.h>
22 #import <dirent.h>
23 
24 #include "VuoCompositionState.h"
25 #include "VuoEventLoop.h"
26 #import "VuoAppDelegate.h"
27 #import "VuoAppSplashWindow.h"
28 
33 const double VuoApp_windowFadeSeconds = 0.45;
34 
39 {
40  static void **VuoApp_mainThread;
41  static dispatch_once_t once = 0;
42  dispatch_once(&once, ^{
43  VuoApp_mainThread = (void **)dlsym(RTLD_SELF, "VuoApp_mainThread");
44  if (!VuoApp_mainThread)
45  VuoApp_mainThread = (void **)dlsym(RTLD_DEFAULT, "VuoApp_mainThread");
46 
47  if (!VuoApp_mainThread)
48  {
49  VUserLog("Error: Couldn't find VuoApp_mainThread.");
51  exit(1);
52  }
53 
54  if (!*VuoApp_mainThread)
55  {
56  VUserLog("Error: VuoApp_mainThread isn't set.");
58  exit(1);
59  }
60  });
61 
62  return *VuoApp_mainThread == (void *)pthread_self();
63 }
64 
74 void VuoApp_executeOnMainThread(void (^block)(void))
75 {
76  if (!block)
77  return;
78 
79  if (VuoApp_isMainThread())
80  block();
81  else
82  {
83  VUOLOG_PROFILE_BEGIN(mainQueue);
84  dispatch_sync(dispatch_get_main_queue(), ^{
85  VUOLOG_PROFILE_END(mainQueue);
86  block();
87  });
88  }
89 }
90 
112 char *VuoApp_getName(void)
113 {
114  char **dylibPath = (char **)dlsym(RTLD_SELF, "VuoApp_dylibPath");
115  if (!dylibPath)
116  dylibPath = (char **)dlsym(RTLD_DEFAULT, "VuoApp_dylibPath");
117  if (dylibPath)
118  {
119  char *filename = strrchr(*dylibPath, '/');
120  if (filename)
121  {
122  char *name = strdup(filename + 1); // Trim leading slash.
123  name[strlen(name) - strlen("-XXXXXX.dylib")] = 0;
124 
125  if (strcmp(name, "VuoComposition") == 0)
126  {
127  free(name);
128  return strdup("Vuo Composition");
129  }
130 
131  return name;
132  }
133  }
134 
135  pid_t runnerPid = VuoGetRunnerPid();
136  if (runnerPid > 0)
137  {
138  char *runnerName = (char *)malloc(2*MAXCOMLEN);
139  proc_name(runnerPid, runnerName, 2*MAXCOMLEN);
140  return runnerName;
141  }
142 
143  NSBundle *mainBundle = [NSBundle mainBundle];
144  NSString *name = [mainBundle objectForInfoDictionaryKey:@"CFBundleDisplayName"];
145  if (!name)
146  name = [mainBundle objectForInfoDictionaryKey:@"CFBundleName"];
147  if (!name)
148  name = [mainBundle objectForInfoDictionaryKey:@"CFBundleExecutable"];
149  if (!name)
150  name = [[[mainBundle executablePath] stringByDeletingPathExtension] lastPathComponent];
151 
152  if (name)
153  return strdup([name UTF8String]);
154  else
155  return strdup("");
156 }
157 
163 static void VuoApp_initNSApplication(bool requiresDockIcon)
164 {
165  NSAutoreleasePool *pool = [NSAutoreleasePool new];
166 
167  // https://stackoverflow.com/a/11010614/238387
168  NSApplication *app = [NSApplication sharedApplication];
169 
170  if (![app delegate])
171  [app setDelegate:[VuoAppDelegate new]];
172 
173  // When there's no host app present,
174  // create the default menu with the About and Quit menu items,
175  // to be overridden if/when any windows get focus.
176  VuoApp_setMenuItems(NULL);
177 
178  [app setActivationPolicy:requiresDockIcon ? NSApplicationActivationPolicyRegular : NSApplicationActivationPolicyAccessory];
179 
180  // Stop bouncing in the dock.
181  [app finishLaunching];
182 
184 
187 
188  [pool drain];
189 }
190 
191 #ifndef DOXYGEN
192 void VuoApp_fini(void);
193 #endif
194 
212 void VuoApp_init(bool requiresDockIcon)
213 {
214  if (NSApp)
215  {
217  if (requiresDockIcon
218  && NSApplication.sharedApplication.activationPolicy != NSApplicationActivationPolicyRegular)
219  [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
220  });
221  return;
222  }
223 
224  static dispatch_once_t once = 0;
225  dispatch_once(&once, ^{
227  VuoApp_initNSApplication(requiresDockIcon);
228  });
230  });
231 }
232 
238 static void VuoApp_finiWindows(uint64_t compositionUid)
239 {
240  SEL stopRecording = @selector(stopRecording);
241  SEL compositionUidSel = @selector(compositionUid);
242  for (NSWindow *window in [NSApp windows])
243  // Stop any window recordings currently in progress.
244  // This prompts the user for the save destination,
245  // so make sure these complete before shutting the composition down.
246  if ([window respondsToSelector:stopRecording]
247  && [window respondsToSelector:compositionUidSel]
248  && (uint64_t)[window performSelector:compositionUidSel] == compositionUid)
249  [window performSelector:stopRecording];
250 
251  if ([NSApp windows].count)
252  {
253  // Animate removing the app from the dock while the window is fading out (instead of waiting until after).
254  [NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited];
255 
256  double fudge = .1; // Wait a little longer, to ensure the animation's completionHandler gets called.
257  [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:VuoApp_windowFadeSeconds + fudge]];
258  }
259 
260  // Avoid leaving menubar remnants behind.
261  // https://b33p.net/kosada/node/13384
262  [NSApp hide:nil];
263 }
264 
272 void VuoApp_fini(void)
273 {
274  if (!NSApp)
275  return;
276 
277  void *compositionState = vuoCopyCompositionStateFromThreadLocalStorage();
278  uint64_t compositionUid = vuoGetCompositionUniqueIdentifier(compositionState);
279  free(compositionState);
280 
281  if (VuoApp_isMainThread())
282  VuoApp_finiWindows(compositionUid);
283  else
284  {
285  VUOLOG_PROFILE_BEGIN(mainQueue);
286  dispatch_sync(dispatch_get_main_queue(), ^{
287  VUOLOG_PROFILE_END(mainQueue);
288  VuoApp_finiWindows(compositionUid);
289  });
290  }
291 }