From 6bcf603f2c7a4d604a5dc13f9ef5cc243c009ae3 Mon Sep 17 00:00:00 2001 From: reuk Date: Fri, 5 Mar 2021 11:26:22 +0000 Subject: [PATCH] AppDelegate: Ensure correct lifetime of static objects Arranges declarations of objects with static storage duration to ensure correct lifetimes. --- .../native/juce_mac_MessageManager.mm | 462 +++++++++--------- 1 file changed, 232 insertions(+), 230 deletions(-) diff --git a/modules/juce_events/native/juce_mac_MessageManager.mm b/modules/juce_events/native/juce_mac_MessageManager.mm index 1cc0b9f8e1..7a07188689 100644 --- a/modules/juce_events/native/juce_mac_MessageManager.mm +++ b/modules/juce_events/native/juce_mac_MessageManager.mm @@ -32,14 +32,244 @@ CheckEventBlockedByModalComps isEventBlockedByModalComps = nullptr; using MenuTrackingChangedCallback = void (*)(bool); MenuTrackingChangedCallback menuTrackingChangedCallback = nullptr; +//============================================================================== +struct AppDelegateClass : public ObjCClass +{ + AppDelegateClass() : ObjCClass ("JUCEAppDelegate_") + { + addMethod (@selector (applicationWillFinishLaunching:), applicationWillFinishLaunching, "v@:@"); + addMethod (@selector (applicationShouldTerminate:), applicationShouldTerminate, "I@:@"); + addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@"); + addMethod (@selector (application:openFile:), application_openFile, "c@:@@"); + addMethod (@selector (application:openFiles:), application_openFiles, "v@:@@"); + addMethod (@selector (applicationDidBecomeActive:), applicationDidBecomeActive, "v@:@"); + addMethod (@selector (applicationDidResignActive:), applicationDidResignActive, "v@:@"); + addMethod (@selector (applicationWillUnhide:), applicationWillUnhide, "v@:@"); + + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") + addMethod (@selector (getUrl:withReplyEvent:), getUrl_withReplyEvent, "v@:@@"); + addMethod (@selector (broadcastMessageCallback:), broadcastMessageCallback, "v@:@"); + addMethod (@selector (mainMenuTrackingBegan:), mainMenuTrackingBegan, "v@:@"); + addMethod (@selector (mainMenuTrackingEnded:), mainMenuTrackingEnded, "v@:@"); + addMethod (@selector (dummyMethod), dummyMethod, "v@:"); + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + + #if JUCE_PUSH_NOTIFICATIONS + //============================================================================== + addIvar*> ("pushNotificationsDelegate"); + + addMethod (@selector (applicationDidFinishLaunching:), applicationDidFinishLaunching, "v@:@"); + + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") + addMethod (@selector (setPushNotificationsDelegate:), setPushNotificationsDelegate, "v@:@"); + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + + addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), registeredForRemoteNotifications, "v@:@@"); + addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), failedToRegisterForRemoteNotifications, "v@:@@"); + addMethod (@selector (application:didReceiveRemoteNotification:), didReceiveRemoteNotification, "v@:@@"); + #endif + + registerClass(); + } + +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 + } + + #if JUCE_PUSH_NOTIFICATIONS + static void 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); + } + } + #endif + + static NSApplicationTerminateReply applicationShouldTerminate (id /*self*/, SEL, NSApplication*) + { + if (auto* app = JUCEApplicationBase::getInstance()) + { + app->systemRequestedQuit(); + + if (! MessageManager::getInstance()->hasStopMessageBeenSent()) + return NSTerminateCancel; + } + + return NSTerminateNow; + } + + static void applicationWillTerminate (id /*self*/, SEL, NSNotification*) + { + JUCEApplicationBase::appWillTerminateByForce(); + } + + static BOOL application_openFile (id /*self*/, SEL, NSApplication*, NSString* filename) + { + if (auto* app = JUCEApplicationBase::getInstance()) + { + app->anotherInstanceStarted (quotedIfContainsSpaces (filename)); + return YES; + } + + return NO; + } + + static void 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 (" ")); + } + } + + static void applicationDidBecomeActive (id /*self*/, SEL, NSNotification*) { focusChanged(); } + static void applicationDidResignActive (id /*self*/, SEL, NSNotification*) { focusChanged(); } + static void applicationWillUnhide (id /*self*/, SEL, NSNotification*) { focusChanged(); } + + 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); + } + + static void mainMenuTrackingBegan (id /*self*/, SEL, NSNotification*) + { + if (menuTrackingChangedCallback != nullptr) + (*menuTrackingChangedCallback) (true); + } + + static void mainMenuTrackingEnded (id /*self*/, SEL, NSNotification*) + { + if (menuTrackingChangedCallback != nullptr) + (*menuTrackingChangedCallback) (false); + } + + static void dummyMethod (id /*self*/, SEL) {} // (used as a way of running a dummy thread) + + 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)); + s = s.unquoted().replace ("\"", "\\\""); + + if (s.containsChar (' ')) + s = s.quoted(); + + return s; + } + + #if JUCE_PUSH_NOTIFICATIONS + //============================================================================== + static void setPushNotificationsDelegate (id self, SEL, NSObject* delegate) + { + object_setInstanceVariable (self, "pushNotificationsDelegate", delegate); + } + + static NSObject* getPushNotificationsDelegate (id self) + { + return getIvar*> (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 +}; + +// This is declared at file scope, so that it's guaranteed to be +// constructed before and destructed after `appDelegate` (below) +static AppDelegateClass appDelegateClass; + //============================================================================== struct AppDelegate { public: AppDelegate() { - static AppDelegateClass cls; - delegate = [cls.createInstance() init]; + delegate = [appDelegateClass.createInstance() init]; NSNotificationCenter* center = [NSNotificationCenter defaultCenter]; @@ -99,234 +329,6 @@ public: MessageQueue messageQueue; id delegate; - -private: - //============================================================================== - struct AppDelegateClass : public ObjCClass - { - AppDelegateClass() : ObjCClass ("JUCEAppDelegate_") - { - addMethod (@selector (applicationWillFinishLaunching:), applicationWillFinishLaunching, "v@:@"); - addMethod (@selector (applicationShouldTerminate:), applicationShouldTerminate, "I@:@"); - addMethod (@selector (applicationWillTerminate:), applicationWillTerminate, "v@:@"); - addMethod (@selector (application:openFile:), application_openFile, "c@:@@"); - addMethod (@selector (application:openFiles:), application_openFiles, "v@:@@"); - addMethod (@selector (applicationDidBecomeActive:), applicationDidBecomeActive, "v@:@"); - addMethod (@selector (applicationDidResignActive:), applicationDidResignActive, "v@:@"); - addMethod (@selector (applicationWillUnhide:), applicationWillUnhide, "v@:@"); - - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") - addMethod (@selector (getUrl:withReplyEvent:), getUrl_withReplyEvent, "v@:@@"); - addMethod (@selector (broadcastMessageCallback:), broadcastMessageCallback, "v@:@"); - addMethod (@selector (mainMenuTrackingBegan:), mainMenuTrackingBegan, "v@:@"); - addMethod (@selector (mainMenuTrackingEnded:), mainMenuTrackingEnded, "v@:@"); - addMethod (@selector (dummyMethod), dummyMethod, "v@:"); - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - - #if JUCE_PUSH_NOTIFICATIONS - //============================================================================== - addIvar*> ("pushNotificationsDelegate"); - - addMethod (@selector (applicationDidFinishLaunching:), applicationDidFinishLaunching, "v@:@"); - - JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") - addMethod (@selector (setPushNotificationsDelegate:), setPushNotificationsDelegate, "v@:@"); - JUCE_END_IGNORE_WARNINGS_GCC_LIKE - - addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), registeredForRemoteNotifications, "v@:@@"); - addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), failedToRegisterForRemoteNotifications, "v@:@@"); - addMethod (@selector (application:didReceiveRemoteNotification:), didReceiveRemoteNotification, "v@:@@"); - #endif - - registerClass(); - } - - 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 - } - - #if JUCE_PUSH_NOTIFICATIONS - static void 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); - } - } - #endif - - static NSApplicationTerminateReply applicationShouldTerminate (id /*self*/, SEL, NSApplication*) - { - if (auto* app = JUCEApplicationBase::getInstance()) - { - app->systemRequestedQuit(); - - if (! MessageManager::getInstance()->hasStopMessageBeenSent()) - return NSTerminateCancel; - } - - return NSTerminateNow; - } - - static void applicationWillTerminate (id /*self*/, SEL, NSNotification*) - { - JUCEApplicationBase::appWillTerminateByForce(); - } - - static BOOL application_openFile (id /*self*/, SEL, NSApplication*, NSString* filename) - { - if (auto* app = JUCEApplicationBase::getInstance()) - { - app->anotherInstanceStarted (quotedIfContainsSpaces (filename)); - return YES; - } - - return NO; - } - - static void 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 (" ")); - } - } - - static void applicationDidBecomeActive (id /*self*/, SEL, NSNotification*) { focusChanged(); } - static void applicationDidResignActive (id /*self*/, SEL, NSNotification*) { focusChanged(); } - static void applicationWillUnhide (id /*self*/, SEL, NSNotification*) { focusChanged(); } - - 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); - } - - static void mainMenuTrackingBegan (id /*self*/, SEL, NSNotification*) - { - if (menuTrackingChangedCallback != nullptr) - (*menuTrackingChangedCallback) (true); - } - - static void mainMenuTrackingEnded (id /*self*/, SEL, NSNotification*) - { - if (menuTrackingChangedCallback != nullptr) - (*menuTrackingChangedCallback) (false); - } - - static void dummyMethod (id /*self*/, SEL) {} // (used as a way of running a dummy thread) - - 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)); - s = s.unquoted().replace ("\"", "\\\""); - - if (s.containsChar (' ')) - s = s.quoted(); - - return s; - } - - #if JUCE_PUSH_NOTIFICATIONS - //============================================================================== - static void setPushNotificationsDelegate (id self, SEL, NSObject* delegate) - { - object_setInstanceVariable (self, "pushNotificationsDelegate", delegate); - } - - static NSObject* getPushNotificationsDelegate (id self) - { - return getIvar*> (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 - }; }; //==============================================================================