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.

436 lines
14KB

  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. /* When you use multiple DLLs which share similarly-named obj-c classes - like
  24. for example having more than one juce plugin loaded into a host, then when a
  25. method is called, the actual code that runs might actually be in a different module
  26. than the one you expect... So any calls to library functions or statics that are
  27. made inside obj-c methods will probably end up getting executed in a different DLL's
  28. memory space. Not a great thing to happen - this obviously leads to bizarre crashes.
  29. To work around this insanity, I'm only allowing obj-c methods to make calls to
  30. virtual methods of an object that's known to live inside the right module's space.
  31. */
  32. class AppDelegateRedirector
  33. {
  34. public:
  35. AppDelegateRedirector() {}
  36. virtual ~AppDelegateRedirector() {}
  37. virtual NSApplicationTerminateReply shouldTerminate()
  38. {
  39. if (JUCEApplicationBase::getInstance() != nullptr)
  40. {
  41. JUCEApplicationBase::getInstance()->systemRequestedQuit();
  42. if (! MessageManager::getInstance()->hasStopMessageBeenSent())
  43. return NSTerminateCancel;
  44. }
  45. return NSTerminateNow;
  46. }
  47. virtual void willTerminate()
  48. {
  49. JUCEApplicationBase::appWillTerminateByForce();
  50. }
  51. virtual BOOL openFile (NSString* filename)
  52. {
  53. if (JUCEApplicationBase::getInstance() != nullptr)
  54. {
  55. JUCEApplicationBase::getInstance()->anotherInstanceStarted (quotedIfContainsSpaces (filename));
  56. return YES;
  57. }
  58. return NO;
  59. }
  60. virtual void openFiles (NSArray* filenames)
  61. {
  62. StringArray files;
  63. for (unsigned int i = 0; i < [filenames count]; ++i)
  64. files.add (quotedIfContainsSpaces ((NSString*) [filenames objectAtIndex: i]));
  65. if (files.size() > 0 && JUCEApplicationBase::getInstance() != nullptr)
  66. JUCEApplicationBase::getInstance()->anotherInstanceStarted (files.joinIntoString (" "));
  67. }
  68. virtual void focusChanged()
  69. {
  70. if (appFocusChangeCallback != nullptr)
  71. (*appFocusChangeCallback)();
  72. }
  73. struct CallbackMessagePayload
  74. {
  75. MessageCallbackFunction* function;
  76. void* parameter;
  77. void* volatile result;
  78. bool volatile hasBeenExecuted;
  79. };
  80. virtual void performCallback (CallbackMessagePayload* pl)
  81. {
  82. pl->result = (*pl->function) (pl->parameter);
  83. pl->hasBeenExecuted = true;
  84. }
  85. virtual void deleteSelf()
  86. {
  87. delete this;
  88. }
  89. void postMessage (Message* const m)
  90. {
  91. messageQueue.post (m);
  92. }
  93. static NSString* getBroacastEventName()
  94. {
  95. return juceStringToNS ("juce_" + String::toHexString (File::getSpecialLocation (File::currentExecutableFile).hashCode64()));
  96. }
  97. private:
  98. CFRunLoopRef runLoop;
  99. CFRunLoopSourceRef runLoopSource;
  100. MessageQueue messageQueue;
  101. static const String quotedIfContainsSpaces (NSString* file)
  102. {
  103. String s (nsStringToJuce (file));
  104. if (s.containsChar (' '))
  105. s = s.quoted ('"');
  106. return s;
  107. }
  108. };
  109. END_JUCE_NAMESPACE
  110. using namespace juce;
  111. #define JuceAppDelegate MakeObjCClassName(JuceAppDelegate)
  112. //==============================================================================
  113. @interface JuceAppDelegate : NSObject
  114. {
  115. @private
  116. id oldDelegate;
  117. @public
  118. AppDelegateRedirector* redirector;
  119. }
  120. - (JuceAppDelegate*) init;
  121. - (void) dealloc;
  122. - (BOOL) application: (NSApplication*) theApplication openFile: (NSString*) filename;
  123. - (void) application: (NSApplication*) sender openFiles: (NSArray*) filenames;
  124. - (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication*) app;
  125. - (void) applicationWillTerminate: (NSNotification*) aNotification;
  126. - (void) applicationDidBecomeActive: (NSNotification*) aNotification;
  127. - (void) applicationDidResignActive: (NSNotification*) aNotification;
  128. - (void) applicationWillUnhide: (NSNotification*) aNotification;
  129. - (void) performCallback: (id) info;
  130. - (void) broadcastMessageCallback: (NSNotification*) info;
  131. - (void) dummyMethod;
  132. @end
  133. @implementation JuceAppDelegate
  134. - (JuceAppDelegate*) init
  135. {
  136. [super init];
  137. redirector = new AppDelegateRedirector();
  138. NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
  139. if (JUCEApplicationBase::isStandaloneApp())
  140. {
  141. oldDelegate = [NSApp delegate];
  142. [NSApp setDelegate: self];
  143. [[NSDistributedNotificationCenter defaultCenter] addObserver: self
  144. selector: @selector (broadcastMessageCallback:)
  145. name: AppDelegateRedirector::getBroacastEventName()
  146. object: nil];
  147. }
  148. else
  149. {
  150. oldDelegate = nil;
  151. [center addObserver: self selector: @selector (applicationDidResignActive:)
  152. name: NSApplicationDidResignActiveNotification object: NSApp];
  153. [center addObserver: self selector: @selector (applicationDidBecomeActive:)
  154. name: NSApplicationDidBecomeActiveNotification object: NSApp];
  155. [center addObserver: self selector: @selector (applicationWillUnhide:)
  156. name: NSApplicationWillUnhideNotification object: NSApp];
  157. }
  158. return self;
  159. }
  160. - (void) dealloc
  161. {
  162. if (oldDelegate != nil)
  163. [NSApp setDelegate: oldDelegate];
  164. [[NSDistributedNotificationCenter defaultCenter] removeObserver: self
  165. name: AppDelegateRedirector::getBroacastEventName()
  166. object: nil];
  167. redirector->deleteSelf();
  168. [super dealloc];
  169. }
  170. - (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication*) app
  171. {
  172. (void) app;
  173. return redirector->shouldTerminate();
  174. }
  175. - (void) applicationWillTerminate: (NSNotification*) aNotification
  176. {
  177. (void) aNotification;
  178. redirector->willTerminate();
  179. }
  180. - (BOOL) application: (NSApplication*) app openFile: (NSString*) filename
  181. {
  182. (void) app;
  183. return redirector->openFile (filename);
  184. }
  185. - (void) application: (NSApplication*) sender openFiles: (NSArray*) filenames
  186. {
  187. (void) sender;
  188. return redirector->openFiles (filenames);
  189. }
  190. - (void) applicationDidBecomeActive: (NSNotification*) notification
  191. {
  192. (void) notification;
  193. redirector->focusChanged();
  194. }
  195. - (void) applicationDidResignActive: (NSNotification*) notification
  196. {
  197. (void) notification;
  198. redirector->focusChanged();
  199. }
  200. - (void) applicationWillUnhide: (NSNotification*) notification
  201. {
  202. (void) notification;
  203. redirector->focusChanged();
  204. }
  205. - (void) performCallback: (id) info
  206. {
  207. if ([info isKindOfClass: [NSData class]])
  208. {
  209. AppDelegateRedirector::CallbackMessagePayload* pl
  210. = (AppDelegateRedirector::CallbackMessagePayload*) [((NSData*) info) bytes];
  211. if (pl != nullptr)
  212. redirector->performCallback (pl);
  213. }
  214. else
  215. {
  216. jassertfalse; // should never get here!
  217. }
  218. }
  219. - (void) broadcastMessageCallback: (NSNotification*) n
  220. {
  221. NSDictionary* dict = (NSDictionary*) [n userInfo];
  222. const String messageString (nsStringToJuce ((NSString*) [dict valueForKey: nsStringLiteral ("message")]));
  223. MessageManager::getInstance()->deliverBroadcastMessage (messageString);
  224. }
  225. - (void) dummyMethod {} // (used as a way of running a dummy thread)
  226. @end
  227. //==============================================================================
  228. BEGIN_JUCE_NAMESPACE
  229. static JuceAppDelegate* juceAppDelegate = nil;
  230. void MessageManager::runDispatchLoop()
  231. {
  232. if (! quitMessagePosted) // check that the quit message wasn't already posted..
  233. {
  234. JUCE_AUTORELEASEPOOL
  235. // must only be called by the message thread!
  236. jassert (isThisTheMessageThread());
  237. #if JUCE_CATCH_UNHANDLED_EXCEPTIONS
  238. @try
  239. {
  240. [NSApp run];
  241. }
  242. @catch (NSException* e)
  243. {
  244. // An AppKit exception will kill the app, but at least this provides a chance to log it.,
  245. std::runtime_error ex (std::string ("NSException: ") + [[e name] UTF8String] + ", Reason:" + [[e reason] UTF8String]);
  246. JUCEApplicationBase::sendUnhandledException (&ex, __FILE__, __LINE__);
  247. }
  248. @finally
  249. {
  250. }
  251. #else
  252. [NSApp run];
  253. #endif
  254. }
  255. }
  256. void MessageManager::stopDispatchLoop()
  257. {
  258. quitMessagePosted = true;
  259. [NSApp stop: nil];
  260. [NSApp activateIgnoringOtherApps: YES]; // (if the app is inactive, it sits there and ignores the quit request until the next time it gets activated)
  261. [NSEvent startPeriodicEventsAfterDelay: 0 withPeriod: 0.1];
  262. }
  263. #if JUCE_MODAL_LOOPS_PERMITTED
  264. bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor)
  265. {
  266. jassert (isThisTheMessageThread()); // must only be called by the message thread
  267. uint32 endTime = Time::getMillisecondCounter() + millisecondsToRunFor;
  268. while (! quitMessagePosted)
  269. {
  270. JUCE_AUTORELEASEPOOL
  271. CFRunLoopRunInMode (kCFRunLoopDefaultMode, 0.001, true);
  272. NSEvent* e = [NSApp nextEventMatchingMask: NSAnyEventMask
  273. untilDate: [NSDate dateWithTimeIntervalSinceNow: 0.001]
  274. inMode: NSDefaultRunLoopMode
  275. dequeue: YES];
  276. if (e != nil && (isEventBlockedByModalComps == nullptr || ! (*isEventBlockedByModalComps) (e)))
  277. [NSApp sendEvent: e];
  278. if (Time::getMillisecondCounter() >= endTime)
  279. break;
  280. }
  281. return ! quitMessagePosted;
  282. }
  283. #endif
  284. //==============================================================================
  285. void initialiseNSApplication()
  286. {
  287. #if JUCE_MAC
  288. JUCE_AUTORELEASEPOOL
  289. [NSApplication sharedApplication];
  290. #endif
  291. }
  292. void MessageManager::doPlatformSpecificInitialisation()
  293. {
  294. if (juceAppDelegate == nil)
  295. juceAppDelegate = [[JuceAppDelegate alloc] init];
  296. // This launches a dummy thread, which forces Cocoa to initialise NSThreads
  297. // correctly (needed prior to 10.5)
  298. if (! [NSThread isMultiThreaded])
  299. [NSThread detachNewThreadSelector: @selector (dummyMethod)
  300. toTarget: juceAppDelegate
  301. withObject: nil];
  302. }
  303. void MessageManager::doPlatformSpecificShutdown()
  304. {
  305. if (juceAppDelegate != nil)
  306. {
  307. [[NSRunLoop currentRunLoop] cancelPerformSelectorsWithTarget: juceAppDelegate];
  308. [[NSNotificationCenter defaultCenter] removeObserver: juceAppDelegate];
  309. [juceAppDelegate release];
  310. juceAppDelegate = nil;
  311. }
  312. }
  313. bool MessageManager::postMessageToSystemQueue (Message* message)
  314. {
  315. juceAppDelegate->redirector->postMessage (message);
  316. return true;
  317. }
  318. void MessageManager::broadcastMessage (const String& message)
  319. {
  320. NSDictionary* info = [NSDictionary dictionaryWithObject: juceStringToNS (message)
  321. forKey: nsStringLiteral ("message")];
  322. [[NSDistributedNotificationCenter defaultCenter] postNotificationName: AppDelegateRedirector::getBroacastEventName()
  323. object: nil
  324. userInfo: info];
  325. }
  326. void* MessageManager::callFunctionOnMessageThread (MessageCallbackFunction* callback, void* data)
  327. {
  328. if (isThisTheMessageThread())
  329. {
  330. return (*callback) (data);
  331. }
  332. else
  333. {
  334. // If a thread has a MessageManagerLock and then tries to call this method, it'll
  335. // deadlock because the message manager is blocked from running, so can never
  336. // call your function..
  337. jassert (! MessageManager::getInstance()->currentThreadHasLockedMessageManager());
  338. JUCE_AUTORELEASEPOOL
  339. AppDelegateRedirector::CallbackMessagePayload cmp;
  340. cmp.function = callback;
  341. cmp.parameter = data;
  342. cmp.result = 0;
  343. cmp.hasBeenExecuted = false;
  344. [juceAppDelegate performSelectorOnMainThread: @selector (performCallback:)
  345. withObject: [NSData dataWithBytesNoCopy: &cmp
  346. length: sizeof (cmp)
  347. freeWhenDone: NO]
  348. waitUntilDone: YES];
  349. return cmp.result;
  350. }
  351. }