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.

759 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. Image detail::WindowingHelpers::createIconForFile (const File&)
  405. {
  406. return {};
  407. }
  408. //==============================================================================
  409. void SystemClipboard::copyTextToClipboard (const String& text)
  410. {
  411. [[UIPasteboard generalPasteboard] setValue: juceStringToNS (text)
  412. forPasteboardType: @"public.text"];
  413. }
  414. String SystemClipboard::getTextFromClipboard()
  415. {
  416. return nsStringToJuce ([[UIPasteboard generalPasteboard] string]);
  417. }
  418. //==============================================================================
  419. bool detail::MouseInputSourceList::addSource()
  420. {
  421. addSource (sources.size(), MouseInputSource::InputSourceType::touch);
  422. return true;
  423. }
  424. bool detail::MouseInputSourceList::canUseTouch() const
  425. {
  426. return true;
  427. }
  428. bool Desktop::canUseSemiTransparentWindows() noexcept
  429. {
  430. return true;
  431. }
  432. bool Desktop::isDarkModeActive() const
  433. {
  434. if (@available (iOS 12.0, *))
  435. return [[[UIScreen mainScreen] traitCollection] userInterfaceStyle] == UIUserInterfaceStyleDark;
  436. return false;
  437. }
  438. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  439. static const auto darkModeSelector = @selector (darkModeChanged:);
  440. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  441. class Desktop::NativeDarkModeChangeDetectorImpl
  442. {
  443. public:
  444. NativeDarkModeChangeDetectorImpl()
  445. {
  446. static DelegateClass delegateClass;
  447. delegate.reset ([delegateClass.createInstance() init]);
  448. observer.emplace (delegate.get(), darkModeSelector, UIViewComponentPeer::getDarkModeNotificationName(), nil);
  449. }
  450. private:
  451. struct DelegateClass : public ObjCClass<NSObject>
  452. {
  453. DelegateClass() : ObjCClass<NSObject> ("JUCEDelegate_")
  454. {
  455. addMethod (darkModeSelector, [] (id, SEL, NSNotification*) { Desktop::getInstance().darkModeChanged(); });
  456. registerClass();
  457. }
  458. };
  459. NSUniquePtr<NSObject> delegate;
  460. Optional<ScopedNotificationCenterObserver> observer;
  461. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeDarkModeChangeDetectorImpl)
  462. };
  463. std::unique_ptr<Desktop::NativeDarkModeChangeDetectorImpl> Desktop::createNativeDarkModeChangeDetectorImpl()
  464. {
  465. return std::make_unique<NativeDarkModeChangeDetectorImpl>();
  466. }
  467. //==============================================================================
  468. Point<float> MouseInputSource::getCurrentRawMousePosition()
  469. {
  470. return juce_lastMousePos;
  471. }
  472. void MouseInputSource::setRawMousePosition (Point<float>)
  473. {
  474. }
  475. double Desktop::getDefaultMasterScale()
  476. {
  477. return 1.0;
  478. }
  479. Desktop::DisplayOrientation Desktop::getCurrentOrientation() const
  480. {
  481. UIInterfaceOrientation orientation = SystemStats::isRunningInAppExtensionSandbox() ? UIInterfaceOrientationPortrait
  482. : getWindowOrientation();
  483. return Orientations::convertToJuce (orientation);
  484. }
  485. template <typename Value>
  486. static BorderSize<Value> operator/ (BorderSize<Value> border, Value scale)
  487. {
  488. return { border.getTop() / scale,
  489. border.getLeft() / scale,
  490. border.getBottom() / scale,
  491. border.getRight() / scale };
  492. }
  493. template <typename Value>
  494. static BorderSize<int> roundToInt (BorderSize<Value> border)
  495. {
  496. return { roundToInt (border.getTop()),
  497. roundToInt (border.getLeft()),
  498. roundToInt (border.getBottom()),
  499. roundToInt (border.getRight()) };
  500. }
  501. // The most straightforward way of retrieving the screen area available to an iOS app
  502. // seems to be to create a new window (which will take up all available space) and to
  503. // query its frame.
  504. struct TemporaryWindow
  505. {
  506. UIWindow* window = [[UIWindow alloc] init];
  507. ~TemporaryWindow() noexcept { [window release]; }
  508. };
  509. static Rectangle<int> getRecommendedWindowBounds()
  510. {
  511. return convertToRectInt (TemporaryWindow().window.frame);
  512. }
  513. static BorderSize<int> getSafeAreaInsets (float masterScale)
  514. {
  515. if (@available (iOS 11.0, *))
  516. {
  517. UIEdgeInsets safeInsets = TemporaryWindow().window.safeAreaInsets;
  518. return roundToInt (BorderSize<double> { safeInsets.top,
  519. safeInsets.left,
  520. safeInsets.bottom,
  521. safeInsets.right } / (double) masterScale);
  522. }
  523. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  524. auto statusBarSize = [UIApplication sharedApplication].statusBarFrame.size;
  525. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  526. auto statusBarHeight = jmin (statusBarSize.width, statusBarSize.height);
  527. return { roundToInt (statusBarHeight / masterScale), 0, 0, 0 };
  528. }
  529. //==============================================================================
  530. void Displays::findDisplays (float masterScale)
  531. {
  532. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  533. static const auto keyboardShownSelector = @selector (juceKeyboardShown:);
  534. static const auto keyboardHiddenSelector = @selector (juceKeyboardHidden:);
  535. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  536. class OnScreenKeyboardChangeDetectorImpl
  537. {
  538. public:
  539. OnScreenKeyboardChangeDetectorImpl()
  540. {
  541. static DelegateClass delegateClass;
  542. delegate.reset ([delegateClass.createInstance() init]);
  543. object_setInstanceVariable (delegate.get(), "owner", this);
  544. observers.emplace_back (delegate.get(), keyboardShownSelector, UIKeyboardDidShowNotification, nil);
  545. observers.emplace_back (delegate.get(), keyboardHiddenSelector, UIKeyboardDidHideNotification, nil);
  546. }
  547. auto getInsets() const { return insets; }
  548. private:
  549. struct DelegateClass : public ObjCClass<NSObject>
  550. {
  551. DelegateClass() : ObjCClass<NSObject> ("JUCEOnScreenKeyboardObserver_")
  552. {
  553. addIvar<OnScreenKeyboardChangeDetectorImpl*> ("owner");
  554. addMethod (keyboardShownSelector, [] (id self, SEL, NSNotification* notification)
  555. {
  556. setKeyboardScreenBounds (self, [&]() -> BorderSize<double>
  557. {
  558. auto* info = [notification userInfo];
  559. if (info == nullptr)
  560. return {};
  561. auto* value = static_cast<NSValue*> ([info objectForKey: UIKeyboardFrameEndUserInfoKey]);
  562. if (value == nullptr)
  563. return {};
  564. auto* display = Desktop::getInstance().getDisplays().getPrimaryDisplay();
  565. if (display == nullptr)
  566. return {};
  567. const auto rect = convertToRectInt ([value CGRectValue]);
  568. BorderSize<double> result;
  569. if (rect.getY() == display->totalArea.getY())
  570. result.setTop (rect.getHeight());
  571. if (rect.getBottom() == display->totalArea.getBottom())
  572. result.setBottom (rect.getHeight());
  573. return result;
  574. }());
  575. });
  576. addMethod (keyboardHiddenSelector, [] (id self, SEL, NSNotification*)
  577. {
  578. setKeyboardScreenBounds (self, {});
  579. });
  580. registerClass();
  581. }
  582. private:
  583. static void setKeyboardScreenBounds (id self, BorderSize<double> insets)
  584. {
  585. if (std::exchange (getIvar<OnScreenKeyboardChangeDetectorImpl*> (self, "owner")->insets, insets) != insets)
  586. Desktop::getInstance().displays->refresh();
  587. }
  588. };
  589. BorderSize<double> insets;
  590. NSUniquePtr<NSObject> delegate;
  591. std::vector<ScopedNotificationCenterObserver> observers;
  592. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OnScreenKeyboardChangeDetectorImpl)
  593. };
  594. JUCE_AUTORELEASEPOOL
  595. {
  596. static OnScreenKeyboardChangeDetectorImpl keyboardChangeDetector;
  597. UIScreen* s = [UIScreen mainScreen];
  598. Display d;
  599. d.totalArea = convertToRectInt ([s bounds]) / masterScale;
  600. d.userArea = getRecommendedWindowBounds() / masterScale;
  601. d.safeAreaInsets = getSafeAreaInsets (masterScale);
  602. d.keyboardInsets = roundToInt (keyboardChangeDetector.getInsets() / (double) masterScale);
  603. d.isMain = true;
  604. d.scale = masterScale * s.scale;
  605. d.dpi = 160 * d.scale;
  606. displays.add (d);
  607. }
  608. }
  609. } // namespace juce