/* ============================================================================== This file is part of the JUCE library. Copyright (c) 2022 - 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 7 End-User License Agreement and JUCE Privacy Policy. End User License Agreement: www.juce.com/juce-7-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() = default; virtual void appBecomingInactive() = 0; }; // This is an internal list of callbacks (but currently used between modules) Array appBecomingInactiveCallbacks; } // namespace juce #if JUCE_PUSH_NOTIFICATIONS @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 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; JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") - (void) application: (UIApplication*) application didRegisterUserNotificationSettings: (UIUserNotificationSettings*) notificationSettings; - (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; JUCE_END_IGNORE_WARNINGS_GCC_LIKE - (void) userNotificationCenter: (UNUserNotificationCenter*) center willPresentNotification: (UNNotification*) notification withCompletionHandler: (void (^)(UNNotificationPresentationOptions options)) completionHandler; - (void) userNotificationCenter: (UNUserNotificationCenter*) center didReceiveNotificationResponse: (UNNotificationResponse*) response withCompletionHandler: (void(^)())completionHandler; #endif @end @implementation JuceAppStartupDelegate NSObject* _pushNotificationsDelegate; - (id) init { self = [super init]; appSuspendTask = UIBackgroundTaskInvalid; #if JUCE_PUSH_NOTIFICATIONS [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 didRegisterForRemoteNotificationsWithDeviceToken: (NSData*) deviceToken { ignoreUnused (application); SEL selector = @selector (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 = @selector (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 = @selector (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 = @selector (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 = @selector (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]; } } JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") - (void) application: (UIApplication*) application didRegisterUserNotificationSettings: (UIUserNotificationSettings*) notificationSettings { ignoreUnused (application); SEL selector = @selector (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 didReceiveLocalNotification: (UILocalNotification*) notification { ignoreUnused (application); SEL selector = @selector (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 = @selector (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 = @selector (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]; } } JUCE_END_IGNORE_WARNINGS_GCC_LIKE - (void) userNotificationCenter: (UNUserNotificationCenter*) center willPresentNotification: (UNNotification*) notification withCompletionHandler: (void (^)(UNNotificationPresentationOptions options)) completionHandler { ignoreUnused (center); SEL selector = @selector (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 = @selector (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 @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 } //============================================================================== 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]; } //============================================================================== Image detail::WindowingHelpers::createIconForFile (const File&) { return {}; } //============================================================================== void SystemClipboard::copyTextToClipboard (const String& text) { [[UIPasteboard generalPasteboard] setValue: juceStringToNS (text) forPasteboardType: @"public.text"]; } String SystemClipboard::getTextFromClipboard() { return nsStringToJuce ([[UIPasteboard generalPasteboard] string]); } //============================================================================== bool detail::MouseInputSourceList::addSource() { addSource (sources.size(), MouseInputSource::InputSourceType::touch); return true; } bool detail::MouseInputSourceList::canUseTouch() const { return true; } bool Desktop::canUseSemiTransparentWindows() noexcept { return true; } bool Desktop::isDarkModeActive() const { if (@available (iOS 12.0, *)) return [[[UIScreen mainScreen] traitCollection] userInterfaceStyle] == UIUserInterfaceStyleDark; return false; } JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") static const auto darkModeSelector = @selector (darkModeChanged:); JUCE_END_IGNORE_WARNINGS_GCC_LIKE class Desktop::NativeDarkModeChangeDetectorImpl { public: NativeDarkModeChangeDetectorImpl() { static DelegateClass delegateClass; delegate.reset ([delegateClass.createInstance() init]); observer.emplace (delegate.get(), darkModeSelector, UIViewComponentPeer::getDarkModeNotificationName(), nil); } private: struct DelegateClass final : public ObjCClass { DelegateClass() : ObjCClass ("JUCEDelegate_") { addMethod (darkModeSelector, [] (id, SEL, NSNotification*) { Desktop::getInstance().darkModeChanged(); }); registerClass(); } }; NSUniquePtr delegate; Optional observer; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeDarkModeChangeDetectorImpl) }; std::unique_ptr Desktop::createNativeDarkModeChangeDetectorImpl() { return std::make_unique(); } //============================================================================== 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); } // The most straightforward way of retrieving the screen area available to an iOS app // seems to be to create a new window (which will take up all available space) and to // query its frame. struct TemporaryWindow { UIWindow* window = [[UIWindow alloc] init]; ~TemporaryWindow() noexcept { [window release]; } }; static Rectangle getRecommendedWindowBounds() { return convertToRectInt (TemporaryWindow().window.frame); } static BorderSize getSafeAreaInsets (float masterScale) { if (@available (iOS 11.0, *)) { UIEdgeInsets safeInsets = TemporaryWindow().window.safeAreaInsets; return detail::WindowingHelpers::roundToInt (BorderSize { safeInsets.top, safeInsets.left, safeInsets.bottom, safeInsets.right }.multipliedBy (1.0 / (double) masterScale)); } JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") auto statusBarSize = [UIApplication sharedApplication].statusBarFrame.size; JUCE_END_IGNORE_WARNINGS_GCC_LIKE auto statusBarHeight = jmin (statusBarSize.width, statusBarSize.height); return { roundToInt (statusBarHeight / masterScale), 0, 0, 0 }; } //============================================================================== void Displays::findDisplays (float masterScale) { JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") static const auto keyboardShownSelector = @selector (juceKeyboardShown:); static const auto keyboardHiddenSelector = @selector (juceKeyboardHidden:); JUCE_END_IGNORE_WARNINGS_GCC_LIKE class OnScreenKeyboardChangeDetectorImpl { public: OnScreenKeyboardChangeDetectorImpl() { static DelegateClass delegateClass; delegate.reset ([delegateClass.createInstance() init]); object_setInstanceVariable (delegate.get(), "owner", this); observers.emplace_back (delegate.get(), keyboardShownSelector, UIKeyboardDidShowNotification, nil); observers.emplace_back (delegate.get(), keyboardHiddenSelector, UIKeyboardDidHideNotification, nil); } auto getInsets() const { return insets; } private: struct DelegateClass final : public ObjCClass { DelegateClass() : ObjCClass ("JUCEOnScreenKeyboardObserver_") { addIvar ("owner"); addMethod (keyboardShownSelector, [] (id self, SEL, NSNotification* notification) { setKeyboardScreenBounds (self, [&]() -> BorderSize { auto* info = [notification userInfo]; if (info == nullptr) return {}; auto* value = static_cast ([info objectForKey: UIKeyboardFrameEndUserInfoKey]); if (value == nullptr) return {}; auto* display = Desktop::getInstance().getDisplays().getPrimaryDisplay(); if (display == nullptr) return {}; const auto rect = convertToRectInt ([value CGRectValue]); BorderSize result; if (rect.getY() == display->totalArea.getY()) result.setTop (rect.getHeight()); if (rect.getBottom() == display->totalArea.getBottom()) result.setBottom (rect.getHeight()); return result; }()); }); addMethod (keyboardHiddenSelector, [] (id self, SEL, NSNotification*) { setKeyboardScreenBounds (self, {}); }); registerClass(); } private: static void setKeyboardScreenBounds (id self, BorderSize insets) { if (std::exchange (getIvar (self, "owner")->insets, insets) != insets) Desktop::getInstance().displays->refresh(); } }; BorderSize insets; NSUniquePtr delegate; std::vector observers; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OnScreenKeyboardChangeDetectorImpl) }; JUCE_AUTORELEASEPOOL { static OnScreenKeyboardChangeDetectorImpl keyboardChangeDetector; UIScreen* s = [UIScreen mainScreen]; Display d; d.totalArea = convertToRectInt ([s bounds]) / masterScale; d.userArea = getRecommendedWindowBounds() / masterScale; d.safeAreaInsets = getSafeAreaInsets (masterScale); const auto scaledInsets = keyboardChangeDetector.getInsets().multipliedBy (1.0 / (double) masterScale); d.keyboardInsets = detail::WindowingHelpers::roundToInt (scaledInsets); d.isMain = true; d.scale = masterScale * s.scale; d.dpi = 160 * d.scale; displays.add (d); } } } // namespace juce