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.

506 lines
15KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-9 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. // (This file gets included by juce_mac_NativeCode.mm, rather than being
  19. // compiled on its own).
  20. #if JUCE_INCLUDED_FILE
  21. struct CallbackMessagePayload
  22. {
  23. MessageCallbackFunction* function;
  24. void* parameter;
  25. void* volatile result;
  26. bool volatile hasBeenExecuted;
  27. };
  28. /* When you use multiple DLLs which share similarly-named obj-c classes - like
  29. for example having more than one juce plugin loaded into a host, then when a
  30. method is called, the actual code that runs might actually be in a different module
  31. than the one you expect... So any calls to library functions or statics that are
  32. made inside obj-c methods will probably end up getting executed in a different DLL's
  33. memory space. Not a great thing to happen - this obviously leads to bizarre crashes.
  34. To work around this insanity, I'm only allowing obj-c methods to make calls to
  35. virtual methods of an object that's known to live inside the right module's space.
  36. */
  37. class AppDelegateRedirector
  38. {
  39. public:
  40. AppDelegateRedirector()
  41. {
  42. #if MAC_OS_X_VERSION_MIN_REQUIRED > MAC_OS_X_VERSION_10_4
  43. runLoop = CFRunLoopGetMain();
  44. #else
  45. runLoop = CFRunLoopGetCurrent();
  46. #endif
  47. CFRunLoopSourceContext sourceContext;
  48. zerostruct (sourceContext);
  49. sourceContext.info = this;
  50. sourceContext.perform = runLoopSourceCallback;
  51. runLoopSource = CFRunLoopSourceCreate (kCFAllocatorDefault, 1, &sourceContext);
  52. CFRunLoopAddSource (runLoop, runLoopSource, kCFRunLoopCommonModes);
  53. }
  54. virtual ~AppDelegateRedirector()
  55. {
  56. CFRunLoopRemoveSource (runLoop, runLoopSource, kCFRunLoopCommonModes);
  57. CFRunLoopSourceInvalidate (runLoopSource);
  58. CFRelease (runLoopSource);
  59. while (messages.size() > 0)
  60. delete ((Message*) messages.remove(0));
  61. }
  62. virtual NSApplicationTerminateReply shouldTerminate()
  63. {
  64. if (JUCEApplication::getInstance() != 0)
  65. {
  66. JUCEApplication::getInstance()->systemRequestedQuit();
  67. return NSTerminateCancel;
  68. }
  69. return NSTerminateNow;
  70. }
  71. virtual BOOL openFile (const NSString* filename)
  72. {
  73. if (JUCEApplication::getInstance() != 0)
  74. {
  75. JUCEApplication::getInstance()->anotherInstanceStarted (nsStringToJuce (filename));
  76. return YES;
  77. }
  78. return NO;
  79. }
  80. virtual void openFiles (NSArray* filenames)
  81. {
  82. StringArray files;
  83. for (unsigned int i = 0; i < [filenames count]; ++i)
  84. {
  85. String filename (nsStringToJuce ((NSString*) [filenames objectAtIndex: i]));
  86. if (filename.containsChar (T(' ')))
  87. filename = filename.quoted('"');
  88. files.add (filename);
  89. }
  90. if (files.size() > 0 && JUCEApplication::getInstance() != 0)
  91. {
  92. JUCEApplication::getInstance()->anotherInstanceStarted (files.joinIntoString (T(" ")));
  93. }
  94. }
  95. virtual void focusChanged()
  96. {
  97. juce_HandleProcessFocusChange();
  98. }
  99. virtual void performCallback (CallbackMessagePayload* pl)
  100. {
  101. pl->result = (*pl->function) (pl->parameter);
  102. pl->hasBeenExecuted = true;
  103. }
  104. virtual void deleteSelf()
  105. {
  106. delete this;
  107. }
  108. void postMessage (void* m)
  109. {
  110. messages.add (m);
  111. CFRunLoopSourceSignal (runLoopSource);
  112. CFRunLoopWakeUp (runLoop);
  113. }
  114. private:
  115. CFRunLoopRef runLoop;
  116. CFRunLoopSourceRef runLoopSource;
  117. Array <void*, CriticalSection> messages;
  118. void runLoopCallback()
  119. {
  120. int numDispatched = 0;
  121. do
  122. {
  123. void* const nextMessage = messages.remove (0);
  124. if (nextMessage == 0)
  125. return;
  126. const ScopedAutoReleasePool pool;
  127. MessageManager::getInstance()->deliverMessage (nextMessage);
  128. } while (++numDispatched <= 4);
  129. CFRunLoopSourceSignal (runLoopSource);
  130. CFRunLoopWakeUp (runLoop);
  131. }
  132. static void runLoopSourceCallback (void* info)
  133. {
  134. ((AppDelegateRedirector*) info)->runLoopCallback();
  135. }
  136. };
  137. END_JUCE_NAMESPACE
  138. using namespace JUCE_NAMESPACE;
  139. #define JuceAppDelegate MakeObjCClassName(JuceAppDelegate)
  140. @interface JuceAppDelegate : NSObject
  141. {
  142. @private
  143. id oldDelegate;
  144. @public
  145. AppDelegateRedirector* redirector;
  146. }
  147. - (JuceAppDelegate*) init;
  148. - (void) dealloc;
  149. - (BOOL) application: (NSApplication*) theApplication openFile: (NSString*) filename;
  150. - (void) application: (NSApplication*) sender openFiles: (NSArray*) filenames;
  151. - (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication*) app;
  152. - (void) applicationDidBecomeActive: (NSNotification*) aNotification;
  153. - (void) applicationDidResignActive: (NSNotification*) aNotification;
  154. - (void) applicationWillUnhide: (NSNotification*) aNotification;
  155. - (void) performCallback: (id) info;
  156. - (void) dummyMethod;
  157. @end
  158. @implementation JuceAppDelegate
  159. - (JuceAppDelegate*) init
  160. {
  161. [super init];
  162. redirector = new AppDelegateRedirector();
  163. NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
  164. if (JUCEApplication::getInstance() != 0)
  165. {
  166. oldDelegate = [NSApp delegate];
  167. [NSApp setDelegate: self];
  168. }
  169. else
  170. {
  171. oldDelegate = 0;
  172. [center addObserver: self selector: @selector (applicationDidResignActive:)
  173. name: NSApplicationDidResignActiveNotification object: NSApp];
  174. [center addObserver: self selector: @selector (applicationDidBecomeActive:)
  175. name: NSApplicationDidBecomeActiveNotification object: NSApp];
  176. [center addObserver: self selector: @selector (applicationWillUnhide:)
  177. name: NSApplicationWillUnhideNotification object: NSApp];
  178. }
  179. return self;
  180. }
  181. - (void) dealloc
  182. {
  183. if (oldDelegate != 0)
  184. [NSApp setDelegate: oldDelegate];
  185. redirector->deleteSelf();
  186. [super dealloc];
  187. }
  188. - (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication*) app
  189. {
  190. return redirector->shouldTerminate();
  191. }
  192. - (BOOL) application: (NSApplication*) app openFile: (NSString*) filename
  193. {
  194. return redirector->openFile (filename);
  195. }
  196. - (void) application: (NSApplication*) sender openFiles: (NSArray*) filenames
  197. {
  198. return redirector->openFiles (filenames);
  199. }
  200. - (void) applicationDidBecomeActive: (NSNotification*) aNotification
  201. {
  202. redirector->focusChanged();
  203. }
  204. - (void) applicationDidResignActive: (NSNotification*) aNotification
  205. {
  206. redirector->focusChanged();
  207. }
  208. - (void) applicationWillUnhide: (NSNotification*) aNotification
  209. {
  210. redirector->focusChanged();
  211. }
  212. - (void) performCallback: (id) info
  213. {
  214. if ([info isKindOfClass: [NSData class]])
  215. {
  216. CallbackMessagePayload* pl = (CallbackMessagePayload*) [((NSData*) info) bytes];
  217. if (pl != 0)
  218. redirector->performCallback (pl);
  219. }
  220. else
  221. {
  222. jassertfalse // should never get here!
  223. }
  224. }
  225. - (void) dummyMethod {} // (used as a way of running a dummy thread)
  226. @end
  227. BEGIN_JUCE_NAMESPACE
  228. static JuceAppDelegate* juceAppDelegate = 0;
  229. void MessageManager::runDispatchLoop()
  230. {
  231. if (! quitMessagePosted) // check that the quit message wasn't already posted..
  232. {
  233. const ScopedAutoReleasePool pool;
  234. // must only be called by the message thread!
  235. jassert (isThisTheMessageThread());
  236. #if JUCE_CATCH_UNHANDLED_EXCEPTIONS
  237. @try
  238. {
  239. [NSApp run];
  240. }
  241. @catch (NSException* e)
  242. {
  243. // An AppKit exception will kill the app, but at least this provides a chance to log it.,
  244. std::runtime_error ex (std::string ("NSException: ") + [[e name] UTF8String] + ", Reason:" + [[e reason] UTF8String]);
  245. JUCEApplication::sendUnhandledException (&ex, __FILE__, __LINE__);
  246. }
  247. @finally
  248. {
  249. }
  250. #else
  251. [NSApp run];
  252. #endif
  253. }
  254. }
  255. void MessageManager::stopDispatchLoop()
  256. {
  257. quitMessagePosted = true;
  258. [NSApp stop: nil];
  259. [NSApp activateIgnoringOtherApps: YES]; // (if the app is inactive, it sits there and ignores the quit request until the next time it gets activated)
  260. [NSEvent startPeriodicEventsAfterDelay: 0 withPeriod: 0.1];
  261. }
  262. static bool isEventBlockedByModalComps (NSEvent* e)
  263. {
  264. if (Component::getNumCurrentlyModalComponents() == 0)
  265. return false;
  266. NSWindow* const w = [e window];
  267. if (w == 0 || [w worksWhenModal])
  268. return false;
  269. bool isKey = false, isInputAttempt = false;
  270. switch ([e type])
  271. {
  272. case NSKeyDown:
  273. case NSKeyUp:
  274. isKey = isInputAttempt = true;
  275. break;
  276. case NSLeftMouseDown:
  277. case NSRightMouseDown:
  278. case NSOtherMouseDown:
  279. isInputAttempt = true;
  280. break;
  281. case NSLeftMouseDragged:
  282. case NSRightMouseDragged:
  283. case NSLeftMouseUp:
  284. case NSRightMouseUp:
  285. case NSOtherMouseUp:
  286. case NSOtherMouseDragged:
  287. if (Component::getComponentUnderMouse() != 0)
  288. return false;
  289. break;
  290. case NSMouseMoved:
  291. case NSMouseEntered:
  292. case NSMouseExited:
  293. case NSCursorUpdate:
  294. case NSScrollWheel:
  295. case NSTabletPoint:
  296. case NSTabletProximity:
  297. break;
  298. default:
  299. return false;
  300. }
  301. for (int i = ComponentPeer::getNumPeers(); --i >= 0;)
  302. {
  303. ComponentPeer* const peer = ComponentPeer::getPeer (i);
  304. NSView* const compView = (NSView*) peer->getNativeHandle();
  305. if ([compView window] == w)
  306. {
  307. if (isKey)
  308. {
  309. if (compView == [w firstResponder])
  310. return false;
  311. }
  312. else
  313. {
  314. if (NSPointInRect ([compView convertPoint: [e locationInWindow] fromView: nil],
  315. [compView bounds]))
  316. return false;
  317. }
  318. }
  319. }
  320. if (isInputAttempt)
  321. {
  322. if (! [NSApp isActive])
  323. [NSApp activateIgnoringOtherApps: YES];
  324. Component* const modal = Component::getCurrentlyModalComponent (0);
  325. if (modal != 0)
  326. modal->inputAttemptWhenModal();
  327. }
  328. return true;
  329. }
  330. bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor)
  331. {
  332. const ScopedAutoReleasePool pool;
  333. jassert (isThisTheMessageThread()); // must only be called by the message thread
  334. uint32 endTime = Time::getMillisecondCounter() + millisecondsToRunFor;
  335. while (! quitMessagePosted)
  336. {
  337. const ScopedAutoReleasePool pool2;
  338. CFRunLoopRunInMode (kCFRunLoopDefaultMode, 0.001, true);
  339. NSEvent* e = [NSApp nextEventMatchingMask: NSAnyEventMask
  340. untilDate: [NSDate dateWithTimeIntervalSinceNow: 0.001]
  341. inMode: NSDefaultRunLoopMode
  342. dequeue: YES];
  343. if (e != 0 && ! isEventBlockedByModalComps (e))
  344. [NSApp sendEvent: e];
  345. if (Time::getMillisecondCounter() >= endTime)
  346. break;
  347. }
  348. return ! quitMessagePosted;
  349. }
  350. //==============================================================================
  351. void MessageManager::doPlatformSpecificInitialisation()
  352. {
  353. if (juceAppDelegate == 0)
  354. juceAppDelegate = [[JuceAppDelegate alloc] init];
  355. // This launches a dummy thread, which forces Cocoa to initialise NSThreads
  356. // correctly (needed prior to 10.5)
  357. if (! [NSThread isMultiThreaded])
  358. [NSThread detachNewThreadSelector: @selector (dummyMethod)
  359. toTarget: juceAppDelegate
  360. withObject: nil];
  361. initialiseMainMenu();
  362. }
  363. void MessageManager::doPlatformSpecificShutdown()
  364. {
  365. if (juceAppDelegate != 0)
  366. {
  367. [[NSRunLoop currentRunLoop] cancelPerformSelectorsWithTarget: juceAppDelegate];
  368. [[NSNotificationCenter defaultCenter] removeObserver: juceAppDelegate];
  369. [juceAppDelegate release];
  370. juceAppDelegate = 0;
  371. }
  372. }
  373. bool juce_postMessageToSystemQueue (void* message)
  374. {
  375. juceAppDelegate->redirector->postMessage (message);
  376. return true;
  377. }
  378. void MessageManager::broadcastMessage (const String& value) throw()
  379. {
  380. }
  381. void* MessageManager::callFunctionOnMessageThread (MessageCallbackFunction* callback,
  382. void* data)
  383. {
  384. if (isThisTheMessageThread())
  385. {
  386. return (*callback) (data);
  387. }
  388. else
  389. {
  390. // If a thread has a MessageManagerLock and then tries to call this method, it'll
  391. // deadlock because the message manager is blocked from running, so can never
  392. // call your function..
  393. jassert (! MessageManager::getInstance()->currentThreadHasLockedMessageManager());
  394. const ScopedAutoReleasePool pool;
  395. CallbackMessagePayload cmp;
  396. cmp.function = callback;
  397. cmp.parameter = data;
  398. cmp.result = 0;
  399. cmp.hasBeenExecuted = false;
  400. [juceAppDelegate performSelectorOnMainThread: @selector (performCallback:)
  401. withObject: [NSData dataWithBytesNoCopy: &cmp
  402. length: sizeof (cmp)
  403. freeWhenDone: NO]
  404. waitUntilDone: YES];
  405. return cmp.result;
  406. }
  407. }
  408. #endif