|
- /*
- ==============================================================================
-
- This file is part of the JUCE library - "Jules' Utility Class Extensions"
- Copyright 2004-11 by Raw Material Software Ltd.
-
- ------------------------------------------------------------------------------
-
- JUCE can be redistributed and/or modified under the terms of the GNU General
- Public License (Version 2), as published by the Free Software Foundation.
- A copy of the license is included in the JUCE distribution, or can be found
- online at www.gnu.org/licenses.
-
- JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE. See the GNU General Public License for more details.
-
- ------------------------------------------------------------------------------
-
- To release a closed-source product which uses JUCE, commercial licenses are
- available: visit www.rawmaterialsoftware.com/juce for more information.
-
- ==============================================================================
- */
-
- typedef void (*AppFocusChangeCallback)();
- AppFocusChangeCallback appFocusChangeCallback = nullptr;
-
- typedef bool (*CheckEventBlockedByModalComps) (NSEvent*);
- CheckEventBlockedByModalComps isEventBlockedByModalComps = nullptr;
-
- //==============================================================================
- /* When you use multiple DLLs which share similarly-named obj-c classes - like
- for example having more than one juce plugin loaded into a host, then when a
- method is called, the actual code that runs might actually be in a different module
- than the one you expect... So any calls to library functions or statics that are
- made inside obj-c methods will probably end up getting executed in a different DLL's
- memory space. Not a great thing to happen - this obviously leads to bizarre crashes.
-
- To work around this insanity, I'm only allowing obj-c methods to make calls to
- virtual methods of an object that's known to live inside the right module's space.
- */
- class AppDelegateRedirector
- {
- public:
- AppDelegateRedirector() {}
- virtual ~AppDelegateRedirector() {}
-
- virtual NSApplicationTerminateReply shouldTerminate()
- {
- if (JUCEApplicationBase::getInstance() != nullptr)
- {
- JUCEApplicationBase::getInstance()->systemRequestedQuit();
-
- if (! MessageManager::getInstance()->hasStopMessageBeenSent())
- return NSTerminateCancel;
- }
-
- return NSTerminateNow;
- }
-
- virtual void willTerminate()
- {
- JUCEApplicationBase::appWillTerminateByForce();
- }
-
- virtual BOOL openFile (NSString* filename)
- {
- if (JUCEApplicationBase::getInstance() != nullptr)
- {
- JUCEApplicationBase::getInstance()->anotherInstanceStarted (quotedIfContainsSpaces (filename));
- return YES;
- }
-
- return NO;
- }
-
- virtual void openFiles (NSArray* filenames)
- {
- StringArray files;
- for (unsigned int i = 0; i < [filenames count]; ++i)
- files.add (quotedIfContainsSpaces ((NSString*) [filenames objectAtIndex: i]));
-
- if (files.size() > 0 && JUCEApplicationBase::getInstance() != nullptr)
- JUCEApplicationBase::getInstance()->anotherInstanceStarted (files.joinIntoString (" "));
- }
-
- virtual void focusChanged()
- {
- if (appFocusChangeCallback != nullptr)
- (*appFocusChangeCallback)();
- }
-
- struct CallbackMessagePayload
- {
- MessageCallbackFunction* function;
- void* parameter;
- void* volatile result;
- bool volatile hasBeenExecuted;
- };
-
- virtual void performCallback (CallbackMessagePayload* pl)
- {
- pl->result = (*pl->function) (pl->parameter);
- pl->hasBeenExecuted = true;
- }
-
- virtual void deleteSelf()
- {
- delete this;
- }
-
- void postMessage (Message* const m)
- {
- messageQueue.post (m);
- }
-
- static NSString* getBroacastEventName()
- {
- return juceStringToNS ("juce_" + String::toHexString (File::getSpecialLocation (File::currentExecutableFile).hashCode64()));
- }
-
- private:
- CFRunLoopRef runLoop;
- CFRunLoopSourceRef runLoopSource;
- MessageQueue messageQueue;
-
- static const String quotedIfContainsSpaces (NSString* file)
- {
- String s (nsStringToJuce (file));
- if (s.containsChar (' '))
- s = s.quoted ('"');
-
- return s;
- }
- };
-
-
- END_JUCE_NAMESPACE
- using namespace juce;
-
- #define JuceAppDelegate MakeObjCClassName(JuceAppDelegate)
-
- //==============================================================================
- @interface JuceAppDelegate : NSObject
- {
- @private
- id oldDelegate;
-
- @public
- AppDelegateRedirector* redirector;
- }
-
- - (JuceAppDelegate*) init;
- - (void) dealloc;
- - (BOOL) application: (NSApplication*) theApplication openFile: (NSString*) filename;
- - (void) application: (NSApplication*) sender openFiles: (NSArray*) filenames;
- - (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication*) app;
- - (void) applicationWillTerminate: (NSNotification*) aNotification;
- - (void) applicationDidBecomeActive: (NSNotification*) aNotification;
- - (void) applicationDidResignActive: (NSNotification*) aNotification;
- - (void) applicationWillUnhide: (NSNotification*) aNotification;
- - (void) performCallback: (id) info;
- - (void) broadcastMessageCallback: (NSNotification*) info;
- - (void) dummyMethod;
- @end
-
- @implementation JuceAppDelegate
-
- - (JuceAppDelegate*) init
- {
- [super init];
-
- redirector = new AppDelegateRedirector();
-
- NSNotificationCenter* center = [NSNotificationCenter defaultCenter];
-
- if (JUCEApplicationBase::isStandaloneApp())
- {
- oldDelegate = [NSApp delegate];
- [NSApp setDelegate: self];
-
- [[NSDistributedNotificationCenter defaultCenter] addObserver: self
- selector: @selector (broadcastMessageCallback:)
- name: AppDelegateRedirector::getBroacastEventName()
- object: nil];
- }
- else
- {
- oldDelegate = nil;
- [center addObserver: self selector: @selector (applicationDidResignActive:)
- name: NSApplicationDidResignActiveNotification object: NSApp];
-
- [center addObserver: self selector: @selector (applicationDidBecomeActive:)
- name: NSApplicationDidBecomeActiveNotification object: NSApp];
-
- [center addObserver: self selector: @selector (applicationWillUnhide:)
- name: NSApplicationWillUnhideNotification object: NSApp];
- }
-
- return self;
- }
-
- - (void) dealloc
- {
- if (oldDelegate != nil)
- [NSApp setDelegate: oldDelegate];
-
- [[NSDistributedNotificationCenter defaultCenter] removeObserver: self
- name: AppDelegateRedirector::getBroacastEventName()
- object: nil];
-
- redirector->deleteSelf();
- [super dealloc];
- }
-
- - (NSApplicationTerminateReply) applicationShouldTerminate: (NSApplication*) app
- {
- (void) app;
- return redirector->shouldTerminate();
- }
-
- - (void) applicationWillTerminate: (NSNotification*) aNotification
- {
- (void) aNotification;
- redirector->willTerminate();
- }
-
- - (BOOL) application: (NSApplication*) app openFile: (NSString*) filename
- {
- (void) app;
- return redirector->openFile (filename);
- }
-
- - (void) application: (NSApplication*) sender openFiles: (NSArray*) filenames
- {
- (void) sender;
- return redirector->openFiles (filenames);
- }
-
- - (void) applicationDidBecomeActive: (NSNotification*) notification
- {
- (void) notification;
- redirector->focusChanged();
- }
-
- - (void) applicationDidResignActive: (NSNotification*) notification
- {
- (void) notification;
- redirector->focusChanged();
- }
-
- - (void) applicationWillUnhide: (NSNotification*) notification
- {
- (void) notification;
- redirector->focusChanged();
- }
-
- - (void) performCallback: (id) info
- {
- if ([info isKindOfClass: [NSData class]])
- {
- AppDelegateRedirector::CallbackMessagePayload* pl
- = (AppDelegateRedirector::CallbackMessagePayload*) [((NSData*) info) bytes];
-
- if (pl != nullptr)
- redirector->performCallback (pl);
- }
- else
- {
- jassertfalse; // should never get here!
- }
- }
-
- - (void) broadcastMessageCallback: (NSNotification*) n
- {
- NSDictionary* dict = (NSDictionary*) [n userInfo];
- const String messageString (nsStringToJuce ((NSString*) [dict valueForKey: nsStringLiteral ("message")]));
- MessageManager::getInstance()->deliverBroadcastMessage (messageString);
- }
-
- - (void) dummyMethod {} // (used as a way of running a dummy thread)
-
- @end
-
- //==============================================================================
- BEGIN_JUCE_NAMESPACE
-
- static JuceAppDelegate* juceAppDelegate = nil;
-
- void MessageManager::runDispatchLoop()
- {
- if (! quitMessagePosted) // check that the quit message wasn't already posted..
- {
- JUCE_AUTORELEASEPOOL
-
- // must only be called by the message thread!
- jassert (isThisTheMessageThread());
-
- #if JUCE_CATCH_UNHANDLED_EXCEPTIONS
- @try
- {
- [NSApp run];
- }
- @catch (NSException* e)
- {
- // An AppKit exception will kill the app, but at least this provides a chance to log it.,
- std::runtime_error ex (std::string ("NSException: ") + [[e name] UTF8String] + ", Reason:" + [[e reason] UTF8String]);
- JUCEApplicationBase::sendUnhandledException (&ex, __FILE__, __LINE__);
- }
- @finally
- {
- }
- #else
- [NSApp run];
- #endif
- }
- }
-
- void MessageManager::stopDispatchLoop()
- {
- quitMessagePosted = true;
- [NSApp stop: nil];
- [NSApp activateIgnoringOtherApps: YES]; // (if the app is inactive, it sits there and ignores the quit request until the next time it gets activated)
- [NSEvent startPeriodicEventsAfterDelay: 0 withPeriod: 0.1];
- }
-
- #if JUCE_MODAL_LOOPS_PERMITTED
- bool MessageManager::runDispatchLoopUntil (int millisecondsToRunFor)
- {
- jassert (isThisTheMessageThread()); // must only be called by the message thread
-
- uint32 endTime = Time::getMillisecondCounter() + millisecondsToRunFor;
-
- while (! quitMessagePosted)
- {
- JUCE_AUTORELEASEPOOL
-
- CFRunLoopRunInMode (kCFRunLoopDefaultMode, 0.001, true);
-
- NSEvent* e = [NSApp nextEventMatchingMask: NSAnyEventMask
- untilDate: [NSDate dateWithTimeIntervalSinceNow: 0.001]
- inMode: NSDefaultRunLoopMode
- dequeue: YES];
-
- if (e != nil && (isEventBlockedByModalComps == nullptr || ! (*isEventBlockedByModalComps) (e)))
- [NSApp sendEvent: e];
-
- if (Time::getMillisecondCounter() >= endTime)
- break;
- }
-
- return ! quitMessagePosted;
- }
- #endif
-
- //==============================================================================
- void initialiseNSApplication()
- {
- #if JUCE_MAC
- JUCE_AUTORELEASEPOOL
- [NSApplication sharedApplication];
- #endif
- }
-
- void MessageManager::doPlatformSpecificInitialisation()
- {
- if (juceAppDelegate == nil)
- juceAppDelegate = [[JuceAppDelegate alloc] init];
-
- // This launches a dummy thread, which forces Cocoa to initialise NSThreads
- // correctly (needed prior to 10.5)
- if (! [NSThread isMultiThreaded])
- [NSThread detachNewThreadSelector: @selector (dummyMethod)
- toTarget: juceAppDelegate
- withObject: nil];
- }
-
- void MessageManager::doPlatformSpecificShutdown()
- {
- if (juceAppDelegate != nil)
- {
- [[NSRunLoop currentRunLoop] cancelPerformSelectorsWithTarget: juceAppDelegate];
- [[NSNotificationCenter defaultCenter] removeObserver: juceAppDelegate];
- [juceAppDelegate release];
- juceAppDelegate = nil;
- }
- }
-
- bool MessageManager::postMessageToSystemQueue (Message* message)
- {
- juceAppDelegate->redirector->postMessage (message);
- return true;
- }
-
- void MessageManager::broadcastMessage (const String& message)
- {
- NSDictionary* info = [NSDictionary dictionaryWithObject: juceStringToNS (message)
- forKey: nsStringLiteral ("message")];
-
- [[NSDistributedNotificationCenter defaultCenter] postNotificationName: AppDelegateRedirector::getBroacastEventName()
- object: nil
- userInfo: info];
- }
-
- void* MessageManager::callFunctionOnMessageThread (MessageCallbackFunction* callback, void* data)
- {
- if (isThisTheMessageThread())
- {
- return (*callback) (data);
- }
- else
- {
- // If a thread has a MessageManagerLock and then tries to call this method, it'll
- // deadlock because the message manager is blocked from running, so can never
- // call your function..
- jassert (! MessageManager::getInstance()->currentThreadHasLockedMessageManager());
-
- JUCE_AUTORELEASEPOOL
-
- AppDelegateRedirector::CallbackMessagePayload cmp;
- cmp.function = callback;
- cmp.parameter = data;
- cmp.result = 0;
- cmp.hasBeenExecuted = false;
-
- [juceAppDelegate performSelectorOnMainThread: @selector (performCallback:)
- withObject: [NSData dataWithBytesNoCopy: &cmp
- length: sizeof (cmp)
- freeWhenDone: NO]
- waitUntilDone: YES];
-
- return cmp.result;
- }
- }
|