/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2020 - Raw Material Software Limited JUCE is an open source library subject to commercial or open-source licensing. By using JUCE, you agree to the terms of both the JUCE 6 End-User License Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). End User License Agreement: www.juce.com/juce-6-licence Privacy Policy: www.juce.com/juce-privacy-policy Or: You may also use this code under the terms of the GPL v3 (see www.gnu.org/licenses). JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE DISCLAIMED. ============================================================================== */ namespace juce { extern bool isIOSAppActive; struct AppInactivityCallback // NB: careful, this declaration is duplicated in other modules { virtual ~AppInactivityCallback() {} virtual void appBecomingInactive() = 0; }; // This is an internal list of callbacks (but currently used between modules) Array appBecomingInactiveCallbacks; } #if JUCE_PUSH_NOTIFICATIONS && defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 @interface JuceAppStartupDelegate : NSObject #else @interface JuceAppStartupDelegate : NSObject #endif { UIBackgroundTaskIdentifier appSuspendTask; } @property (strong, nonatomic) UIWindow *window; - (id) init; - (void) dealloc; - (void) applicationDidFinishLaunching: (UIApplication*) application; - (void) applicationWillTerminate: (UIApplication*) application; - (void) applicationDidEnterBackground: (UIApplication*) application; - (void) applicationWillEnterForeground: (UIApplication*) application; - (void) applicationDidBecomeActive: (UIApplication*) application; - (void) applicationWillResignActive: (UIApplication*) application; - (void) application: (UIApplication*) application handleEventsForBackgroundURLSession: (NSString*) identifier completionHandler: (void (^)(void)) completionHandler; - (void) applicationDidReceiveMemoryWarning: (UIApplication *) application; #if JUCE_PUSH_NOTIFICATIONS - (void) application: (UIApplication*) application didRegisterUserNotificationSettings: (UIUserNotificationSettings*) notificationSettings; - (void) application: (UIApplication*) application didRegisterForRemoteNotificationsWithDeviceToken: (NSData*) deviceToken; - (void) application: (UIApplication*) application didFailToRegisterForRemoteNotificationsWithError: (NSError*) error; - (void) application: (UIApplication*) application didReceiveRemoteNotification: (NSDictionary*) userInfo; - (void) application: (UIApplication*) application didReceiveRemoteNotification: (NSDictionary*) userInfo fetchCompletionHandler: (void (^)(UIBackgroundFetchResult result)) completionHandler; - (void) application: (UIApplication*) application handleActionWithIdentifier: (NSString*) identifier forRemoteNotification: (NSDictionary*) userInfo withResponseInfo: (NSDictionary*) responseInfo completionHandler: (void(^)()) completionHandler; - (void) application: (UIApplication*) application didReceiveLocalNotification: (UILocalNotification*) notification; - (void) application: (UIApplication*) application handleActionWithIdentifier: (NSString*) identifier forLocalNotification: (UILocalNotification*) notification completionHandler: (void(^)()) completionHandler; - (void) application: (UIApplication*) application handleActionWithIdentifier: (NSString*) identifier forLocalNotification: (UILocalNotification*) notification withResponseInfo: (NSDictionary*) responseInfo completionHandler: (void(^)()) completionHandler; #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - (void) userNotificationCenter: (UNUserNotificationCenter*) center willPresentNotification: (UNNotification*) notification withCompletionHandler: (void (^)(UNNotificationPresentationOptions options)) completionHandler; - (void) userNotificationCenter: (UNUserNotificationCenter*) center didReceiveNotificationResponse: (UNNotificationResponse*) response withCompletionHandler: (void(^)())completionHandler; #endif #endif @end @implementation JuceAppStartupDelegate NSObject* _pushNotificationsDelegate; - (id) init { self = [super init]; appSuspendTask = UIBackgroundTaskInvalid; #if JUCE_PUSH_NOTIFICATIONS && defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 [UNUserNotificationCenter currentNotificationCenter].delegate = self; #endif return self; } - (void) dealloc { [super dealloc]; } - (void) applicationDidFinishLaunching: (UIApplication*) application { ignoreUnused (application); initialiseJuce_GUI(); if (auto* app = JUCEApplicationBase::createInstance()) { if (! app->initialiseApp()) exit (app->shutdownApp()); } else { jassertfalse; // you must supply an application object for an iOS app! } } - (void) applicationWillTerminate: (UIApplication*) application { ignoreUnused (application); JUCEApplicationBase::appWillTerminateByForce(); } - (void) applicationDidEnterBackground: (UIApplication*) application { if (auto* app = JUCEApplicationBase::getInstance()) { #if JUCE_EXECUTE_APP_SUSPEND_ON_BACKGROUND_TASK appSuspendTask = [application beginBackgroundTaskWithName:@"JUCE Suspend Task" expirationHandler:^{ if (appSuspendTask != UIBackgroundTaskInvalid) { [application endBackgroundTask:appSuspendTask]; appSuspendTask = UIBackgroundTaskInvalid; } }]; MessageManager::callAsync ([app] { app->suspended(); }); #else ignoreUnused (application); app->suspended(); #endif } } - (void) applicationWillEnterForeground: (UIApplication*) application { ignoreUnused (application); if (auto* app = JUCEApplicationBase::getInstance()) app->resumed(); } - (void) applicationDidBecomeActive: (UIApplication*) application { application.applicationIconBadgeNumber = 0; isIOSAppActive = true; } - (void) applicationWillResignActive: (UIApplication*) application { ignoreUnused (application); isIOSAppActive = false; for (int i = appBecomingInactiveCallbacks.size(); --i >= 0;) appBecomingInactiveCallbacks.getReference(i)->appBecomingInactive(); } - (void) application: (UIApplication*) application handleEventsForBackgroundURLSession: (NSString*)identifier completionHandler: (void (^)(void))completionHandler { ignoreUnused (application); URL::DownloadTask::juce_iosURLSessionNotify (nsStringToJuce (identifier)); completionHandler(); } - (void) applicationDidReceiveMemoryWarning: (UIApplication*) application { ignoreUnused (application); if (auto* app = JUCEApplicationBase::getInstance()) app->memoryWarningReceived(); } - (void) setPushNotificationsDelegateToUse: (NSObject*) delegate { _pushNotificationsDelegate = delegate; } #if JUCE_PUSH_NOTIFICATIONS - (void) application: (UIApplication*) application didRegisterUserNotificationSettings: (UIUserNotificationSettings*) notificationSettings { ignoreUnused (application); SEL selector = NSSelectorFromString (@"application:didRegisterUserNotificationSettings:"); if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector]) { NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]]; [invocation setSelector: selector]; [invocation setTarget: _pushNotificationsDelegate]; [invocation setArgument: &application atIndex:2]; [invocation setArgument: ¬ificationSettings atIndex:3]; [invocation invoke]; } } - (void) application: (UIApplication*) application didRegisterForRemoteNotificationsWithDeviceToken: (NSData*) deviceToken { ignoreUnused (application); SEL selector = NSSelectorFromString (@"application:didRegisterForRemoteNotificationsWithDeviceToken:"); if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector]) { NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]]; [invocation setSelector: selector]; [invocation setTarget: _pushNotificationsDelegate]; [invocation setArgument: &application atIndex:2]; [invocation setArgument: &deviceToken atIndex:3]; [invocation invoke]; } } - (void) application: (UIApplication*) application didFailToRegisterForRemoteNotificationsWithError: (NSError*) error { ignoreUnused (application); SEL selector = NSSelectorFromString (@"application:didFailToRegisterForRemoteNotificationsWithError:"); if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector]) { NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]]; [invocation setSelector: selector]; [invocation setTarget: _pushNotificationsDelegate]; [invocation setArgument: &application atIndex:2]; [invocation setArgument: &error atIndex:3]; [invocation invoke]; } } - (void) application: (UIApplication*) application didReceiveRemoteNotification: (NSDictionary*) userInfo { ignoreUnused (application); SEL selector = NSSelectorFromString (@"application:didReceiveRemoteNotification:"); if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector]) { NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]]; [invocation setSelector: selector]; [invocation setTarget: _pushNotificationsDelegate]; [invocation setArgument: &application atIndex:2]; [invocation setArgument: &userInfo atIndex:3]; [invocation invoke]; } } - (void) application: (UIApplication*) application didReceiveRemoteNotification: (NSDictionary*) userInfo fetchCompletionHandler: (void (^)(UIBackgroundFetchResult result)) completionHandler { ignoreUnused (application); SEL selector = NSSelectorFromString (@"application:didReceiveRemoteNotification:fetchCompletionHandler:"); if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector]) { NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]]; [invocation setSelector: selector]; [invocation setTarget: _pushNotificationsDelegate]; [invocation setArgument: &application atIndex:2]; [invocation setArgument: &userInfo atIndex:3]; [invocation setArgument: &completionHandler atIndex:4]; [invocation invoke]; } } - (void) application: (UIApplication*) application handleActionWithIdentifier: (NSString*) identifier forRemoteNotification: (NSDictionary*) userInfo withResponseInfo: (NSDictionary*) responseInfo completionHandler: (void(^)()) completionHandler { ignoreUnused (application); SEL selector = NSSelectorFromString (@"application:handleActionWithIdentifier:forRemoteNotification:withResponseInfo:completionHandler:"); if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector]) { NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]]; [invocation setSelector: selector]; [invocation setTarget: _pushNotificationsDelegate]; [invocation setArgument: &application atIndex:2]; [invocation setArgument: &identifier atIndex:3]; [invocation setArgument: &userInfo atIndex:4]; [invocation setArgument: &responseInfo atIndex:5]; [invocation setArgument: &completionHandler atIndex:6]; [invocation invoke]; } } - (void) application: (UIApplication*) application didReceiveLocalNotification: (UILocalNotification*) notification { ignoreUnused (application); SEL selector = NSSelectorFromString (@"application:didReceiveLocalNotification:"); if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector]) { NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]]; [invocation setSelector: selector]; [invocation setTarget: _pushNotificationsDelegate]; [invocation setArgument: &application atIndex:2]; [invocation setArgument: ¬ification atIndex:3]; [invocation invoke]; } } - (void) application: (UIApplication*) application handleActionWithIdentifier: (NSString*) identifier forLocalNotification: (UILocalNotification*) notification completionHandler: (void(^)()) completionHandler { ignoreUnused (application); SEL selector = NSSelectorFromString (@"application:handleActionWithIdentifier:forLocalNotification:completionHandler:"); if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector]) { NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]]; [invocation setSelector: selector]; [invocation setTarget: _pushNotificationsDelegate]; [invocation setArgument: &application atIndex:2]; [invocation setArgument: &identifier atIndex:3]; [invocation setArgument: ¬ification atIndex:4]; [invocation setArgument: &completionHandler atIndex:5]; [invocation invoke]; } } - (void) application: (UIApplication*) application handleActionWithIdentifier: (NSString*) identifier forLocalNotification: (UILocalNotification*) notification withResponseInfo: (NSDictionary*) responseInfo completionHandler: (void(^)()) completionHandler { ignoreUnused (application); SEL selector = NSSelectorFromString (@"application:handleActionWithIdentifier:forLocalNotification:withResponseInfo:completionHandler:"); if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector]) { NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]]; [invocation setSelector: selector]; [invocation setTarget: _pushNotificationsDelegate]; [invocation setArgument: &application atIndex:2]; [invocation setArgument: &identifier atIndex:3]; [invocation setArgument: ¬ification atIndex:4]; [invocation setArgument: &responseInfo atIndex:5]; [invocation setArgument: &completionHandler atIndex:6]; [invocation invoke]; } } #if defined (__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 - (void) userNotificationCenter: (UNUserNotificationCenter*) center willPresentNotification: (UNNotification*) notification withCompletionHandler: (void (^)(UNNotificationPresentationOptions options)) completionHandler { ignoreUnused (center); SEL selector = NSSelectorFromString (@"userNotificationCenter:willPresentNotification:withCompletionHandler:"); if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector]) { NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]]; [invocation setSelector: selector]; [invocation setTarget: _pushNotificationsDelegate]; [invocation setArgument: ¢er atIndex:2]; [invocation setArgument: ¬ification atIndex:3]; [invocation setArgument: &completionHandler atIndex:4]; [invocation invoke]; } } - (void) userNotificationCenter: (UNUserNotificationCenter*) center didReceiveNotificationResponse: (UNNotificationResponse*) response withCompletionHandler: (void(^)()) completionHandler { ignoreUnused (center); SEL selector = NSSelectorFromString (@"userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:"); if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector]) { NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]]; [invocation setSelector: selector]; [invocation setTarget: _pushNotificationsDelegate]; [invocation setArgument: ¢er atIndex:2]; [invocation setArgument: &response atIndex:3]; [invocation setArgument: &completionHandler atIndex:4]; [invocation invoke]; } } #endif #endif @end namespace juce { int juce_iOSMain (int argc, const char* argv[], void* customDelegatePtr); int juce_iOSMain (int argc, const char* argv[], void* customDelegatePtr) { Class delegateClass = (customDelegatePtr != nullptr ? reinterpret_cast (customDelegatePtr) : [JuceAppStartupDelegate class]); return UIApplicationMain (argc, const_cast (argv), nil, NSStringFromClass (delegateClass)); } //============================================================================== void LookAndFeel::playAlertSound() { // TODO } //============================================================================== class iOSMessageBox; #if defined (__IPHONE_8_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_8_0 #define JUCE_USE_NEW_IOS_ALERTWINDOW 1 #endif #if ! JUCE_USE_NEW_IOS_ALERTWINDOW } // (juce namespace) @interface JuceAlertBoxDelegate : NSObject { @public iOSMessageBox* owner; } - (void) alertView: (UIAlertView*) alertView clickedButtonAtIndex: (NSInteger) buttonIndex; @end namespace juce { #endif class iOSMessageBox { public: iOSMessageBox (const String& title, const String& message, NSString* button1, NSString* button2, NSString* button3, ModalComponentManager::Callback* cb, const bool async) : result (0), resultReceived (false), callback (cb), isAsync (async) { #if JUCE_USE_NEW_IOS_ALERTWINDOW if (currentlyFocusedPeer != nullptr) { UIAlertController* alert = [UIAlertController alertControllerWithTitle: juceStringToNS (title) message: juceStringToNS (message) preferredStyle: UIAlertControllerStyleAlert]; addButton (alert, button1, 0); addButton (alert, button2, 1); addButton (alert, button3, 2); [currentlyFocusedPeer->controller presentViewController: alert animated: YES completion: nil]; } else { // Since iOS8, alert windows need to be associated with a window, so you need to // have at least one window on screen when you use this jassertfalse; } #else delegate = [[JuceAlertBoxDelegate alloc] init]; delegate->owner = this; alert = [[UIAlertView alloc] initWithTitle: juceStringToNS (title) message: juceStringToNS (message) delegate: delegate cancelButtonTitle: button1 otherButtonTitles: button2, button3, nil]; [alert retain]; [alert show]; #endif } ~iOSMessageBox() { #if ! JUCE_USE_NEW_IOS_ALERTWINDOW [alert release]; [delegate release]; #endif } int getResult() { jassert (callback == nullptr); JUCE_AUTORELEASEPOOL { #if JUCE_USE_NEW_IOS_ALERTWINDOW while (! resultReceived) #else while (! (alert.hidden || resultReceived)) #endif [[NSRunLoop mainRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]]; } return result; } void buttonClicked (const int buttonIndex) noexcept { result = buttonIndex; resultReceived = true; if (callback != nullptr) callback->modalStateFinished (result); if (isAsync) delete this; } private: int result; bool resultReceived; std::unique_ptr callback; const bool isAsync; #if JUCE_USE_NEW_IOS_ALERTWINDOW void addButton (UIAlertController* alert, NSString* text, int index) { if (text != nil) [alert addAction: [UIAlertAction actionWithTitle: text style: UIAlertActionStyleDefault handler: ^(UIAlertAction*) { this->buttonClicked (index); }]]; } #else UIAlertView* alert; JuceAlertBoxDelegate* delegate; #endif JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (iOSMessageBox) }; #if ! JUCE_USE_NEW_IOS_ALERTWINDOW } // (juce namespace) @implementation JuceAlertBoxDelegate - (void) alertView: (UIAlertView*) alertView clickedButtonAtIndex: (NSInteger) buttonIndex { owner->buttonClicked ((int) buttonIndex); alertView.hidden = true; } @end namespace juce { #endif //============================================================================== #if JUCE_MODAL_LOOPS_PERMITTED void JUCE_CALLTYPE NativeMessageBox::showMessageBox (AlertWindow::AlertIconType /*iconType*/, const String& title, const String& message, Component* /*associatedComponent*/) { JUCE_AUTORELEASEPOOL { iOSMessageBox mb (title, message, @"OK", nil, nil, nullptr, false); ignoreUnused (mb.getResult()); } } #endif void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (AlertWindow::AlertIconType /*iconType*/, const String& title, const String& message, Component* /*associatedComponent*/, ModalComponentManager::Callback* callback) { new iOSMessageBox (title, message, @"OK", nil, nil, callback, true); } bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (AlertWindow::AlertIconType /*iconType*/, const String& title, const String& message, Component* /*associatedComponent*/, ModalComponentManager::Callback* callback) { std::unique_ptr mb (new iOSMessageBox (title, message, @"Cancel", @"OK", nil, callback, callback != nullptr)); if (callback == nullptr) return mb->getResult() == 1; mb.release(); return false; } int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (AlertWindow::AlertIconType /*iconType*/, const String& title, const String& message, Component* /*associatedComponent*/, ModalComponentManager::Callback* callback) { std::unique_ptr mb (new iOSMessageBox (title, message, @"Cancel", @"Yes", @"No", callback, callback != nullptr)); if (callback == nullptr) return mb->getResult(); mb.release(); return 0; } int JUCE_CALLTYPE NativeMessageBox::showYesNoBox (AlertWindow::AlertIconType /*iconType*/, const String& title, const String& message, Component* /*associatedComponent*/, ModalComponentManager::Callback* callback) { std::unique_ptr mb (new iOSMessageBox (title, message, @"No", @"Yes", nil, callback, callback != nullptr)); if (callback == nullptr) return mb->getResult(); mb.release(); return 0; } //============================================================================== bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray&, bool, Component*, std::function) { jassertfalse; // no such thing on iOS! return false; } bool DragAndDropContainer::performExternalDragDropOfText (const String&, Component*, std::function) { jassertfalse; // no such thing on iOS! return false; } //============================================================================== void Desktop::setScreenSaverEnabled (const bool isEnabled) { if (! SystemStats::isRunningInAppExtensionSandbox()) [[UIApplication sharedApplication] setIdleTimerDisabled: ! isEnabled]; } bool Desktop::isScreenSaverEnabled() { if (SystemStats::isRunningInAppExtensionSandbox()) return true; return ! [[UIApplication sharedApplication] isIdleTimerDisabled]; } //============================================================================== bool juce_areThereAnyAlwaysOnTopWindows() { return false; } //============================================================================== Image juce_createIconForFile (const File&) { return Image(); } //============================================================================== void SystemClipboard::copyTextToClipboard (const String& text) { [[UIPasteboard generalPasteboard] setValue: juceStringToNS (text) forPasteboardType: @"public.text"]; } String SystemClipboard::getTextFromClipboard() { return nsStringToJuce ([[UIPasteboard generalPasteboard] valueForPasteboardType: @"public.text"]); } //============================================================================== bool MouseInputSource::SourceList::addSource() { addSource (sources.size(), MouseInputSource::InputSourceType::touch); return true; } bool MouseInputSource::SourceList::canUseTouch() { return true; } bool Desktop::canUseSemiTransparentWindows() noexcept { return true; } Point MouseInputSource::getCurrentRawMousePosition() { return juce_lastMousePos; } void MouseInputSource::setRawMousePosition (Point) { } double Desktop::getDefaultMasterScale() { return 1.0; } Desktop::DisplayOrientation Desktop::getCurrentOrientation() const { UIInterfaceOrientation orientation = SystemStats::isRunningInAppExtensionSandbox() ? UIInterfaceOrientationPortrait : getWindowOrientation(); return Orientations::convertToJuce (orientation); } void Displays::findDisplays (float masterScale) { JUCE_AUTORELEASEPOOL { UIScreen* s = [UIScreen mainScreen]; Display d; d.userArea = d.totalArea = UIViewComponentPeer::realScreenPosToRotated (convertToRectInt ([s bounds])) / masterScale; d.isMain = true; d.scale = masterScale; if ([s respondsToSelector: @selector (scale)]) d.scale *= s.scale; d.dpi = 160 * d.scale; displays.add (d); } } } // namespace juce