The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

765 lines
28KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. namespace juce
  19. {
  20. extern bool isIOSAppActive;
  21. struct AppInactivityCallback // NB: careful, this declaration is duplicated in other modules
  22. {
  23. virtual ~AppInactivityCallback() = default;
  24. virtual void appBecomingInactive() = 0;
  25. };
  26. // This is an internal list of callbacks (but currently used between modules)
  27. Array<AppInactivityCallback*> appBecomingInactiveCallbacks;
  28. } // namespace juce
  29. #if JUCE_PUSH_NOTIFICATIONS
  30. @interface JuceAppStartupDelegate : NSObject <UIApplicationDelegate, UNUserNotificationCenterDelegate>
  31. #else
  32. @interface JuceAppStartupDelegate : NSObject <UIApplicationDelegate>
  33. #endif
  34. {
  35. UIBackgroundTaskIdentifier appSuspendTask;
  36. }
  37. @property (strong, nonatomic) UIWindow *window;
  38. - (id) init;
  39. - (void) dealloc;
  40. - (void) applicationDidFinishLaunching: (UIApplication*) application;
  41. - (void) applicationWillTerminate: (UIApplication*) application;
  42. - (void) applicationDidEnterBackground: (UIApplication*) application;
  43. - (void) applicationWillEnterForeground: (UIApplication*) application;
  44. - (void) applicationDidBecomeActive: (UIApplication*) application;
  45. - (void) applicationWillResignActive: (UIApplication*) application;
  46. - (void) application: (UIApplication*) application handleEventsForBackgroundURLSession: (NSString*) identifier
  47. completionHandler: (void (^)(void)) completionHandler;
  48. - (void) applicationDidReceiveMemoryWarning: (UIApplication *) application;
  49. #if JUCE_PUSH_NOTIFICATIONS
  50. - (void) application: (UIApplication*) application
  51. didRegisterForRemoteNotificationsWithDeviceToken: (NSData*) deviceToken;
  52. - (void) application: (UIApplication*) application
  53. didFailToRegisterForRemoteNotificationsWithError: (NSError*) error;
  54. - (void) application: (UIApplication*) application
  55. didReceiveRemoteNotification: (NSDictionary*) userInfo;
  56. - (void) application: (UIApplication*) application
  57. didReceiveRemoteNotification: (NSDictionary*) userInfo
  58. fetchCompletionHandler: (void (^)(UIBackgroundFetchResult result)) completionHandler;
  59. - (void) application: (UIApplication*) application
  60. handleActionWithIdentifier: (NSString*) identifier
  61. forRemoteNotification: (NSDictionary*) userInfo
  62. withResponseInfo: (NSDictionary*) responseInfo
  63. completionHandler: (void(^)()) completionHandler;
  64. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  65. - (void) application: (UIApplication*) application
  66. didRegisterUserNotificationSettings: (UIUserNotificationSettings*) notificationSettings;
  67. - (void) application: (UIApplication*) application
  68. didReceiveLocalNotification: (UILocalNotification*) notification;
  69. - (void) application: (UIApplication*) application
  70. handleActionWithIdentifier: (NSString*) identifier
  71. forLocalNotification: (UILocalNotification*) notification
  72. completionHandler: (void(^)()) completionHandler;
  73. - (void) application: (UIApplication*) application
  74. handleActionWithIdentifier: (NSString*) identifier
  75. forLocalNotification: (UILocalNotification*) notification
  76. withResponseInfo: (NSDictionary*) responseInfo
  77. completionHandler: (void(^)()) completionHandler;
  78. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  79. - (void) userNotificationCenter: (UNUserNotificationCenter*) center
  80. willPresentNotification: (UNNotification*) notification
  81. withCompletionHandler: (void (^)(UNNotificationPresentationOptions options)) completionHandler;
  82. - (void) userNotificationCenter: (UNUserNotificationCenter*) center
  83. didReceiveNotificationResponse: (UNNotificationResponse*) response
  84. withCompletionHandler: (void(^)())completionHandler;
  85. #endif
  86. @end
  87. @implementation JuceAppStartupDelegate
  88. NSObject* _pushNotificationsDelegate;
  89. - (id) init
  90. {
  91. self = [super init];
  92. appSuspendTask = UIBackgroundTaskInvalid;
  93. #if JUCE_PUSH_NOTIFICATIONS
  94. [UNUserNotificationCenter currentNotificationCenter].delegate = self;
  95. #endif
  96. return self;
  97. }
  98. - (void) dealloc
  99. {
  100. [super dealloc];
  101. }
  102. - (void) applicationDidFinishLaunching: (UIApplication*) application
  103. {
  104. ignoreUnused (application);
  105. initialiseJuce_GUI();
  106. if (auto* app = JUCEApplicationBase::createInstance())
  107. {
  108. if (! app->initialiseApp())
  109. exit (app->shutdownApp());
  110. }
  111. else
  112. {
  113. jassertfalse; // you must supply an application object for an iOS app!
  114. }
  115. }
  116. - (void) applicationWillTerminate: (UIApplication*) application
  117. {
  118. ignoreUnused (application);
  119. JUCEApplicationBase::appWillTerminateByForce();
  120. }
  121. - (void) applicationDidEnterBackground: (UIApplication*) application
  122. {
  123. if (auto* app = JUCEApplicationBase::getInstance())
  124. {
  125. #if JUCE_EXECUTE_APP_SUSPEND_ON_BACKGROUND_TASK
  126. appSuspendTask = [application beginBackgroundTaskWithName:@"JUCE Suspend Task" expirationHandler:^{
  127. if (appSuspendTask != UIBackgroundTaskInvalid)
  128. {
  129. [application endBackgroundTask:appSuspendTask];
  130. appSuspendTask = UIBackgroundTaskInvalid;
  131. }
  132. }];
  133. MessageManager::callAsync ([app] { app->suspended(); });
  134. #else
  135. ignoreUnused (application);
  136. app->suspended();
  137. #endif
  138. }
  139. }
  140. - (void) applicationWillEnterForeground: (UIApplication*) application
  141. {
  142. ignoreUnused (application);
  143. if (auto* app = JUCEApplicationBase::getInstance())
  144. app->resumed();
  145. }
  146. - (void) applicationDidBecomeActive: (UIApplication*) application
  147. {
  148. application.applicationIconBadgeNumber = 0;
  149. isIOSAppActive = true;
  150. }
  151. - (void) applicationWillResignActive: (UIApplication*) application
  152. {
  153. ignoreUnused (application);
  154. isIOSAppActive = false;
  155. for (int i = appBecomingInactiveCallbacks.size(); --i >= 0;)
  156. appBecomingInactiveCallbacks.getReference(i)->appBecomingInactive();
  157. }
  158. - (void) application: (UIApplication*) application handleEventsForBackgroundURLSession: (NSString*)identifier
  159. completionHandler: (void (^)(void))completionHandler
  160. {
  161. ignoreUnused (application);
  162. URL::DownloadTask::juce_iosURLSessionNotify (nsStringToJuce (identifier));
  163. completionHandler();
  164. }
  165. - (void) applicationDidReceiveMemoryWarning: (UIApplication*) application
  166. {
  167. ignoreUnused (application);
  168. if (auto* app = JUCEApplicationBase::getInstance())
  169. app->memoryWarningReceived();
  170. }
  171. - (void) setPushNotificationsDelegateToUse: (NSObject*) delegate
  172. {
  173. _pushNotificationsDelegate = delegate;
  174. }
  175. #if JUCE_PUSH_NOTIFICATIONS
  176. - (void) application: (UIApplication*) application
  177. didRegisterForRemoteNotificationsWithDeviceToken: (NSData*) deviceToken
  178. {
  179. ignoreUnused (application);
  180. SEL selector = @selector (application:didRegisterForRemoteNotificationsWithDeviceToken:);
  181. if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
  182. {
  183. NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
  184. [invocation setSelector: selector];
  185. [invocation setTarget: _pushNotificationsDelegate];
  186. [invocation setArgument: &application atIndex:2];
  187. [invocation setArgument: &deviceToken atIndex:3];
  188. [invocation invoke];
  189. }
  190. }
  191. - (void) application: (UIApplication*) application
  192. didFailToRegisterForRemoteNotificationsWithError: (NSError*) error
  193. {
  194. ignoreUnused (application);
  195. SEL selector = @selector (application:didFailToRegisterForRemoteNotificationsWithError:);
  196. if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
  197. {
  198. NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
  199. [invocation setSelector: selector];
  200. [invocation setTarget: _pushNotificationsDelegate];
  201. [invocation setArgument: &application atIndex:2];
  202. [invocation setArgument: &error atIndex:3];
  203. [invocation invoke];
  204. }
  205. }
  206. - (void) application: (UIApplication*) application
  207. didReceiveRemoteNotification: (NSDictionary*) userInfo
  208. {
  209. ignoreUnused (application);
  210. SEL selector = @selector (application:didReceiveRemoteNotification:);
  211. if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
  212. {
  213. NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
  214. [invocation setSelector: selector];
  215. [invocation setTarget: _pushNotificationsDelegate];
  216. [invocation setArgument: &application atIndex:2];
  217. [invocation setArgument: &userInfo atIndex:3];
  218. [invocation invoke];
  219. }
  220. }
  221. - (void) application: (UIApplication*) application
  222. didReceiveRemoteNotification: (NSDictionary*) userInfo
  223. fetchCompletionHandler: (void (^)(UIBackgroundFetchResult result)) completionHandler
  224. {
  225. ignoreUnused (application);
  226. SEL selector = @selector (application:didReceiveRemoteNotification:fetchCompletionHandler:);
  227. if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
  228. {
  229. NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
  230. [invocation setSelector: selector];
  231. [invocation setTarget: _pushNotificationsDelegate];
  232. [invocation setArgument: &application atIndex:2];
  233. [invocation setArgument: &userInfo atIndex:3];
  234. [invocation setArgument: &completionHandler atIndex:4];
  235. [invocation invoke];
  236. }
  237. }
  238. - (void) application: (UIApplication*) application
  239. handleActionWithIdentifier: (NSString*) identifier
  240. forRemoteNotification: (NSDictionary*) userInfo
  241. withResponseInfo: (NSDictionary*) responseInfo
  242. completionHandler: (void(^)()) completionHandler
  243. {
  244. ignoreUnused (application);
  245. SEL selector = @selector (application:handleActionWithIdentifier:forRemoteNotification:withResponseInfo:completionHandler:);
  246. if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
  247. {
  248. NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
  249. [invocation setSelector: selector];
  250. [invocation setTarget: _pushNotificationsDelegate];
  251. [invocation setArgument: &application atIndex:2];
  252. [invocation setArgument: &identifier atIndex:3];
  253. [invocation setArgument: &userInfo atIndex:4];
  254. [invocation setArgument: &responseInfo atIndex:5];
  255. [invocation setArgument: &completionHandler atIndex:6];
  256. [invocation invoke];
  257. }
  258. }
  259. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  260. - (void) application: (UIApplication*) application
  261. didRegisterUserNotificationSettings: (UIUserNotificationSettings*) notificationSettings
  262. {
  263. ignoreUnused (application);
  264. SEL selector = @selector (application:didRegisterUserNotificationSettings:);
  265. if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector:selector])
  266. {
  267. NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
  268. [invocation setSelector: selector];
  269. [invocation setTarget: _pushNotificationsDelegate];
  270. [invocation setArgument: &application atIndex: 2];
  271. [invocation setArgument: &notificationSettings atIndex: 3];
  272. [invocation invoke];
  273. }
  274. }
  275. - (void) application: (UIApplication*) application
  276. didReceiveLocalNotification: (UILocalNotification*) notification
  277. {
  278. ignoreUnused (application);
  279. SEL selector = @selector (application:didReceiveLocalNotification:);
  280. if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
  281. {
  282. NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
  283. [invocation setSelector: selector];
  284. [invocation setTarget: _pushNotificationsDelegate];
  285. [invocation setArgument: &application atIndex: 2];
  286. [invocation setArgument: &notification atIndex: 3];
  287. [invocation invoke];
  288. }
  289. }
  290. - (void) application: (UIApplication*) application
  291. handleActionWithIdentifier: (NSString*) identifier
  292. forLocalNotification: (UILocalNotification*) notification
  293. completionHandler: (void(^)()) completionHandler
  294. {
  295. ignoreUnused (application);
  296. SEL selector = @selector (application:handleActionWithIdentifier:forLocalNotification:completionHandler:);
  297. if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
  298. {
  299. NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
  300. [invocation setSelector: selector];
  301. [invocation setTarget: _pushNotificationsDelegate];
  302. [invocation setArgument: &application atIndex:2];
  303. [invocation setArgument: &identifier atIndex:3];
  304. [invocation setArgument: &notification atIndex:4];
  305. [invocation setArgument: &completionHandler atIndex:5];
  306. [invocation invoke];
  307. }
  308. }
  309. - (void) application: (UIApplication*) application
  310. handleActionWithIdentifier: (NSString*) identifier
  311. forLocalNotification: (UILocalNotification*) notification
  312. withResponseInfo: (NSDictionary*) responseInfo
  313. completionHandler: (void(^)()) completionHandler
  314. {
  315. ignoreUnused (application);
  316. SEL selector = @selector (application:handleActionWithIdentifier:forLocalNotification:withResponseInfo:completionHandler:);
  317. if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
  318. {
  319. NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
  320. [invocation setSelector: selector];
  321. [invocation setTarget: _pushNotificationsDelegate];
  322. [invocation setArgument: &application atIndex:2];
  323. [invocation setArgument: &identifier atIndex:3];
  324. [invocation setArgument: &notification atIndex:4];
  325. [invocation setArgument: &responseInfo atIndex:5];
  326. [invocation setArgument: &completionHandler atIndex:6];
  327. [invocation invoke];
  328. }
  329. }
  330. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  331. - (void) userNotificationCenter: (UNUserNotificationCenter*) center
  332. willPresentNotification: (UNNotification*) notification
  333. withCompletionHandler: (void (^)(UNNotificationPresentationOptions options)) completionHandler
  334. {
  335. ignoreUnused (center);
  336. SEL selector = @selector (userNotificationCenter:willPresentNotification:withCompletionHandler:);
  337. if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
  338. {
  339. NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
  340. [invocation setSelector: selector];
  341. [invocation setTarget: _pushNotificationsDelegate];
  342. [invocation setArgument: &center atIndex:2];
  343. [invocation setArgument: &notification atIndex:3];
  344. [invocation setArgument: &completionHandler atIndex:4];
  345. [invocation invoke];
  346. }
  347. }
  348. - (void) userNotificationCenter: (UNUserNotificationCenter*) center
  349. didReceiveNotificationResponse: (UNNotificationResponse*) response
  350. withCompletionHandler: (void(^)()) completionHandler
  351. {
  352. ignoreUnused (center);
  353. SEL selector = @selector (userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:);
  354. if (_pushNotificationsDelegate != nil && [_pushNotificationsDelegate respondsToSelector: selector])
  355. {
  356. NSInvocation* invocation = [NSInvocation invocationWithMethodSignature: [_pushNotificationsDelegate methodSignatureForSelector: selector]];
  357. [invocation setSelector: selector];
  358. [invocation setTarget: _pushNotificationsDelegate];
  359. [invocation setArgument: &center atIndex:2];
  360. [invocation setArgument: &response atIndex:3];
  361. [invocation setArgument: &completionHandler atIndex:4];
  362. [invocation invoke];
  363. }
  364. }
  365. #endif
  366. @end
  367. namespace juce
  368. {
  369. int juce_iOSMain (int argc, const char* argv[], void* customDelegatePtr);
  370. int juce_iOSMain (int argc, const char* argv[], void* customDelegatePtr)
  371. {
  372. Class delegateClass = (customDelegatePtr != nullptr ? reinterpret_cast<Class> (customDelegatePtr) : [JuceAppStartupDelegate class]);
  373. return UIApplicationMain (argc, const_cast<char**> (argv), nil, NSStringFromClass (delegateClass));
  374. }
  375. //==============================================================================
  376. void LookAndFeel::playAlertSound()
  377. {
  378. // TODO
  379. }
  380. //==============================================================================
  381. bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray&, bool, Component*, std::function<void()>)
  382. {
  383. jassertfalse; // no such thing on iOS!
  384. return false;
  385. }
  386. bool DragAndDropContainer::performExternalDragDropOfText (const String&, Component*, std::function<void()>)
  387. {
  388. jassertfalse; // no such thing on iOS!
  389. return false;
  390. }
  391. //==============================================================================
  392. void Desktop::setScreenSaverEnabled (const bool isEnabled)
  393. {
  394. if (! SystemStats::isRunningInAppExtensionSandbox())
  395. [[UIApplication sharedApplication] setIdleTimerDisabled: ! isEnabled];
  396. }
  397. bool Desktop::isScreenSaverEnabled()
  398. {
  399. if (SystemStats::isRunningInAppExtensionSandbox())
  400. return true;
  401. return ! [[UIApplication sharedApplication] isIdleTimerDisabled];
  402. }
  403. //==============================================================================
  404. bool detail::WindowingHelpers::areThereAnyAlwaysOnTopWindows()
  405. {
  406. return false;
  407. }
  408. //==============================================================================
  409. Image detail::WindowingHelpers::createIconForFile (const File&)
  410. {
  411. return {};
  412. }
  413. //==============================================================================
  414. void SystemClipboard::copyTextToClipboard (const String& text)
  415. {
  416. [[UIPasteboard generalPasteboard] setValue: juceStringToNS (text)
  417. forPasteboardType: @"public.text"];
  418. }
  419. String SystemClipboard::getTextFromClipboard()
  420. {
  421. return nsStringToJuce ([[UIPasteboard generalPasteboard] string]);
  422. }
  423. //==============================================================================
  424. bool detail::MouseInputSourceList::addSource()
  425. {
  426. addSource (sources.size(), MouseInputSource::InputSourceType::touch);
  427. return true;
  428. }
  429. bool detail::MouseInputSourceList::canUseTouch() const
  430. {
  431. return true;
  432. }
  433. bool Desktop::canUseSemiTransparentWindows() noexcept
  434. {
  435. return true;
  436. }
  437. bool Desktop::isDarkModeActive() const
  438. {
  439. if (@available (iOS 12.0, *))
  440. return [[[UIScreen mainScreen] traitCollection] userInterfaceStyle] == UIUserInterfaceStyleDark;
  441. return false;
  442. }
  443. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  444. static const auto darkModeSelector = @selector (darkModeChanged:);
  445. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  446. class Desktop::NativeDarkModeChangeDetectorImpl
  447. {
  448. public:
  449. NativeDarkModeChangeDetectorImpl()
  450. {
  451. static DelegateClass delegateClass;
  452. delegate.reset ([delegateClass.createInstance() init]);
  453. observer.emplace (delegate.get(), darkModeSelector, UIViewComponentPeer::getDarkModeNotificationName(), nil);
  454. }
  455. private:
  456. struct DelegateClass : public ObjCClass<NSObject>
  457. {
  458. DelegateClass() : ObjCClass<NSObject> ("JUCEDelegate_")
  459. {
  460. addMethod (darkModeSelector, [] (id, SEL, NSNotification*) { Desktop::getInstance().darkModeChanged(); });
  461. registerClass();
  462. }
  463. };
  464. NSUniquePtr<NSObject> delegate;
  465. Optional<ScopedNotificationCenterObserver> observer;
  466. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeDarkModeChangeDetectorImpl)
  467. };
  468. std::unique_ptr<Desktop::NativeDarkModeChangeDetectorImpl> Desktop::createNativeDarkModeChangeDetectorImpl()
  469. {
  470. return std::make_unique<NativeDarkModeChangeDetectorImpl>();
  471. }
  472. //==============================================================================
  473. Point<float> MouseInputSource::getCurrentRawMousePosition()
  474. {
  475. return juce_lastMousePos;
  476. }
  477. void MouseInputSource::setRawMousePosition (Point<float>)
  478. {
  479. }
  480. double Desktop::getDefaultMasterScale()
  481. {
  482. return 1.0;
  483. }
  484. Desktop::DisplayOrientation Desktop::getCurrentOrientation() const
  485. {
  486. UIInterfaceOrientation orientation = SystemStats::isRunningInAppExtensionSandbox() ? UIInterfaceOrientationPortrait
  487. : getWindowOrientation();
  488. return Orientations::convertToJuce (orientation);
  489. }
  490. template <typename Value>
  491. static BorderSize<Value> operator/ (BorderSize<Value> border, Value scale)
  492. {
  493. return { border.getTop() / scale,
  494. border.getLeft() / scale,
  495. border.getBottom() / scale,
  496. border.getRight() / scale };
  497. }
  498. template <typename Value>
  499. static BorderSize<int> roundToInt (BorderSize<Value> border)
  500. {
  501. return { roundToInt (border.getTop()),
  502. roundToInt (border.getLeft()),
  503. roundToInt (border.getBottom()),
  504. roundToInt (border.getRight()) };
  505. }
  506. // The most straightforward way of retrieving the screen area available to an iOS app
  507. // seems to be to create a new window (which will take up all available space) and to
  508. // query its frame.
  509. struct TemporaryWindow
  510. {
  511. UIWindow* window = [[UIWindow alloc] init];
  512. ~TemporaryWindow() noexcept { [window release]; }
  513. };
  514. static Rectangle<int> getRecommendedWindowBounds()
  515. {
  516. return convertToRectInt (TemporaryWindow().window.frame);
  517. }
  518. static BorderSize<int> getSafeAreaInsets (float masterScale)
  519. {
  520. if (@available (iOS 11.0, *))
  521. {
  522. UIEdgeInsets safeInsets = TemporaryWindow().window.safeAreaInsets;
  523. return roundToInt (BorderSize<double> { safeInsets.top,
  524. safeInsets.left,
  525. safeInsets.bottom,
  526. safeInsets.right } / (double) masterScale);
  527. }
  528. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  529. auto statusBarSize = [UIApplication sharedApplication].statusBarFrame.size;
  530. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  531. auto statusBarHeight = jmin (statusBarSize.width, statusBarSize.height);
  532. return { roundToInt (statusBarHeight / masterScale), 0, 0, 0 };
  533. }
  534. //==============================================================================
  535. void Displays::findDisplays (float masterScale)
  536. {
  537. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  538. static const auto keyboardShownSelector = @selector (juceKeyboardShown:);
  539. static const auto keyboardHiddenSelector = @selector (juceKeyboardHidden:);
  540. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  541. class OnScreenKeyboardChangeDetectorImpl
  542. {
  543. public:
  544. OnScreenKeyboardChangeDetectorImpl()
  545. {
  546. static DelegateClass delegateClass;
  547. delegate.reset ([delegateClass.createInstance() init]);
  548. object_setInstanceVariable (delegate.get(), "owner", this);
  549. observers.emplace_back (delegate.get(), keyboardShownSelector, UIKeyboardDidShowNotification, nil);
  550. observers.emplace_back (delegate.get(), keyboardHiddenSelector, UIKeyboardDidHideNotification, nil);
  551. }
  552. auto getInsets() const { return insets; }
  553. private:
  554. struct DelegateClass : public ObjCClass<NSObject>
  555. {
  556. DelegateClass() : ObjCClass<NSObject> ("JUCEOnScreenKeyboardObserver_")
  557. {
  558. addIvar<OnScreenKeyboardChangeDetectorImpl*> ("owner");
  559. addMethod (keyboardShownSelector, [] (id self, SEL, NSNotification* notification)
  560. {
  561. setKeyboardScreenBounds (self, [&]() -> BorderSize<double>
  562. {
  563. auto* info = [notification userInfo];
  564. if (info == nullptr)
  565. return {};
  566. auto* value = static_cast<NSValue*> ([info objectForKey: UIKeyboardFrameEndUserInfoKey]);
  567. if (value == nullptr)
  568. return {};
  569. auto* display = Desktop::getInstance().getDisplays().getPrimaryDisplay();
  570. if (display == nullptr)
  571. return {};
  572. const auto rect = convertToRectInt ([value CGRectValue]);
  573. BorderSize<double> result;
  574. if (rect.getY() == display->totalArea.getY())
  575. result.setTop (rect.getHeight());
  576. if (rect.getBottom() == display->totalArea.getBottom())
  577. result.setBottom (rect.getHeight());
  578. return result;
  579. }());
  580. });
  581. addMethod (keyboardHiddenSelector, [] (id self, SEL, NSNotification*)
  582. {
  583. setKeyboardScreenBounds (self, {});
  584. });
  585. registerClass();
  586. }
  587. private:
  588. static void setKeyboardScreenBounds (id self, BorderSize<double> insets)
  589. {
  590. if (std::exchange (getIvar<OnScreenKeyboardChangeDetectorImpl*> (self, "owner")->insets, insets) != insets)
  591. Desktop::getInstance().displays->refresh();
  592. }
  593. };
  594. BorderSize<double> insets;
  595. NSUniquePtr<NSObject> delegate;
  596. std::vector<ScopedNotificationCenterObserver> observers;
  597. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OnScreenKeyboardChangeDetectorImpl)
  598. };
  599. JUCE_AUTORELEASEPOOL
  600. {
  601. static OnScreenKeyboardChangeDetectorImpl keyboardChangeDetector;
  602. UIScreen* s = [UIScreen mainScreen];
  603. Display d;
  604. d.totalArea = convertToRectInt ([s bounds]) / masterScale;
  605. d.userArea = getRecommendedWindowBounds() / masterScale;
  606. d.safeAreaInsets = getSafeAreaInsets (masterScale);
  607. d.keyboardInsets = roundToInt (keyboardChangeDetector.getInsets() / (double) masterScale);
  608. d.isMain = true;
  609. d.scale = masterScale * s.scale;
  610. d.dpi = 160 * d.scale;
  611. displays.add (d);
  612. }
  613. }
  614. } // namespace juce