mirror of
https://github.com/juce-framework/JUCE.git
synced 2026-01-10 23:44:24 +00:00
435 lines
14 KiB
Text
435 lines
14 KiB
Text
/*
|
|
==============================================================================
|
|
|
|
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;
|
|
}
|
|
}
|