The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
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.

331 lines
13KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. typedef void (*AppFocusChangeCallback)();
  19. AppFocusChangeCallback appFocusChangeCallback = nullptr;
  20. typedef bool (*CheckEventBlockedByModalComps) (NSEvent*);
  21. CheckEventBlockedByModalComps isEventBlockedByModalComps = nullptr;
  22. typedef void (*MenuTrackingChangedCallback)(bool);
  23. MenuTrackingChangedCallback menuTrackingChangedCallback = nullptr;
  24. //==============================================================================
  25. struct AppDelegate
  26. {
  27. public:
  28. AppDelegate()
  29. {
  30. static AppDelegateClass cls;
  31. delegate = [cls.createInstance() init];
  32. NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
  33. [center addObserver: delegate selector: @selector (mainMenuTrackingBegan:)
  34. name: NSMenuDidBeginTrackingNotification object: nil];
  35. [center addObserver: delegate selector: @selector (mainMenuTrackingEnded:)
  36. name: NSMenuDidEndTrackingNotification object: nil];
  37. if (JUCEApplicationBase::isStandaloneApp())
  38. {
  39. [NSApp setDelegate: delegate];
  40. [[NSDistributedNotificationCenter defaultCenter] addObserver: delegate
  41. selector: @selector (broadcastMessageCallback:)
  42. name: getBroacastEventName()
  43. object: nil];
  44. }
  45. else
  46. {
  47. [center addObserver: delegate selector: @selector (applicationDidResignActive:)
  48. name: NSApplicationDidResignActiveNotification object: NSApp];
  49. [center addObserver: delegate selector: @selector (applicationDidBecomeActive:)
  50. name: NSApplicationDidBecomeActiveNotification object: NSApp];
  51. [center addObserver: delegate selector: @selector (applicationWillUnhide:)
  52. name: NSApplicationWillUnhideNotification object: NSApp];
  53. }
  54. }
  55. ~AppDelegate()
  56. {
  57. [[NSRunLoop currentRunLoop] cancelPerformSelectorsWithTarget: delegate];
  58. [[NSNotificationCenter defaultCenter] removeObserver: delegate];
  59. if (JUCEApplicationBase::isStandaloneApp())
  60. {
  61. [NSApp setDelegate: nil];
  62. [[NSDistributedNotificationCenter defaultCenter] removeObserver: delegate
  63. name: getBroacastEventName()
  64. object: nil];
  65. }
  66. [delegate release];
  67. }
  68. static NSString* getBroacastEventName()
  69. {
  70. return juceStringToNS ("juce_" + String::toHexString (File::getSpecialLocation (File::currentExecutableFile).hashCode64()));
  71. }
  72. MessageQueue messageQueue;
  73. id delegate;
  74. private:
  75. //==============================================================================
  76. struct AppDelegateClass : public ObjCClass <NSObject>
  77. {
  78. AppDelegateClass() : ObjCClass <NSObject> ("JUCEAppDelegate_")
  79. {
  80. addMethod (@selector (applicationShouldTerminate:), applicationShouldTerminate, "I@:@");
  81. addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@");
  82. addMethod (@selector (application:openFile:), application_openFile, "c@:@@");
  83. addMethod (@selector (application:openFiles:), application_openFiles, "v@:@@");
  84. addMethod (@selector (applicationDidBecomeActive:), applicationDidBecomeActive, "v@:@");
  85. addMethod (@selector (applicationDidResignActive:), applicationDidResignActive, "v@:@");
  86. addMethod (@selector (applicationWillUnhide:), applicationWillUnhide, "v@:@");
  87. addMethod (@selector (broadcastMessageCallback:), broadcastMessageCallback, "v@:@");
  88. addMethod (@selector (mainMenuTrackingBegan:), mainMenuTrackingBegan, "v@:@");
  89. addMethod (@selector (mainMenuTrackingEnded:), mainMenuTrackingEnded, "v@:@");
  90. addMethod (@selector (dummyMethod), dummyMethod, "v@:");
  91. registerClass();
  92. }
  93. private:
  94. static NSApplicationTerminateReply applicationShouldTerminate (id /*self*/, SEL, NSApplication*)
  95. {
  96. if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance())
  97. {
  98. app->systemRequestedQuit();
  99. if (! MessageManager::getInstance()->hasStopMessageBeenSent())
  100. return NSTerminateCancel;
  101. }
  102. return NSTerminateNow;
  103. }
  104. static void applicationWillTerminate (id /*self*/, SEL, NSNotification*)
  105. {
  106. JUCEApplicationBase::appWillTerminateByForce();
  107. }
  108. static BOOL application_openFile (id /*self*/, SEL, NSApplication*, NSString* filename)
  109. {
  110. if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance())
  111. {
  112. app->anotherInstanceStarted (quotedIfContainsSpaces (filename));
  113. return YES;
  114. }
  115. return NO;
  116. }
  117. static void application_openFiles (id /*self*/, SEL, NSApplication*, NSArray* filenames)
  118. {
  119. if (JUCEApplicationBase* const app = JUCEApplicationBase::getInstance())
  120. {
  121. StringArray files;
  122. for (unsigned int i = 0; i < [filenames count]; ++i)
  123. files.add (quotedIfContainsSpaces ((NSString*) [filenames objectAtIndex: i]));
  124. if (files.size() > 0)
  125. app->anotherInstanceStarted (files.joinIntoString (" "));
  126. }
  127. }
  128. static void applicationDidBecomeActive (id /*self*/, SEL, NSNotification*) { focusChanged(); }
  129. static void applicationDidResignActive (id /*self*/, SEL, NSNotification*) { focusChanged(); }
  130. static void applicationWillUnhide (id /*self*/, SEL, NSNotification*) { focusChanged(); }
  131. static void broadcastMessageCallback (id /*self*/, SEL, NSNotification* n)
  132. {
  133. NSDictionary* dict = (NSDictionary*) [n userInfo];
  134. const String messageString (nsStringToJuce ((NSString*) [dict valueForKey: nsStringLiteral ("message")]));
  135. MessageManager::getInstance()->deliverBroadcastMessage (messageString);
  136. }
  137. static void mainMenuTrackingBegan (id /*self*/, SEL, NSNotification*)
  138. {
  139. if (menuTrackingChangedCallback != nullptr)
  140. (*menuTrackingChangedCallback) (true);
  141. }
  142. static void mainMenuTrackingEnded (id /*self*/, SEL, NSNotification*)
  143. {
  144. if (menuTrackingChangedCallback != nullptr)
  145. (*menuTrackingChangedCallback) (false);
  146. }
  147. static void dummyMethod (id /*self*/, SEL) {} // (used as a way of running a dummy thread)
  148. private:
  149. static void focusChanged()
  150. {
  151. if (appFocusChangeCallback != nullptr)
  152. (*appFocusChangeCallback)();
  153. }
  154. static String quotedIfContainsSpaces (NSString* file)
  155. {
  156. String s (nsStringToJuce (file));
  157. if (s.containsChar (' '))
  158. s = s.quoted ('"');
  159. return s;
  160. }
  161. };
  162. };
  163. //==============================================================================
  164. void MessageManager::runDispatchLoop()
  165. {
  166. if (! quitMessagePosted) // check that the quit message wasn't already posted..
  167. {
  168. JUCE_AUTORELEASEPOOL
  169. {
  170. // must only be called by the message thread!
  171. jassert (isThisTheMessageThread());
  172. #if JUCE_PROJUCER_LIVE_BUILD
  173. runDispatchLoopUntil (std::numeric_limits<int>::max());
  174. #else
  175. #if JUCE_CATCH_UNHANDLED_EXCEPTIONS
  176. @try
  177. {
  178. [NSApp run];
  179. }
  180. @catch (NSException* e)
  181. {
  182. // An AppKit exception will kill the app, but at least this provides a chance to log it.,
  183. std::runtime_error ex (std::string ("NSException: ") + [[e name] UTF8String] + ", Reason:" + [[e reason] UTF8String]);
  184. JUCEApplication::sendUnhandledException (&ex, __FILE__, __LINE__);
  185. }
  186. @finally
  187. {
  188. }
  189. #else
  190. [NSApp run];
  191. #endif
  192. #endif
  193. }
  194. }
  195. }
  196. void MessageManager::stopDispatchLoop()
  197. {
  198. jassert (isThisTheMessageThread()); // must only be called by the message thread
  199. quitMessagePosted = true;
  200. #if ! JUCE_PROJUCER_LIVE_BUILD
  201. [NSApp stop: nil];
  202. [NSApp activateIgnoringOtherApps: YES]; // (if the app is inactive, it sits there and ignores the quit request until the next time it gets activated)
  203. [NSEvent startPeriodicEventsAfterDelay: 0 withPeriod: 0.1];
  204. #endif
  205. }
  206. #if JUCE_MODAL_LOOPS_PERMITTED
  207. bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor)
  208. {
  209. jassert (millisecondsToRunFor >= 0);
  210. jassert (isThisTheMessageThread()); // must only be called by the message thread
  211. uint32 endTime = Time::getMillisecondCounter() + (uint32) millisecondsToRunFor;
  212. while (! quitMessagePosted)
  213. {
  214. JUCE_AUTORELEASEPOOL
  215. {
  216. CFRunLoopRunInMode (kCFRunLoopDefaultMode, 0.001, true);
  217. NSEvent* e = [NSApp nextEventMatchingMask: NSAnyEventMask
  218. untilDate: [NSDate dateWithTimeIntervalSinceNow: 0.001]
  219. inMode: NSDefaultRunLoopMode
  220. dequeue: YES];
  221. if (e != nil && (isEventBlockedByModalComps == nullptr || ! (*isEventBlockedByModalComps) (e)))
  222. [NSApp sendEvent: e];
  223. if (Time::getMillisecondCounter() >= endTime)
  224. break;
  225. }
  226. }
  227. return ! quitMessagePosted;
  228. }
  229. #endif
  230. //==============================================================================
  231. void initialiseNSApplication();
  232. void initialiseNSApplication()
  233. {
  234. JUCE_AUTORELEASEPOOL
  235. {
  236. [NSApplication sharedApplication];
  237. }
  238. }
  239. static AppDelegate* appDelegate = nullptr;
  240. void MessageManager::doPlatformSpecificInitialisation()
  241. {
  242. if (appDelegate == nil)
  243. appDelegate = new AppDelegate();
  244. #if ! (defined (MAC_OS_X_VERSION_10_5) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5)
  245. // This launches a dummy thread, which forces Cocoa to initialise NSThreads correctly (needed prior to 10.5)
  246. if (! [NSThread isMultiThreaded])
  247. [NSThread detachNewThreadSelector: @selector (dummyMethod)
  248. toTarget: appDelegate->delegate
  249. withObject: nil];
  250. #endif
  251. }
  252. void MessageManager::doPlatformSpecificShutdown()
  253. {
  254. delete appDelegate;
  255. appDelegate = nullptr;
  256. }
  257. bool MessageManager::postMessageToSystemQueue (MessageBase* message)
  258. {
  259. jassert (appDelegate != nil);
  260. appDelegate->messageQueue.post (message);
  261. return true;
  262. }
  263. void MessageManager::broadcastMessage (const String& message)
  264. {
  265. NSDictionary* info = [NSDictionary dictionaryWithObject: juceStringToNS (message)
  266. forKey: nsStringLiteral ("message")];
  267. [[NSDistributedNotificationCenter defaultCenter] postNotificationName: AppDelegate::getBroacastEventName()
  268. object: nil
  269. userInfo: info];
  270. }