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 static void VuoApp_initNSApplication(bool requiresDockIcon)
169 {
170  NSAutoreleasePool *pool = [NSAutoreleasePool new];
171 
172  // https://stackoverflow.com/a/11010614/238387
173  NSApplication *app = [NSApplication sharedApplication];
174 
175  if (![app delegate])
176  [app setDelegate:[VuoAppDelegate new]];
177 
178  // When there's no host app present,
179  // create the default menu with the About and Quit menu items,
180  // to be overridden if/when any windows get focus.
181  VuoApp_setMenuItems(NULL);
182 
183  [app setActivationPolicy:requiresDockIcon ? NSApplicationActivationPolicyRegular : NSApplicationActivationPolicyAccessory];
184 
185  // Stop bouncing in the dock.
186  [app finishLaunching];
187 
189 
192 
193  [pool drain];
194 }
195 
196 #ifndef DOXYGEN
197 void VuoApp_fini(void);
198 #endif
199 
217 void VuoApp_init(bool requiresDockIcon)
218 {
219  if (NSApp)
220  {
222  if (requiresDockIcon
223  && NSApplication.sharedApplication.activationPolicy != NSApplicationActivationPolicyRegular)
224  [NSApp setActivationPolicy:NSApplicationActivationPolicyRegular];
225  });
226  return;
227  }
228 
229  static dispatch_once_t once = 0;
230  dispatch_once(&once, ^{
232  VuoApp_initNSApplication(requiresDockIcon);
233  });
235  });
236 }
237 
243 static void VuoApp_finiWindows(uint64_t compositionUid)
244 {
245  SEL stopRecording = @selector(stopRecording);
246  SEL compositionUidSel = @selector(compositionUid);
247  for (NSWindow *window in [NSApp windows])
248  // Stop any window recordings currently in progress.
249  // This prompts the user for the save destination,
250  // so make sure these complete before shutting the composition down.
251  if ([window respondsToSelector:stopRecording]
252  && [window respondsToSelector:compositionUidSel]
253  && (uint64_t)[window performSelector:compositionUidSel] == compositionUid)
254  [window performSelector:stopRecording];
255 
256  if ([NSApp windows].count)
257  {
258  // Animate removing the app from the dock while the window is fading out (instead of waiting until after).
259  [NSApp setActivationPolicy:NSApplicationActivationPolicyProhibited];
260 
261  double fudge = .1; // Wait a little longer, to ensure the animation's completionHandler gets called.
262  [NSRunLoop.currentRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:VuoApp_windowFadeSeconds + fudge]];
263  }
264 
265  // Avoid leaving menubar remnants behind.
266  // https://b33p.net/kosada/node/13384
267  [NSApp hide:nil];
268 }
269 
277 void VuoApp_fini(void)
278 {
279  if (!NSApp)
280  return;
281 
282  void *compositionState = vuoCopyCompositionStateFromThreadLocalStorage();
283  uint64_t compositionUid = vuoGetCompositionUniqueIdentifier(compositionState);
284  free(compositionState);
285 
286  if (VuoApp_isMainThread())
287  VuoApp_finiWindows(compositionUid);
288  else
289  {
290  VUOLOG_PROFILE_BEGIN(mainQueue);
291  dispatch_sync(dispatch_get_main_queue(), ^{
292  VUOLOG_PROFILE_END(mainQueue);
293  VuoApp_finiWindows(compositionUid);
294  });
295  }
296 }