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.

juce_mac_MessageManager.mm 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  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: getBroacastEventName()
  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: getBroacastEventName()
  63. object: nil];
  64. }
  65. [delegate release];
  66. }
  67. static NSString* getBroacastEventName()
  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 (applicationShouldTerminate:), applicationShouldTerminate, "I@:@");
  80. addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@");
  81. addMethod (@selector (application:openFile:), application_openFile, "c@:@@");
  82. addMethod (@selector (application:openFiles:), application_openFiles, "v@:@@");
  83. addMethod (@selector (applicationDidBecomeActive:), applicationDidBecomeActive, "v@:@");
  84. addMethod (@selector (applicationDidResignActive:), applicationDidResignActive, "v@:@");
  85. addMethod (@selector (applicationWillUnhide:), applicationWillUnhide, "v@:@");
  86. addMethod (@selector (broadcastMessageCallback:), broadcastMessageCallback, "v@:@");
  87. addMethod (@selector (mainMenuTrackingBegan:), mainMenuTrackingBegan, "v@:@");
  88. addMethod (@selector (mainMenuTrackingEnded:), mainMenuTrackingEnded, "v@:@");
  89. addMethod (@selector (dummyMethod), dummyMethod, "v@:");
  90. registerClass();
  91. }
  92. private:
  93. static NSApplicationTerminateReply applicationShouldTerminate (id /*self*/, SEL, NSApplication*)
  94. {
  95. if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance())
  96. {
  97. app->systemRequestedQuit();
  98. if (! MessageManager::getInstance()->hasStopMessageBeenSent())
  99. return NSTerminateCancel;
  100. }
  101. return NSTerminateNow;
  102. }
  103. static void applicationWillTerminate (id /*self*/, SEL, NSNotification*)
  104. {
  105. JUCEApplicationBase::appWillTerminateByForce();
  106. }
  107. static BOOL application_openFile (id /*self*/, SEL, NSApplication*, NSString* filename)
  108. {
  109. if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance())
  110. {
  111. app->anotherInstanceStarted (quotedIfContainsSpaces (filename));
  112. return YES;
  113. }
  114. return NO;
  115. }
  116. static void application_openFiles (id /*self*/, SEL, NSApplication*, NSArray* filenames)
  117. {
  118. if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance())
  119. {
  120. StringArray files;
  121. for (NSString* f in filenames)
  122. files.add (quotedIfContainsSpaces (f));
  123. if (files.size() > 0)
  124. app->anotherInstanceStarted (files.joinIntoString (" "));
  125. }
  126. }
  127. static void applicationDidBecomeActive (id /*self*/, SEL, NSNotification*) { focusChanged(); }
  128. static void applicationDidResignActive (id /*self*/, SEL, NSNotification*) { focusChanged(); }
  129. static void applicationWillUnhide (id /*self*/, SEL, NSNotification*) { focusChanged(); }
  130. static void broadcastMessageCallback (id /*self*/, SEL, NSNotification* n)
  131. {
  132. NSDictionary* dict = (NSDictionary*) [n userInfo];
  133. const String messageString (nsStringToJuce ((NSString*) [dict valueForKey: nsStringLiteral ("message")]));
  134. MessageManager::getInstance()->deliverBroadcastMessage (messageString);
  135. }
  136. static void mainMenuTrackingBegan (id /*self*/, SEL, NSNotification*)
  137. {
  138. if (menuTrackingChangedCallback != nullptr)
  139. (*menuTrackingChangedCallback) (true);
  140. }
  141. static void mainMenuTrackingEnded (id /*self*/, SEL, NSNotification*)
  142. {
  143. if (menuTrackingChangedCallback != nullptr)
  144. (*menuTrackingChangedCallback) (false);
  145. }
  146. static void dummyMethod (id /*self*/, SEL) {} // (used as a way of running a dummy thread)
  147. private:
  148. static void focusChanged()
  149. {
  150. if (appFocusChangeCallback != nullptr)
  151. (*appFocusChangeCallback)();
  152. }
  153. static String quotedIfContainsSpaces (NSString* file)
  154. {
  155. String s (nsStringToJuce (file));
  156. if (s.containsChar (' '))
  157. s = s.quoted ('"');
  158. return s;
  159. }
  160. };
  161. };
  162. //==============================================================================
  163. void MessageManager::runDispatchLoop()
  164. {
  165. if (! quitMessagePosted) // check that the quit message wasn't already posted..
  166. {
  167. JUCE_AUTORELEASEPOOL
  168. {
  169. // must only be called by the message thread!
  170. jassert (isThisTheMessageThread());
  171. #if JUCE_PROJUCER_LIVE_BUILD
  172. runDispatchLoopUntil (std::numeric_limits<int>::max());
  173. #else
  174. #if JUCE_CATCH_UNHANDLED_EXCEPTIONS
  175. @try
  176. {
  177. [NSApp run];
  178. }
  179. @catch (NSException* e)
  180. {
  181. // An AppKit exception will kill the app, but at least this provides a chance to log it.,
  182. std::runtime_error ex (std::string ("NSException: ") + [[e name] UTF8String] + ", Reason:" + [[e reason] UTF8String]);
  183. JUCEApplication::sendUnhandledException (&ex, __FILE__, __LINE__);
  184. }
  185. @finally
  186. {
  187. }
  188. #else
  189. [NSApp run];
  190. #endif
  191. #endif
  192. }
  193. }
  194. }
  195. static void shutdownNSApp()
  196. {
  197. [NSApp stop: nil];
  198. [NSApp activateIgnoringOtherApps: YES]; // (if the app is inactive, it sits there and ignores the quit request until the next time it gets activated)
  199. [NSEvent startPeriodicEventsAfterDelay: 0 withPeriod: 0.1];
  200. }
  201. void MessageManager::stopDispatchLoop()
  202. {
  203. quitMessagePosted = true;
  204. #if ! JUCE_PROJUCER_LIVE_BUILD
  205. if (isThisTheMessageThread())
  206. {
  207. shutdownNSApp();
  208. }
  209. else
  210. {
  211. struct QuitCallback : public CallbackMessage
  212. {
  213. QuitCallback() {}
  214. void messageCallback() override { shutdownNSApp(); }
  215. };
  216. (new QuitCallback())->post();
  217. }
  218. #endif
  219. }
  220. #if JUCE_MODAL_LOOPS_PERMITTED
  221. bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor)
  222. {
  223. jassert (millisecondsToRunFor >= 0);
  224. jassert (isThisTheMessageThread()); // must only be called by the message thread
  225. uint32 endTime = Time::getMillisecondCounter() + (uint32) millisecondsToRunFor;
  226. while (! quitMessagePosted)
  227. {
  228. JUCE_AUTORELEASEPOOL
  229. {
  230. CFRunLoopRunInMode (kCFRunLoopDefaultMode, 0.001, true);
  231. NSEvent* e = [NSApp nextEventMatchingMask: NSAnyEventMask
  232. untilDate: [NSDate dateWithTimeIntervalSinceNow: 0.001]
  233. inMode: NSDefaultRunLoopMode
  234. dequeue: YES];
  235. if (e != nil && (isEventBlockedByModalComps == nullptr || ! (*isEventBlockedByModalComps) (e)))
  236. [NSApp sendEvent: e];
  237. if (Time::getMillisecondCounter() >= endTime)
  238. break;
  239. }
  240. }
  241. return ! quitMessagePosted;
  242. }
  243. #endif
  244. //==============================================================================
  245. void initialiseNSApplication();
  246. void initialiseNSApplication()
  247. {
  248. JUCE_AUTORELEASEPOOL
  249. {
  250. [NSApplication sharedApplication];
  251. }
  252. }
  253. static AppDelegate* appDelegate = nullptr;
  254. void MessageManager::doPlatformSpecificInitialisation()
  255. {
  256. if (appDelegate == nil)
  257. appDelegate = new AppDelegate();
  258. #if ! (defined (MAC_OS_X_VERSION_10_5) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5)
  259. // This launches a dummy thread, which forces Cocoa to initialise NSThreads correctly (needed prior to 10.5)
  260. if (! [NSThread isMultiThreaded])
  261. [NSThread detachNewThreadSelector: @selector (dummyMethod)
  262. toTarget: appDelegate->delegate
  263. withObject: nil];
  264. #endif
  265. }
  266. void MessageManager::doPlatformSpecificShutdown()
  267. {
  268. delete appDelegate;
  269. appDelegate = nullptr;
  270. }
  271. bool MessageManager::postMessageToSystemQueue (MessageBase* message)
  272. {
  273. jassert (appDelegate != nil);
  274. appDelegate->messageQueue.post (message);
  275. return true;
  276. }
  277. void MessageManager::broadcastMessage (const String& message)
  278. {
  279. NSDictionary* info = [NSDictionary dictionaryWithObject: juceStringToNS (message)
  280. forKey: nsStringLiteral ("message")];
  281. [[NSDistributedNotificationCenter defaultCenter] postNotificationName: AppDelegate::getBroacastEventName()
  282. object: nil
  283. userInfo: info];
  284. }
  285. // Special function used by some plugin classes to re-post carbon events
  286. void repostCurrentNSEvent();
  287. void repostCurrentNSEvent()
  288. {
  289. struct EventReposter : public CallbackMessage
  290. {
  291. EventReposter() : e ([[NSApp currentEvent] retain]) {}
  292. ~EventReposter() { [e release]; }
  293. void messageCallback() override
  294. {
  295. [NSApp postEvent: e atStart: YES];
  296. }
  297. NSEvent* e;
  298. };
  299. (new EventReposter())->post();
  300. }