| @@ -35,151 +35,186 @@ MenuTrackingChangedCallback menuTrackingChangedCallback = nullptr; | |||
| //============================================================================== | |||
| struct AppDelegateClass : public ObjCClass<NSObject> | |||
| { | |||
| AppDelegateClass() : ObjCClass<NSObject> ("JUCEAppDelegate_") | |||
| AppDelegateClass() : ObjCClass ("JUCEAppDelegate_") | |||
| { | |||
| addMethod (@selector (applicationWillFinishLaunching:), applicationWillFinishLaunching); | |||
| addMethod (@selector (applicationShouldTerminate:), applicationShouldTerminate); | |||
| addMethod (@selector (applicationWillTerminate:), applicationWillTerminate); | |||
| addMethod (@selector (application:openFile:), application_openFile); | |||
| addMethod (@selector (application:openFiles:), application_openFiles); | |||
| addMethod (@selector (applicationDidBecomeActive:), applicationDidBecomeActive); | |||
| addMethod (@selector (applicationDidResignActive:), applicationDidResignActive); | |||
| addMethod (@selector (applicationWillUnhide:), applicationWillUnhide); | |||
| addMethod (@selector (applicationWillFinishLaunching:), [] (id self, SEL, NSNotification*) | |||
| { | |||
| JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") | |||
| [[NSAppleEventManager sharedAppleEventManager] setEventHandler: self | |||
| andSelector: @selector (getUrl:withReplyEvent:) | |||
| forEventClass: kInternetEventClass | |||
| andEventID: kAEGetURL]; | |||
| JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
| }); | |||
| JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") | |||
| addMethod (@selector (getUrl:withReplyEvent:), getUrl_withReplyEvent); | |||
| addMethod (@selector (broadcastMessageCallback:), broadcastMessageCallback); | |||
| addMethod (@selector (mainMenuTrackingBegan:), mainMenuTrackingBegan); | |||
| addMethod (@selector (mainMenuTrackingEnded:), mainMenuTrackingEnded); | |||
| addMethod (@selector (dummyMethod), dummyMethod); | |||
| JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
| addMethod (@selector (applicationShouldTerminate:), [] (id /*self*/, SEL, NSApplication*) | |||
| { | |||
| if (auto* app = JUCEApplicationBase::getInstance()) | |||
| { | |||
| app->systemRequestedQuit(); | |||
| #if JUCE_PUSH_NOTIFICATIONS | |||
| //============================================================================== | |||
| addIvar<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>*> ("pushNotificationsDelegate"); | |||
| if (! MessageManager::getInstance()->hasStopMessageBeenSent()) | |||
| return NSTerminateCancel; | |||
| } | |||
| addMethod (@selector (applicationDidFinishLaunching:), applicationDidFinishLaunching); | |||
| return NSTerminateNow; | |||
| }); | |||
| JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") | |||
| addMethod (@selector (setPushNotificationsDelegate:), setPushNotificationsDelegate); | |||
| JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
| addMethod (@selector (applicationWillTerminate:), [] (id /*self*/, SEL, NSNotification*) | |||
| { | |||
| JUCEApplicationBase::appWillTerminateByForce(); | |||
| }); | |||
| addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), registeredForRemoteNotifications); | |||
| addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), failedToRegisterForRemoteNotifications); | |||
| addMethod (@selector (application:didReceiveRemoteNotification:), didReceiveRemoteNotification); | |||
| #endif | |||
| addMethod (@selector (application:openFile:), [] (id /*self*/, SEL, NSApplication*, NSString* filename) | |||
| { | |||
| if (auto* app = JUCEApplicationBase::getInstance()) | |||
| { | |||
| app->anotherInstanceStarted (quotedIfContainsSpaces (filename)); | |||
| return YES; | |||
| } | |||
| registerClass(); | |||
| } | |||
| return NO; | |||
| }); | |||
| addMethod (@selector (application:openFiles:), [] (id /*self*/, SEL, NSApplication*, NSArray* filenames) | |||
| { | |||
| if (auto* app = JUCEApplicationBase::getInstance()) | |||
| { | |||
| StringArray files; | |||
| for (NSString* f in filenames) | |||
| files.add (quotedIfContainsSpaces (f)); | |||
| if (files.size() > 0) | |||
| app->anotherInstanceStarted (files.joinIntoString (" ")); | |||
| } | |||
| }); | |||
| addMethod (@selector (applicationDidBecomeActive:), [] (id /*self*/, SEL, NSNotification*) { focusChanged(); }); | |||
| addMethod (@selector (applicationDidResignActive:), [] (id /*self*/, SEL, NSNotification*) { focusChanged(); }); | |||
| addMethod (@selector (applicationWillUnhide:), [] (id /*self*/, SEL, NSNotification*) { focusChanged(); }); | |||
| private: | |||
| static void applicationWillFinishLaunching (id self, SEL, NSNotification*) | |||
| { | |||
| JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") | |||
| [[NSAppleEventManager sharedAppleEventManager] setEventHandler: self | |||
| andSelector: @selector (getUrl:withReplyEvent:) | |||
| forEventClass: kInternetEventClass | |||
| andEventID: kAEGetURL]; | |||
| JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
| } | |||
| addMethod (@selector (getUrl:withReplyEvent:), [] (id /*self*/, SEL, NSAppleEventDescriptor* event, NSAppleEventDescriptor*) | |||
| { | |||
| if (auto* app = JUCEApplicationBase::getInstance()) | |||
| app->anotherInstanceStarted (quotedIfContainsSpaces ([[event paramDescriptorForKeyword: keyDirectObject] stringValue])); | |||
| }); | |||
| #if JUCE_PUSH_NOTIFICATIONS | |||
| static void applicationDidFinishLaunching (id self, SEL, NSNotification* notification) | |||
| { | |||
| if (notification.userInfo != nil) | |||
| addMethod (@selector (broadcastMessageCallback:), [] (id /*self*/, SEL, NSNotification* n) | |||
| { | |||
| JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") | |||
| // NSUserNotification is deprecated from macOS 11, but there doesn't seem to be a | |||
| // replacement for NSApplicationLaunchUserNotificationKey returning a non-deprecated type | |||
| NSUserNotification* userNotification = notification.userInfo[NSApplicationLaunchUserNotificationKey]; | |||
| JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
| NSDictionary* dict = (NSDictionary*) [n userInfo]; | |||
| auto messageString = nsStringToJuce ((NSString*) [dict valueForKey: nsStringLiteral ("message")]); | |||
| MessageManager::getInstance()->deliverBroadcastMessage (messageString); | |||
| }); | |||
| if (userNotification != nil && userNotification.userInfo != nil) | |||
| didReceiveRemoteNotification (self, nil, [NSApplication sharedApplication], userNotification.userInfo); | |||
| } | |||
| } | |||
| #endif | |||
| addMethod (@selector (mainMenuTrackingBegan:), [] (id /*self*/, SEL, NSNotification*) | |||
| { | |||
| if (menuTrackingChangedCallback != nullptr) | |||
| menuTrackingChangedCallback (true); | |||
| }); | |||
| static NSApplicationTerminateReply applicationShouldTerminate (id /*self*/, SEL, NSApplication*) | |||
| { | |||
| if (auto* app = JUCEApplicationBase::getInstance()) | |||
| addMethod (@selector (mainMenuTrackingEnded:), [] (id /*self*/, SEL, NSNotification*) | |||
| { | |||
| app->systemRequestedQuit(); | |||
| if (menuTrackingChangedCallback != nullptr) | |||
| menuTrackingChangedCallback (false); | |||
| }); | |||
| if (! MessageManager::getInstance()->hasStopMessageBeenSent()) | |||
| return NSTerminateCancel; | |||
| } | |||
| // (used as a way of running a dummy thread) | |||
| addMethod (@selector (dummyMethod), [] (id /*self*/, SEL) {}); | |||
| JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
| return NSTerminateNow; | |||
| } | |||
| #if JUCE_PUSH_NOTIFICATIONS | |||
| //============================================================================== | |||
| addIvar<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>*> ("pushNotificationsDelegate"); | |||
| static void applicationWillTerminate (id /*self*/, SEL, NSNotification*) | |||
| { | |||
| JUCEApplicationBase::appWillTerminateByForce(); | |||
| } | |||
| addMethod (@selector (applicationDidFinishLaunching:), [] (id self, SEL, NSNotification* notification) | |||
| { | |||
| if (notification.userInfo != nil) | |||
| { | |||
| JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") | |||
| // NSUserNotification is deprecated from macOS 11, but there doesn't seem to be a | |||
| // replacement for NSApplicationLaunchUserNotificationKey returning a non-deprecated type | |||
| NSUserNotification* userNotification = notification.userInfo[NSApplicationLaunchUserNotificationKey]; | |||
| JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
| if (userNotification != nil && userNotification.userInfo != nil) | |||
| didReceiveRemoteNotification (self, nil, [NSApplication sharedApplication], userNotification.userInfo); | |||
| } | |||
| }); | |||
| static BOOL application_openFile (id /*self*/, SEL, NSApplication*, NSString* filename) | |||
| { | |||
| if (auto* app = JUCEApplicationBase::getInstance()) | |||
| JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") | |||
| addMethod (@selector (setPushNotificationsDelegate:), [] (id self, SEL, NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>* delegate) | |||
| { | |||
| app->anotherInstanceStarted (quotedIfContainsSpaces (filename)); | |||
| return YES; | |||
| } | |||
| object_setInstanceVariable (self, "pushNotificationsDelegate", delegate); | |||
| }); | |||
| JUCE_END_IGNORE_WARNINGS_GCC_LIKE | |||
| return NO; | |||
| } | |||
| addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), [] (id self, SEL, NSApplication* application, NSData* deviceToken) | |||
| { | |||
| auto* delegate = getPushNotificationsDelegate (self); | |||
| static void application_openFiles (id /*self*/, SEL, NSApplication*, NSArray* filenames) | |||
| { | |||
| if (auto* app = JUCEApplicationBase::getInstance()) | |||
| SEL selector = @selector (application:didRegisterForRemoteNotificationsWithDeviceToken:); | |||
| if (delegate != nil && [delegate respondsToSelector: selector]) | |||
| { | |||
| NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; | |||
| [invocation setSelector: selector]; | |||
| [invocation setTarget: delegate]; | |||
| [invocation setArgument: &application atIndex:2]; | |||
| [invocation setArgument: &deviceToken atIndex:3]; | |||
| [invocation invoke]; | |||
| } | |||
| }); | |||
| addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), [] (id self, SEL, NSApplication* application, NSError* error) | |||
| { | |||
| StringArray files; | |||
| auto* delegate = getPushNotificationsDelegate (self); | |||
| for (NSString* f in filenames) | |||
| files.add (quotedIfContainsSpaces (f)); | |||
| SEL selector = @selector (application:didFailToRegisterForRemoteNotificationsWithError:); | |||
| if (files.size() > 0) | |||
| app->anotherInstanceStarted (files.joinIntoString (" ")); | |||
| } | |||
| } | |||
| if (delegate != nil && [delegate respondsToSelector: selector]) | |||
| { | |||
| NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; | |||
| [invocation setSelector: selector]; | |||
| [invocation setTarget: delegate]; | |||
| [invocation setArgument: &application atIndex:2]; | |||
| [invocation setArgument: &error atIndex:3]; | |||
| static void applicationDidBecomeActive (id /*self*/, SEL, NSNotification*) { focusChanged(); } | |||
| static void applicationDidResignActive (id /*self*/, SEL, NSNotification*) { focusChanged(); } | |||
| static void applicationWillUnhide (id /*self*/, SEL, NSNotification*) { focusChanged(); } | |||
| [invocation invoke]; | |||
| } | |||
| }); | |||
| static void broadcastMessageCallback (id /*self*/, SEL, NSNotification* n) | |||
| { | |||
| NSDictionary* dict = (NSDictionary*) [n userInfo]; | |||
| auto messageString = nsStringToJuce ((NSString*) [dict valueForKey: nsStringLiteral ("message")]); | |||
| MessageManager::getInstance()->deliverBroadcastMessage (messageString); | |||
| } | |||
| addMethod (@selector (application:didReceiveRemoteNotification:), [] (id self, SEL, NSApplication* application, NSDictionary* userInfo) | |||
| { | |||
| auto* delegate = getPushNotificationsDelegate (self); | |||
| static void mainMenuTrackingBegan (id /*self*/, SEL, NSNotification*) | |||
| { | |||
| if (menuTrackingChangedCallback != nullptr) | |||
| menuTrackingChangedCallback (true); | |||
| } | |||
| SEL selector = @selector (application:didReceiveRemoteNotification:); | |||
| static void mainMenuTrackingEnded (id /*self*/, SEL, NSNotification*) | |||
| { | |||
| if (menuTrackingChangedCallback != nullptr) | |||
| menuTrackingChangedCallback (false); | |||
| } | |||
| if (delegate != nil && [delegate respondsToSelector: selector]) | |||
| { | |||
| NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; | |||
| [invocation setSelector: selector]; | |||
| [invocation setTarget: delegate]; | |||
| [invocation setArgument: &application atIndex:2]; | |||
| [invocation setArgument: &userInfo atIndex:3]; | |||
| [invocation invoke]; | |||
| } | |||
| }); | |||
| #endif | |||
| static void dummyMethod (id /*self*/, SEL) {} // (used as a way of running a dummy thread) | |||
| registerClass(); | |||
| } | |||
| private: | |||
| static void focusChanged() | |||
| { | |||
| if (appFocusChangeCallback != nullptr) | |||
| (*appFocusChangeCallback)(); | |||
| } | |||
| static void getUrl_withReplyEvent (id /*self*/, SEL, NSAppleEventDescriptor* event, NSAppleEventDescriptor*) | |||
| { | |||
| if (auto* app = JUCEApplicationBase::getInstance()) | |||
| app->anotherInstanceStarted (quotedIfContainsSpaces ([[event paramDescriptorForKeyword: keyDirectObject] stringValue])); | |||
| } | |||
| static String quotedIfContainsSpaces (NSString* file) | |||
| { | |||
| String s (nsStringToJuce (file)); | |||
| @@ -191,71 +226,12 @@ private: | |||
| return s; | |||
| } | |||
| #if JUCE_PUSH_NOTIFICATIONS | |||
| //============================================================================== | |||
| static void setPushNotificationsDelegate (id self, SEL, NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>* delegate) | |||
| { | |||
| object_setInstanceVariable (self, "pushNotificationsDelegate", delegate); | |||
| } | |||
| #if JUCE_PUSH_NOTIFICATIONS | |||
| static NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>* getPushNotificationsDelegate (id self) | |||
| { | |||
| return getIvar<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>*> (self, "pushNotificationsDelegate"); | |||
| } | |||
| static void registeredForRemoteNotifications (id self, SEL, NSApplication* application, NSData* deviceToken) | |||
| { | |||
| auto* delegate = getPushNotificationsDelegate (self); | |||
| SEL selector = @selector (application:didRegisterForRemoteNotificationsWithDeviceToken:); | |||
| if (delegate != nil && [delegate respondsToSelector: selector]) | |||
| { | |||
| NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; | |||
| [invocation setSelector: selector]; | |||
| [invocation setTarget: delegate]; | |||
| [invocation setArgument: &application atIndex:2]; | |||
| [invocation setArgument: &deviceToken atIndex:3]; | |||
| [invocation invoke]; | |||
| } | |||
| } | |||
| static void failedToRegisterForRemoteNotifications (id self, SEL, NSApplication* application, NSError* error) | |||
| { | |||
| auto* delegate = getPushNotificationsDelegate (self); | |||
| SEL selector = @selector (application:didFailToRegisterForRemoteNotificationsWithError:); | |||
| if (delegate != nil && [delegate respondsToSelector: selector]) | |||
| { | |||
| NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; | |||
| [invocation setSelector: selector]; | |||
| [invocation setTarget: delegate]; | |||
| [invocation setArgument: &application atIndex:2]; | |||
| [invocation setArgument: &error atIndex:3]; | |||
| [invocation invoke]; | |||
| } | |||
| } | |||
| static void didReceiveRemoteNotification (id self, SEL, NSApplication* application, NSDictionary* userInfo) | |||
| { | |||
| auto* delegate = getPushNotificationsDelegate (self); | |||
| SEL selector = @selector (application:didReceiveRemoteNotification:); | |||
| if (delegate != nil && [delegate respondsToSelector: selector]) | |||
| { | |||
| NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [delegate methodSignatureForSelector: selector]]; | |||
| [invocation setSelector: selector]; | |||
| [invocation setTarget: delegate]; | |||
| [invocation setArgument: &application atIndex:2]; | |||
| [invocation setArgument: &userInfo atIndex:3]; | |||
| [invocation invoke]; | |||
| } | |||
| } | |||
| #endif | |||
| }; | |||