Audio plugin host https://kx.studio/carla
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

434 lines
16KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2013 - Raw Material Software Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. typedef void (*AppFocusChangeCallback)();
  18. AppFocusChangeCallback appFocusChangeCallback = nullptr;
  19. typedef bool (*CheckEventBlockedByModalComps) (NSEvent*);
  20. CheckEventBlockedByModalComps isEventBlockedByModalComps = nullptr;
  21. typedef void (*MenuTrackingChangedCallback)(bool);
  22. MenuTrackingChangedCallback menuTrackingChangedCallback = nullptr;
  23. //==============================================================================
  24. struct AppDelegate
  25. {
  26. public:
  27. AppDelegate()
  28. {
  29. static AppDelegateClass cls;
  30. delegate = [cls.createInstance() init];
  31. NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
  32. [center addObserver: delegate selector: @selector (mainMenuTrackingBegan:)
  33. name: NSMenuDidBeginTrackingNotification object: nil];
  34. [center addObserver: delegate selector: @selector (mainMenuTrackingEnded:)
  35. name: NSMenuDidEndTrackingNotification object: nil];
  36. if (JUCEApplicationBase::isStandaloneApp())
  37. {
  38. [NSApp setDelegate: delegate];
  39. [[NSDistributedNotificationCenter defaultCenter] addObserver: delegate
  40. selector: @selector (broadcastMessageCallback:)
  41. name: getBroadcastEventName()
  42. object: nil];
  43. }
  44. else
  45. {
  46. [center addObserver: delegate selector: @selector (applicationDidResignActive:)
  47. name: NSApplicationDidResignActiveNotification object: NSApp];
  48. [center addObserver: delegate selector: @selector (applicationDidBecomeActive:)
  49. name: NSApplicationDidBecomeActiveNotification object: NSApp];
  50. [center addObserver: delegate selector: @selector (applicationWillUnhide:)
  51. name: NSApplicationWillUnhideNotification object: NSApp];
  52. }
  53. }
  54. ~AppDelegate()
  55. {
  56. [[NSRunLoop currentRunLoop] cancelPerformSelectorsWithTarget: delegate];
  57. [[NSNotificationCenter defaultCenter] removeObserver: delegate];
  58. if (JUCEApplicationBase::isStandaloneApp())
  59. {
  60. [NSApp setDelegate: nil];
  61. [[NSDistributedNotificationCenter defaultCenter] removeObserver: delegate
  62. name: getBroadcastEventName()
  63. object: nil];
  64. }
  65. [delegate release];
  66. }
  67. static NSString* getBroadcastEventName()
  68. {
  69. return juceStringToNS ("juce_" + String::toHexString (File::getSpecialLocation (File::currentExecutableFile).hashCode64()));
  70. }
  71. MessageQueue messageQueue;
  72. id delegate;
  73. private:
  74. //==============================================================================
  75. struct AppDelegateClass : public ObjCClass<NSObject>
  76. {
  77. AppDelegateClass() : ObjCClass<NSObject> ("JUCEAppDelegate_")
  78. {
  79. addMethod (@selector (applicationWillFinishLaunching:), applicationWillFinishLaunching, "v@:@@");
  80. addMethod (@selector (getUrl:withReplyEvent:), getUrl_withReplyEvent, "v@:@@");
  81. addMethod (@selector (applicationShouldTerminate:), applicationShouldTerminate, "I@:@");
  82. addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@");
  83. addMethod (@selector (application:openFile:), application_openFile, "c@:@@");
  84. addMethod (@selector (application:openFiles:), application_openFiles, "v@:@@");
  85. addMethod (@selector (applicationDidBecomeActive:), applicationDidBecomeActive, "v@:@");
  86. addMethod (@selector (applicationDidResignActive:), applicationDidResignActive, "v@:@");
  87. addMethod (@selector (applicationWillUnhide:), applicationWillUnhide, "v@:@");
  88. addMethod (@selector (broadcastMessageCallback:), broadcastMessageCallback, "v@:@");
  89. addMethod (@selector (mainMenuTrackingBegan:), mainMenuTrackingBegan, "v@:@");
  90. addMethod (@selector (mainMenuTrackingEnded:), mainMenuTrackingEnded, "v@:@");
  91. addMethod (@selector (dummyMethod), dummyMethod, "v@:");
  92. registerClass();
  93. }
  94. private:
  95. static void applicationWillFinishLaunching (id self, SEL, NSApplication*, NSNotification*)
  96. {
  97. [[NSAppleEventManager sharedAppleEventManager] setEventHandler: self
  98. andSelector: @selector (getUrl:withReplyEvent:)
  99. forEventClass: kInternetEventClass
  100. andEventID: kAEGetURL];
  101. }
  102. static NSApplicationTerminateReply applicationShouldTerminate (id /*self*/, SEL, NSApplication*)
  103. {
  104. if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance())
  105. {
  106. app->systemRequestedQuit();
  107. if (! MessageManager::getInstance()->hasStopMessageBeenSent())
  108. return NSTerminateCancel;
  109. }
  110. return NSTerminateNow;
  111. }
  112. static void applicationWillTerminate (id /*self*/, SEL, NSNotification*)
  113. {
  114. JUCEApplicationBase::appWillTerminateByForce();
  115. }
  116. static BOOL application_openFile (id /*self*/, SEL, NSApplication*, NSString* filename)
  117. {
  118. if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance())
  119. {
  120. app->anotherInstanceStarted (quotedIfContainsSpaces (filename));
  121. return YES;
  122. }
  123. return NO;
  124. }
  125. static void application_openFiles (id /*self*/, SEL, NSApplication*, NSArray* filenames)
  126. {
  127. if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance())
  128. {
  129. StringArray files;
  130. for (NSString* f in filenames)
  131. files.add (quotedIfContainsSpaces (f));
  132. if (files.size() > 0)
  133. app->anotherInstanceStarted (files.joinIntoString (" "));
  134. }
  135. }
  136. static void applicationDidBecomeActive (id /*self*/, SEL, NSNotification*) { focusChanged(); }
  137. static void applicationDidResignActive (id /*self*/, SEL, NSNotification*) { focusChanged(); }
  138. static void applicationWillUnhide (id /*self*/, SEL, NSNotification*) { focusChanged(); }
  139. static void broadcastMessageCallback (id /*self*/, SEL, NSNotification* n)
  140. {
  141. NSDictionary* dict = (NSDictionary*) [n userInfo];
  142. const String messageString (nsStringToJuce ((NSString*) [dict valueForKey: nsStringLiteral ("message")]));
  143. MessageManager::getInstance()->deliverBroadcastMessage (messageString);
  144. }
  145. static void mainMenuTrackingBegan (id /*self*/, SEL, NSNotification*)
  146. {
  147. if (menuTrackingChangedCallback != nullptr)
  148. (*menuTrackingChangedCallback) (true);
  149. }
  150. static void mainMenuTrackingEnded (id /*self*/, SEL, NSNotification*)
  151. {
  152. if (menuTrackingChangedCallback != nullptr)
  153. (*menuTrackingChangedCallback) (false);
  154. }
  155. static void dummyMethod (id /*self*/, SEL) {} // (used as a way of running a dummy thread)
  156. static void focusChanged()
  157. {
  158. if (appFocusChangeCallback != nullptr)
  159. (*appFocusChangeCallback)();
  160. }
  161. static void getUrl_withReplyEvent (id /*self*/, SEL, NSAppleEventDescriptor* event, NSAppleEventDescriptor*)
  162. {
  163. if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance())
  164. app->anotherInstanceStarted (quotedIfContainsSpaces ([[event paramDescriptorForKeyword: keyDirectObject] stringValue]));
  165. }
  166. static String quotedIfContainsSpaces (NSString* file)
  167. {
  168. String s (nsStringToJuce (file));
  169. if (s.containsChar (' '))
  170. s = s.quoted ('"');
  171. return s;
  172. }
  173. };
  174. };
  175. //==============================================================================
  176. void MessageManager::runDispatchLoop()
  177. {
  178. if (! quitMessagePosted) // check that the quit message wasn't already posted..
  179. {
  180. JUCE_AUTORELEASEPOOL
  181. {
  182. // must only be called by the message thread!
  183. jassert (isThisTheMessageThread());
  184. #if JUCE_PROJUCER_LIVE_BUILD
  185. runDispatchLoopUntil (std::numeric_limits<int>::max());
  186. #else
  187. #if JUCE_CATCH_UNHANDLED_EXCEPTIONS
  188. @try
  189. {
  190. [NSApp run];
  191. }
  192. @catch (NSException* e)
  193. {
  194. // An AppKit exception will kill the app, but at least this provides a chance to log it.,
  195. std::runtime_error ex (std::string ("NSException: ") + [[e name] UTF8String] + ", Reason:" + [[e reason] UTF8String]);
  196. JUCEApplication::sendUnhandledException (&ex, __FILE__, __LINE__);
  197. }
  198. @finally
  199. {
  200. }
  201. #else
  202. [NSApp run];
  203. #endif
  204. #endif
  205. }
  206. }
  207. }
  208. static void shutdownNSApp()
  209. {
  210. [NSApp stop: nil];
  211. [NSApp activateIgnoringOtherApps: YES]; // (if the app is inactive, it sits there and ignores the quit request until the next time it gets activated)
  212. [NSEvent startPeriodicEventsAfterDelay: 0 withPeriod: 0.1];
  213. }
  214. void MessageManager::stopDispatchLoop()
  215. {
  216. quitMessagePosted = true;
  217. #if ! JUCE_PROJUCER_LIVE_BUILD
  218. if (isThisTheMessageThread())
  219. {
  220. shutdownNSApp();
  221. }
  222. else
  223. {
  224. struct QuitCallback : public CallbackMessage
  225. {
  226. QuitCallback() {}
  227. void messageCallback() override { shutdownNSApp(); }
  228. };
  229. (new QuitCallback())->post();
  230. }
  231. #endif
  232. }
  233. #if JUCE_MODAL_LOOPS_PERMITTED
  234. bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor)
  235. {
  236. jassert (millisecondsToRunFor >= 0);
  237. jassert (isThisTheMessageThread()); // must only be called by the message thread
  238. uint32 endTime = Time::getMillisecondCounter() + (uint32) millisecondsToRunFor;
  239. while (! quitMessagePosted)
  240. {
  241. JUCE_AUTORELEASEPOOL
  242. {
  243. CFRunLoopRunInMode (kCFRunLoopDefaultMode, 0.001, true);
  244. NSEvent* e = [NSApp nextEventMatchingMask: NSAnyEventMask
  245. untilDate: [NSDate dateWithTimeIntervalSinceNow: 0.001]
  246. inMode: NSDefaultRunLoopMode
  247. dequeue: YES];
  248. if (e != nil && (isEventBlockedByModalComps == nullptr || ! (*isEventBlockedByModalComps) (e)))
  249. [NSApp sendEvent: e];
  250. if (Time::getMillisecondCounter() >= endTime)
  251. break;
  252. }
  253. }
  254. return ! quitMessagePosted;
  255. }
  256. #endif
  257. //==============================================================================
  258. void initialiseNSApplication();
  259. void initialiseNSApplication()
  260. {
  261. JUCE_AUTORELEASEPOOL
  262. {
  263. [NSApplication sharedApplication];
  264. }
  265. }
  266. static AppDelegate* appDelegate = nullptr;
  267. void MessageManager::doPlatformSpecificInitialisation()
  268. {
  269. if (appDelegate == nil)
  270. appDelegate = new AppDelegate();
  271. #if ! (defined (MAC_OS_X_VERSION_10_5) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5)
  272. // This launches a dummy thread, which forces Cocoa to initialise NSThreads correctly (needed prior to 10.5)
  273. if (! [NSThread isMultiThreaded])
  274. [NSThread detachNewThreadSelector: @selector (dummyMethod)
  275. toTarget: appDelegate->delegate
  276. withObject: nil];
  277. #endif
  278. }
  279. void MessageManager::doPlatformSpecificShutdown()
  280. {
  281. delete appDelegate;
  282. appDelegate = nullptr;
  283. }
  284. bool MessageManager::postMessageToSystemQueue (MessageBase* message)
  285. {
  286. jassert (appDelegate != nil);
  287. appDelegate->messageQueue.post (message);
  288. return true;
  289. }
  290. void MessageManager::broadcastMessage (const String& message)
  291. {
  292. NSDictionary* info = [NSDictionary dictionaryWithObject: juceStringToNS (message)
  293. forKey: nsStringLiteral ("message")];
  294. [[NSDistributedNotificationCenter defaultCenter] postNotificationName: AppDelegate::getBroadcastEventName()
  295. object: nil
  296. userInfo: info];
  297. }
  298. // Special function used by some plugin classes to re-post carbon events
  299. void repostCurrentNSEvent();
  300. void repostCurrentNSEvent()
  301. {
  302. struct EventReposter : public CallbackMessage
  303. {
  304. EventReposter() : e ([[NSApp currentEvent] retain]) {}
  305. ~EventReposter() { [e release]; }
  306. void messageCallback() override
  307. {
  308. [NSApp postEvent: e atStart: YES];
  309. }
  310. NSEvent* e;
  311. };
  312. (new EventReposter())->post();
  313. }
  314. //==============================================================================
  315. #if JUCE_MAC
  316. struct MountedVolumeListChangeDetector::Pimpl
  317. {
  318. Pimpl (MountedVolumeListChangeDetector& d) : owner (d)
  319. {
  320. static ObserverClass cls;
  321. delegate = [cls.createInstance() init];
  322. ObserverClass::setOwner (delegate, this);
  323. NSNotificationCenter* nc = [[NSWorkspace sharedWorkspace] notificationCenter];
  324. [nc addObserver: delegate selector: @selector (changed:) name: NSWorkspaceDidMountNotification object: nil];
  325. [nc addObserver: delegate selector: @selector (changed:) name: NSWorkspaceDidUnmountNotification object: nil];
  326. }
  327. ~Pimpl()
  328. {
  329. [[[NSWorkspace sharedWorkspace] notificationCenter] removeObserver: delegate];
  330. [delegate release];
  331. }
  332. private:
  333. MountedVolumeListChangeDetector& owner;
  334. id delegate;
  335. struct ObserverClass : public ObjCClass<NSObject>
  336. {
  337. ObserverClass() : ObjCClass<NSObject> ("JUCEDriveObserver_")
  338. {
  339. addIvar<Pimpl*> ("owner");
  340. addMethod (@selector (changed:), changed, "v@:@");
  341. addProtocol (@protocol (NSTextInput));
  342. registerClass();
  343. }
  344. static Pimpl* getOwner (id self) { return getIvar<Pimpl*> (self, "owner"); }
  345. static void setOwner (id self, Pimpl* owner) { object_setInstanceVariable (self, "owner", owner); }
  346. static void changed (id self, SEL, NSNotification*)
  347. {
  348. getOwner (self)->owner.mountedVolumeListChanged();
  349. }
  350. };
  351. };
  352. MountedVolumeListChangeDetector::MountedVolumeListChangeDetector() { pimpl = new Pimpl (*this); }
  353. MountedVolumeListChangeDetector::~MountedVolumeListChangeDetector() {}
  354. #endif