| @@ -35,151 +35,186 @@ MenuTrackingChangedCallback menuTrackingChangedCallback = nullptr; | |||||
| //============================================================================== | //============================================================================== | ||||
| struct AppDelegateClass : public ObjCClass<NSObject> | 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") | 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() | static void focusChanged() | ||||
| { | { | ||||
| if (appFocusChangeCallback != nullptr) | if (appFocusChangeCallback != nullptr) | ||||
| (*appFocusChangeCallback)(); | (*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) | static String quotedIfContainsSpaces (NSString* file) | ||||
| { | { | ||||
| String s (nsStringToJuce (file)); | String s (nsStringToJuce (file)); | ||||
| @@ -191,71 +226,12 @@ private: | |||||
| return s; | 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) | static NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>* getPushNotificationsDelegate (id self) | ||||
| { | { | ||||
| return getIvar<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>*> (self, "pushNotificationsDelegate"); | 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 | #endif | ||||
| }; | }; | ||||