/* ============================================================================== This file is part of the JUCE 7 technical preview. Copyright (c) 2022 - Raw Material Software Limited You may use this code under the terms of the GPL v3 (see www.gnu.org/licenses). For the technical preview this file cannot be licensed commercially. 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; } #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 = @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 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]; } } - (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]; } } #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 = @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 #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 { public: iOSMessageBox (const MessageBoxOptions& opts, std::unique_ptr&& cb, bool deleteOnCompletion) : callback (std::move (cb)), shouldDeleteThis (deleteOnCompletion) { if (currentlyFocusedPeer != nullptr) { UIAlertController* alert = [UIAlertController alertControllerWithTitle: juceStringToNS (opts.getTitle()) message: juceStringToNS (opts.getMessage()) preferredStyle: UIAlertControllerStyleAlert]; addButton (alert, opts.getButtonText (0)); addButton (alert, opts.getButtonText (1)); addButton (alert, opts.getButtonText (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; } } int getResult() { jassert (callback == nullptr); JUCE_AUTORELEASEPOOL { while (result < 0) [[NSRunLoop mainRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]]; } return result; } void buttonClicked (int buttonIndex) noexcept { result = buttonIndex; if (callback != nullptr) callback->modalStateFinished (result); if (shouldDeleteThis) delete this; } private: void addButton (UIAlertController* alert, const String& text) { if (! text.isEmpty()) { const auto index = [[alert actions] count]; [alert addAction: [UIAlertAction actionWithTitle: juceStringToNS (text) style: UIAlertActionStyleDefault handler: ^(UIAlertAction*) { this->buttonClicked ((int) index); }]]; } } int result = -1; std::unique_ptr callback; const bool shouldDeleteThis; JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (iOSMessageBox) }; //============================================================================== static int showDialog (const MessageBoxOptions& options, ModalComponentManager::Callback* callbackIn, AlertWindowMappings::MapFn mapFn) { #if JUCE_MODAL_LOOPS_PERMITTED if (callbackIn == nullptr) { JUCE_AUTORELEASEPOOL { jassert (mapFn != nullptr); iOSMessageBox messageBox (options, nullptr, false); return mapFn (messageBox.getResult()); } } #endif const auto showBox = [options, callbackIn, mapFn] { new iOSMessageBox (options, AlertWindowMappings::getWrappedCallback (callbackIn, mapFn), true); }; if (MessageManager::getInstance()->isThisTheMessageThread()) showBox(); else MessageManager::callAsync (showBox); return 0; } #if JUCE_MODAL_LOOPS_PERMITTED void JUCE_CALLTYPE NativeMessageBox::showMessageBox (MessageBoxIconType /*iconType*/, const String& title, const String& message, Component* /*associatedComponent*/) { showDialog (MessageBoxOptions() .withTitle (title) .withMessage (message) .withButton (TRANS("OK")), nullptr, AlertWindowMappings::messageBox); } int JUCE_CALLTYPE NativeMessageBox::show (const MessageBoxOptions& options) { return showDialog (options, nullptr, AlertWindowMappings::noMapping); } #endif void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (MessageBoxIconType /*iconType*/, const String& title, const String& message, Component* /*associatedComponent*/, ModalComponentManager::Callback* callback) { showDialog (MessageBoxOptions() .withTitle (title) .withMessage (message) .withButton (TRANS("OK")), callback, AlertWindowMappings::messageBox); } bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (MessageBoxIconType /*iconType*/, const String& title, const String& message, Component* /*associatedComponent*/, ModalComponentManager::Callback* callback) { return showDialog (MessageBoxOptions() .withTitle (title) .withMessage (message) .withButton (TRANS("OK")) .withButton (TRANS("Cancel")), callback, AlertWindowMappings::okCancel) != 0; } int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (MessageBoxIconType /*iconType*/, const String& title, const String& message, Component* /*associatedComponent*/, ModalComponentManager::Callback* callback) { return showDialog (MessageBoxOptions() .withTitle (title) .withMessage (message) .withButton (TRANS("Yes")) .withButton (TRANS("No")) .withButton (TRANS("Cancel")), callback, AlertWindowMappings::yesNoCancel); } int JUCE_CALLTYPE NativeMessageBox::showYesNoBox (MessageBoxIconType /*iconType*/, const String& title, const String& message, Component* /*associatedComponent*/, ModalComponentManager::Callback* callback) { return showDialog (MessageBoxOptions() .withTitle (title) .withMessage (message) .withButton (TRANS("Yes")) .withButton (TRANS("No")), callback, AlertWindowMappings::okCancel); } void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options, ModalComponentManager::Callback* callback) { showDialog (options, callback, AlertWindowMappings::noMapping); } void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options, std::function callback) { showAsync (options, ModalCallbackFunction::create (callback)); } //============================================================================== 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] string]); } //============================================================================== bool MouseInputSource::SourceList::addSource() { addSource (sources.size(), MouseInputSource::InputSourceType::touch); return true; } bool MouseInputSource::SourceList::canUseTouch() { return true; } bool Desktop::canUseSemiTransparentWindows() noexcept { return true; } bool Desktop::isDarkModeActive() const { #if defined (__IPHONE_12_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_12_0 if (@available (iOS 12.0, *)) return [[[UIScreen mainScreen] traitCollection] userInterfaceStyle] == UIUserInterfaceStyleDark; #endif return false; } class Desktop::NativeDarkModeChangeDetectorImpl { public: NativeDarkModeChangeDetectorImpl() { static DelegateClass delegateClass; delegate = [delegateClass.createInstance() init]; object_setInstanceVariable (delegate, "owner", this); JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") [[NSNotificationCenter defaultCenter] addObserver: delegate selector: @selector (darkModeChanged:) name: UIViewComponentPeer::getDarkModeNotificationName() object: nil]; JUCE_END_IGNORE_WARNINGS_GCC_LIKE } ~NativeDarkModeChangeDetectorImpl() { object_setInstanceVariable (delegate, "owner", nullptr); [[NSNotificationCenter defaultCenter] removeObserver: delegate]; [delegate release]; } void darkModeChanged() { Desktop::getInstance().darkModeChanged(); } private: struct DelegateClass : public ObjCClass { DelegateClass() : ObjCClass ("JUCEDelegate_") { addIvar ("owner"); JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") addMethod (@selector (darkModeChanged:), darkModeChanged); JUCE_END_IGNORE_WARNINGS_GCC_LIKE registerClass(); } static void darkModeChanged (id self, SEL, NSNotification*) { if (auto* owner = getIvar (self, "owner")) owner->darkModeChanged(); } }; id delegate = nil; 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 defined (__IPHONE_11_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_11_0 if (@available (iOS 11.0, *)) { UIEdgeInsets safeInsets = TemporaryWindow().window.safeAreaInsets; auto getInset = [&] (CGFloat original) { return roundToInt (original / masterScale); }; return { getInset (safeInsets.top), getInset (safeInsets.left), getInset (safeInsets.bottom), getInset (safeInsets.right) }; } #endif 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_AUTORELEASEPOOL { UIScreen* s = [UIScreen mainScreen]; Display d; d.totalArea = convertToRectInt ([s bounds]) / masterScale; d.userArea = getRecommendedWindowBounds() / masterScale; d.safeAreaInsets = getSafeAreaInsets (masterScale); d.isMain = true; d.scale = masterScale * s.scale; d.dpi = 160 * d.scale; displays.add (d); } } } // namespace juce