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.

466 lines
14KB

  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. #ifdef 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. virtual ~AppDelegateRedirector() {}
  42. virtual NSApplicationTerminateReply shouldTerminate()
  43. {
  44. if (JUCEApplication::getInstance() != 0)
  45. {
  46. JUCEApplication::getInstance()->systemRequestedQuit();
  47. return NSTerminateCancel;
  48. }
  49. return NSTerminateNow;
  50. }
  51. virtual BOOL openFile (const NSString* filename)
  52. {
  53. if (JUCEApplication::getInstance() != 0)
  54. {
  55. JUCEApplication::getInstance()->anotherInstanceStarted (nsStringToJuce (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 (nsStringToJuce ((NSString*) [filenames objectAtIndex: i]));
  65. if (files.size() > 0 && JUCEApplication::getInstance() != 0)
  66. {
  67. JUCEApplication::getInstance()->anotherInstanceStarted (files.joinIntoString (T(" ")));
  68. }
  69. }
  70. virtual void focusChanged()
  71. {
  72. juce_HandleProcessFocusChange();
  73. }
  74. virtual void deliverMessage (void* message)
  75. {
  76. // no need for an mm lock here - deliverMessage locks it
  77. MessageManager::getInstance()->deliverMessage (message);
  78. }
  79. virtual void performCallback (CallbackMessagePayload* pl)
  80. {
  81. pl->result = (*pl->function) (pl->parameter);
  82. pl->hasBeenExecuted = true;
  83. }
  84. virtual void deleteSelf()
  85. {
  86. delete this;
  87. }
  88. };
  89. END_JUCE_NAMESPACE
  90. using namespace JUCE_NAMESPACE;
  91. #define JuceAppDelegate MakeObjCClassName(JuceAppDelegate)
  92. static int numPendingMessages = 0;
  93. @interface JuceAppDelegate : NSObject
  94. {
  95. @private
  96. id oldDelegate;
  97. AppDelegateRedirector* redirector;
  98. @public
  99. bool flushingMessages;
  100. }
  101. - (JuceAppDelegate*) init;
  102. - (void) dealloc;
  103. - (BOOL) application: (NSApplication*) theApplication openFile: (NSString*) filename;
  104. - (void) application: (NSApplication*) sender openFiles: (NSArray*) filenames;
  105. - (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication*) app;
  106. - (void) applicationDidBecomeActive: (NSNotification*) aNotification;
  107. - (void) applicationDidResignActive: (NSNotification*) aNotification;
  108. - (void) applicationWillUnhide: (NSNotification*) aNotification;
  109. - (void) customEvent: (id) data;
  110. - (void) performCallback: (id) info;
  111. - (void) dummyMethod;
  112. @end
  113. @implementation JuceAppDelegate
  114. - (JuceAppDelegate*) init
  115. {
  116. [super init];
  117. redirector = new AppDelegateRedirector();
  118. numPendingMessages = 0;
  119. flushingMessages = false;
  120. NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
  121. if (JUCEApplication::getInstance() != 0)
  122. {
  123. oldDelegate = [NSApp delegate];
  124. [NSApp setDelegate: self];
  125. }
  126. else
  127. {
  128. oldDelegate = 0;
  129. [center addObserver: self selector: @selector (applicationDidResignActive:)
  130. name: NSApplicationDidResignActiveNotification object: NSApp];
  131. [center addObserver: self selector: @selector (applicationDidBecomeActive:)
  132. name: NSApplicationDidBecomeActiveNotification object: NSApp];
  133. [center addObserver: self selector: @selector (applicationWillUnhide:)
  134. name: NSApplicationWillUnhideNotification object: NSApp];
  135. }
  136. return self;
  137. }
  138. - (void) dealloc
  139. {
  140. if (oldDelegate != 0)
  141. [NSApp setDelegate: oldDelegate];
  142. redirector->deleteSelf();
  143. [super dealloc];
  144. }
  145. - (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication*) app
  146. {
  147. return redirector->shouldTerminate();
  148. }
  149. - (BOOL) application: (NSApplication*) app openFile: (NSString*) filename
  150. {
  151. return redirector->openFile (filename);
  152. }
  153. - (void) application: (NSApplication*) sender openFiles: (NSArray*) filenames
  154. {
  155. return redirector->openFiles (filenames);
  156. }
  157. - (void) applicationDidBecomeActive: (NSNotification*) aNotification
  158. {
  159. redirector->focusChanged();
  160. }
  161. - (void) applicationDidResignActive: (NSNotification*) aNotification
  162. {
  163. redirector->focusChanged();
  164. }
  165. - (void) applicationWillUnhide: (NSNotification*) aNotification
  166. {
  167. redirector->focusChanged();
  168. }
  169. - (void) customEvent: (id) n
  170. {
  171. atomicDecrement (numPendingMessages);
  172. NSData* data = (NSData*) n;
  173. void* message = 0;
  174. [data getBytes: &message length: sizeof (message)];
  175. [data release];
  176. if (message != 0 && ! flushingMessages)
  177. redirector->deliverMessage (message);
  178. }
  179. - (void) performCallback: (id) info
  180. {
  181. if ([info isKindOfClass: [NSData class]])
  182. {
  183. CallbackMessagePayload* pl = (CallbackMessagePayload*) [((NSData*) info) bytes];
  184. if (pl != 0)
  185. redirector->performCallback (pl);
  186. }
  187. else
  188. {
  189. jassertfalse // should never get here!
  190. }
  191. }
  192. - (void) dummyMethod {} // (used as a way of running a dummy thread)
  193. @end
  194. BEGIN_JUCE_NAMESPACE
  195. static JuceAppDelegate* juceAppDelegate = 0;
  196. void MessageManager::runDispatchLoop()
  197. {
  198. if (! quitMessagePosted) // check that the quit message wasn't already posted..
  199. {
  200. const ScopedAutoReleasePool pool;
  201. // must only be called by the message thread!
  202. jassert (isThisTheMessageThread());
  203. [NSApp run];
  204. }
  205. }
  206. void MessageManager::stopDispatchLoop()
  207. {
  208. quitMessagePosted = true;
  209. [NSApp stop: nil];
  210. [NSApp activateIgnoringOtherApps: YES]; // (if the app is inactive, it sits there and ignores the quit request until the next time it gets activated)
  211. }
  212. static bool isEventBlockedByModalComps (NSEvent* e)
  213. {
  214. if (Component::getNumCurrentlyModalComponents() == 0)
  215. return false;
  216. NSWindow* const w = [e window];
  217. if (w == 0 || [w worksWhenModal])
  218. return false;
  219. bool isKey = false, isInputAttempt = false;
  220. switch ([e type])
  221. {
  222. case NSKeyDown:
  223. case NSKeyUp:
  224. isKey = isInputAttempt = true;
  225. break;
  226. case NSLeftMouseDown:
  227. case NSRightMouseDown:
  228. case NSOtherMouseDown:
  229. isInputAttempt = true;
  230. break;
  231. case NSLeftMouseDragged:
  232. case NSRightMouseDragged:
  233. case NSLeftMouseUp:
  234. case NSRightMouseUp:
  235. case NSOtherMouseUp:
  236. case NSOtherMouseDragged:
  237. if (Component::getComponentUnderMouse() != 0)
  238. return false;
  239. break;
  240. case NSMouseMoved:
  241. case NSMouseEntered:
  242. case NSMouseExited:
  243. case NSCursorUpdate:
  244. case NSScrollWheel:
  245. case NSTabletPoint:
  246. case NSTabletProximity:
  247. break;
  248. default:
  249. return false;
  250. }
  251. for (int i = ComponentPeer::getNumPeers(); --i >= 0;)
  252. {
  253. ComponentPeer* const peer = ComponentPeer::getPeer (i);
  254. NSView* const compView = (NSView*) peer->getNativeHandle();
  255. if ([compView window] == w)
  256. {
  257. if (isKey)
  258. {
  259. if (compView == [w firstResponder])
  260. return false;
  261. }
  262. else
  263. {
  264. if (NSPointInRect ([compView convertPoint: [e locationInWindow] fromView: nil],
  265. [compView bounds]))
  266. return false;
  267. }
  268. }
  269. }
  270. if (isInputAttempt)
  271. {
  272. if (! [NSApp isActive])
  273. [NSApp activateIgnoringOtherApps: YES];
  274. Component* const modal = Component::getCurrentlyModalComponent (0);
  275. if (modal != 0)
  276. modal->inputAttemptWhenModal();
  277. }
  278. return true;
  279. }
  280. bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor)
  281. {
  282. const ScopedAutoReleasePool pool;
  283. jassert (isThisTheMessageThread()); // must only be called by the message thread
  284. uint32 endTime = Time::getMillisecondCounter() + millisecondsToRunFor;
  285. NSDate* endDate = [NSDate dateWithTimeIntervalSinceNow: millisecondsToRunFor * 0.001];
  286. while (! quitMessagePosted)
  287. {
  288. const ScopedAutoReleasePool pool;
  289. [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
  290. beforeDate: endDate];
  291. NSEvent* e = [NSApp nextEventMatchingMask: NSAnyEventMask
  292. untilDate: endDate
  293. inMode: NSDefaultRunLoopMode
  294. dequeue: YES];
  295. if (e != 0 && ! isEventBlockedByModalComps (e))
  296. [NSApp sendEvent: e];
  297. if (Time::getMillisecondCounter() >= endTime)
  298. break;
  299. }
  300. return ! quitMessagePosted;
  301. }
  302. //==============================================================================
  303. void MessageManager::doPlatformSpecificInitialisation()
  304. {
  305. if (juceAppDelegate == 0)
  306. juceAppDelegate = [[JuceAppDelegate alloc] init];
  307. // This launches a dummy thread, which forces Cocoa to initialise NSThreads
  308. // correctly (needed prior to 10.5)
  309. if (! [NSThread isMultiThreaded])
  310. [NSThread detachNewThreadSelector: @selector (dummyMethod)
  311. toTarget: juceAppDelegate
  312. withObject: nil];
  313. initialiseMainMenu();
  314. }
  315. void MessageManager::doPlatformSpecificShutdown()
  316. {
  317. if (juceAppDelegate != 0)
  318. {
  319. [[NSRunLoop currentRunLoop] cancelPerformSelectorsWithTarget: juceAppDelegate];
  320. [[NSNotificationCenter defaultCenter] removeObserver: juceAppDelegate];
  321. // Annoyingly, cancelPerformSelectorsWithTarget can't actually cancel the messages
  322. // sent by performSelectorOnMainThread, so need to manually flush these before quitting..
  323. juceAppDelegate->flushingMessages = true;
  324. for (int i = 100; --i >= 0 && numPendingMessages > 0;)
  325. {
  326. const ScopedAutoReleasePool pool;
  327. [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
  328. beforeDate: [NSDate dateWithTimeIntervalSinceNow: 5 * 0.001]];
  329. }
  330. [juceAppDelegate release];
  331. juceAppDelegate = 0;
  332. }
  333. }
  334. bool juce_postMessageToSystemQueue (void* message)
  335. {
  336. atomicIncrement (numPendingMessages);
  337. [juceAppDelegate performSelectorOnMainThread: @selector (customEvent:)
  338. withObject: (id) [[NSData alloc] initWithBytes: &message length: (int) sizeof (message)]
  339. waitUntilDone: NO];
  340. return true;
  341. }
  342. void MessageManager::broadcastMessage (const String& value) throw()
  343. {
  344. }
  345. void* MessageManager::callFunctionOnMessageThread (MessageCallbackFunction* callback,
  346. void* data)
  347. {
  348. if (isThisTheMessageThread())
  349. {
  350. return (*callback) (data);
  351. }
  352. else
  353. {
  354. // If a thread has a MessageManagerLock and then tries to call this method, it'll
  355. // deadlock because the message manager is blocked from running, so can never
  356. // call your function..
  357. jassert (! MessageManager::getInstance()->currentThreadHasLockedMessageManager());
  358. const ScopedAutoReleasePool pool;
  359. CallbackMessagePayload cmp;
  360. cmp.function = callback;
  361. cmp.parameter = data;
  362. cmp.result = 0;
  363. cmp.hasBeenExecuted = false;
  364. [juceAppDelegate performSelectorOnMainThread: @selector (performCallback:)
  365. withObject: [NSData dataWithBytesNoCopy: &cmp
  366. length: sizeof (cmp)
  367. freeWhenDone: NO]
  368. waitUntilDone: YES];
  369. return cmp.result;
  370. }
  371. }
  372. #endif