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.

306 lines
12KB

  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. //==============================================================================
  23. struct AppDelegate
  24. {
  25. public:
  26. AppDelegate()
  27. {
  28. static AppDelegateClass cls;
  29. delegate = [cls.createInstance() init];
  30. if (JUCEApplicationBase::isStandaloneApp())
  31. {
  32. [NSApp setDelegate: delegate];
  33. [[NSDistributedNotificationCenter defaultCenter] addObserver: delegate
  34. selector: @selector (broadcastMessageCallback:)
  35. name: getBroacastEventName()
  36. object: nil];
  37. }
  38. else
  39. {
  40. NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
  41. [center addObserver: delegate selector: @selector (applicationDidResignActive:)
  42. name: NSApplicationDidResignActiveNotification object: NSApp];
  43. [center addObserver: delegate selector: @selector (applicationDidBecomeActive:)
  44. name: NSApplicationDidBecomeActiveNotification object: NSApp];
  45. [center addObserver: delegate selector: @selector (applicationWillUnhide:)
  46. name: NSApplicationWillUnhideNotification object: NSApp];
  47. }
  48. }
  49. ~AppDelegate()
  50. {
  51. [[NSRunLoop currentRunLoop] cancelPerformSelectorsWithTarget: delegate];
  52. [[NSNotificationCenter defaultCenter] removeObserver: delegate];
  53. if (JUCEApplicationBase::isStandaloneApp())
  54. {
  55. [NSApp setDelegate: nil];
  56. [[NSDistributedNotificationCenter defaultCenter] removeObserver: delegate
  57. name: getBroacastEventName()
  58. object: nil];
  59. }
  60. [delegate release];
  61. }
  62. static NSString* getBroacastEventName()
  63. {
  64. return juceStringToNS ("juce_" + String::toHexString (File::getSpecialLocation (File::currentExecutableFile).hashCode64()));
  65. }
  66. MessageQueue messageQueue;
  67. id delegate;
  68. private:
  69. CFRunLoopRef runLoop;
  70. CFRunLoopSourceRef runLoopSource;
  71. //==============================================================================
  72. struct AppDelegateClass : public ObjCClass <NSObject>
  73. {
  74. AppDelegateClass() : ObjCClass <NSObject> ("JUCEAppDelegate_")
  75. {
  76. addMethod (@selector (applicationShouldTerminate:), applicationShouldTerminate, "I@:@");
  77. addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@");
  78. addMethod (@selector (application:openFile:), application_openFile, "c@:@@");
  79. addMethod (@selector (application:openFiles:), application_openFiles, "v@:@@");
  80. addMethod (@selector (applicationDidBecomeActive:), applicationDidBecomeActive, "v@:@");
  81. addMethod (@selector (applicationDidResignActive:), applicationDidResignActive, "v@:@");
  82. addMethod (@selector (applicationWillUnhide:), applicationWillUnhide, "v@:@");
  83. addMethod (@selector (broadcastMessageCallback:), broadcastMessageCallback, "v@:@");
  84. addMethod (@selector (dummyMethod), dummyMethod, "v@:");
  85. registerClass();
  86. }
  87. private:
  88. static NSApplicationTerminateReply applicationShouldTerminate (id /*self*/, SEL, NSApplication*)
  89. {
  90. JUCEApplicationBase* const app = JUCEApplicationBase::getInstance();
  91. if (app != nullptr)
  92. {
  93. app->systemRequestedQuit();
  94. if (! MessageManager::getInstance()->hasStopMessageBeenSent())
  95. return NSTerminateCancel;
  96. }
  97. return NSTerminateNow;
  98. }
  99. static void applicationWillTerminate (id /*self*/, SEL, NSNotification*)
  100. {
  101. JUCEApplicationBase::appWillTerminateByForce();
  102. }
  103. static BOOL application_openFile (id /*self*/, SEL, NSApplication*, NSString* filename)
  104. {
  105. JUCEApplicationBase* const app = JUCEApplicationBase::getInstance();
  106. if (app != nullptr)
  107. {
  108. app->anotherInstanceStarted (quotedIfContainsSpaces (filename));
  109. return YES;
  110. }
  111. return NO;
  112. }
  113. static void application_openFiles (id /*self*/, SEL, NSApplication*, NSArray* filenames)
  114. {
  115. JUCEApplicationBase* const app = JUCEApplicationBase::getInstance();
  116. if (app != nullptr)
  117. {
  118. StringArray files;
  119. for (unsigned int i = 0; i < [filenames count]; ++i)
  120. files.add (quotedIfContainsSpaces ((NSString*) [filenames objectAtIndex: i]));
  121. if (files.size() > 0)
  122. app->anotherInstanceStarted (files.joinIntoString (" "));
  123. }
  124. }
  125. static void applicationDidBecomeActive (id /*self*/, SEL, NSNotification*) { focusChanged(); }
  126. static void applicationDidResignActive (id /*self*/, SEL, NSNotification*) { focusChanged(); }
  127. static void applicationWillUnhide (id /*self*/, SEL, NSNotification*) { focusChanged(); }
  128. static void broadcastMessageCallback (id /*self*/, SEL, NSNotification* n)
  129. {
  130. NSDictionary* dict = (NSDictionary*) [n userInfo];
  131. const String messageString (nsStringToJuce ((NSString*) [dict valueForKey: nsStringLiteral ("message")]));
  132. MessageManager::getInstance()->deliverBroadcastMessage (messageString);
  133. }
  134. static void dummyMethod (id /*self*/, SEL) {} // (used as a way of running a dummy thread)
  135. private:
  136. static void focusChanged()
  137. {
  138. if (appFocusChangeCallback != nullptr)
  139. (*appFocusChangeCallback)();
  140. }
  141. static String quotedIfContainsSpaces (NSString* file)
  142. {
  143. String s (nsStringToJuce (file));
  144. if (s.containsChar (' '))
  145. s = s.quoted ('"');
  146. return s;
  147. }
  148. };
  149. };
  150. //==============================================================================
  151. void MessageManager::runDispatchLoop()
  152. {
  153. if (! quitMessagePosted) // check that the quit message wasn't already posted..
  154. {
  155. JUCE_AUTORELEASEPOOL
  156. // must only be called by the message thread!
  157. jassert (isThisTheMessageThread());
  158. #if JUCE_CATCH_UNHANDLED_EXCEPTIONS
  159. @try
  160. {
  161. [NSApp run];
  162. }
  163. @catch (NSException* e)
  164. {
  165. // An AppKit exception will kill the app, but at least this provides a chance to log it.,
  166. std::runtime_error ex (std::string ("NSException: ") + [[e name] UTF8String] + ", Reason:" + [[e reason] UTF8String]);
  167. JUCEApplication::sendUnhandledException (&ex, __FILE__, __LINE__);
  168. }
  169. @finally
  170. {
  171. }
  172. #else
  173. [NSApp run];
  174. #endif
  175. }
  176. }
  177. void MessageManager::stopDispatchLoop()
  178. {
  179. quitMessagePosted = true;
  180. [NSApp stop: nil];
  181. [NSApp activateIgnoringOtherApps: YES]; // (if the app is inactive, it sits there and ignores the quit request until the next time it gets activated)
  182. [NSEvent startPeriodicEventsAfterDelay: 0 withPeriod: 0.1];
  183. }
  184. #if JUCE_MODAL_LOOPS_PERMITTED
  185. bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor)
  186. {
  187. jassert (millisecondsToRunFor >= 0);
  188. jassert (isThisTheMessageThread()); // must only be called by the message thread
  189. uint32 endTime = Time::getMillisecondCounter() + (uint32) millisecondsToRunFor;
  190. while (! quitMessagePosted)
  191. {
  192. JUCE_AUTORELEASEPOOL
  193. CFRunLoopRunInMode (kCFRunLoopDefaultMode, 0.001, true);
  194. NSEvent* e = [NSApp nextEventMatchingMask: NSAnyEventMask
  195. untilDate: [NSDate dateWithTimeIntervalSinceNow: 0.001]
  196. inMode: NSDefaultRunLoopMode
  197. dequeue: YES];
  198. if (e != nil && (isEventBlockedByModalComps == nullptr || ! (*isEventBlockedByModalComps) (e)))
  199. [NSApp sendEvent: e];
  200. if (Time::getMillisecondCounter() >= endTime)
  201. break;
  202. }
  203. return ! quitMessagePosted;
  204. }
  205. #endif
  206. //==============================================================================
  207. void initialiseNSApplication();
  208. void initialiseNSApplication()
  209. {
  210. JUCE_AUTORELEASEPOOL
  211. [NSApplication sharedApplication];
  212. }
  213. static AppDelegate* appDelegate = nullptr;
  214. void MessageManager::doPlatformSpecificInitialisation()
  215. {
  216. if (appDelegate == nil)
  217. appDelegate = new AppDelegate();
  218. #if ! (defined (MAC_OS_X_VERSION_10_5) && MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5)
  219. // This launches a dummy thread, which forces Cocoa to initialise NSThreads correctly (needed prior to 10.5)
  220. if (! [NSThread isMultiThreaded])
  221. [NSThread detachNewThreadSelector: @selector (dummyMethod)
  222. toTarget: appDelegate->delegate
  223. withObject: nil];
  224. #endif
  225. }
  226. void MessageManager::doPlatformSpecificShutdown()
  227. {
  228. delete appDelegate;
  229. appDelegate = nullptr;
  230. }
  231. bool MessageManager::postMessageToSystemQueue (MessageBase* message)
  232. {
  233. jassert (appDelegate != nil);
  234. appDelegate->messageQueue.post (message);
  235. return true;
  236. }
  237. void MessageManager::broadcastMessage (const String& message)
  238. {
  239. NSDictionary* info = [NSDictionary dictionaryWithObject: juceStringToNS (message)
  240. forKey: nsStringLiteral ("message")];
  241. [[NSDistributedNotificationCenter defaultCenter] postNotificationName: AppDelegate::getBroacastEventName()
  242. object: nil
  243. userInfo: info];
  244. }