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.

956 lines
36KB

  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. }
  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. class iOSMessageBox
  382. {
  383. public:
  384. iOSMessageBox (const MessageBoxOptions& opts,
  385. std::unique_ptr<ModalComponentManager::Callback>&& cb,
  386. bool deleteOnCompletion)
  387. : callback (std::move (cb)),
  388. shouldDeleteThis (deleteOnCompletion)
  389. {
  390. if (iOSGlobals::currentlyFocusedPeer != nullptr)
  391. {
  392. UIAlertController* alert = [UIAlertController alertControllerWithTitle: juceStringToNS (opts.getTitle())
  393. message: juceStringToNS (opts.getMessage())
  394. preferredStyle: UIAlertControllerStyleAlert];
  395. addButton (alert, opts.getButtonText (0));
  396. addButton (alert, opts.getButtonText (1));
  397. addButton (alert, opts.getButtonText (2));
  398. [iOSGlobals::currentlyFocusedPeer->controller presentViewController: alert
  399. animated: YES
  400. completion: nil];
  401. }
  402. else
  403. {
  404. // Since iOS8, alert windows need to be associated with a window, so you need to
  405. // have at least one window on screen when you use this
  406. jassertfalse;
  407. }
  408. }
  409. int getResult()
  410. {
  411. jassert (callback == nullptr);
  412. JUCE_AUTORELEASEPOOL
  413. {
  414. while (result < 0)
  415. [[NSRunLoop mainRunLoop] runUntilDate: [NSDate dateWithTimeIntervalSinceNow: 0.01]];
  416. }
  417. return result;
  418. }
  419. void buttonClicked (int buttonIndex) noexcept
  420. {
  421. result = buttonIndex;
  422. if (callback != nullptr)
  423. callback->modalStateFinished (result);
  424. if (shouldDeleteThis)
  425. delete this;
  426. }
  427. private:
  428. void addButton (UIAlertController* alert, const String& text)
  429. {
  430. if (! text.isEmpty())
  431. {
  432. const auto index = [[alert actions] count];
  433. [alert addAction: [UIAlertAction actionWithTitle: juceStringToNS (text)
  434. style: UIAlertActionStyleDefault
  435. handler: ^(UIAlertAction*) { this->buttonClicked ((int) index); }]];
  436. }
  437. }
  438. int result = -1;
  439. std::unique_ptr<ModalComponentManager::Callback> callback;
  440. const bool shouldDeleteThis;
  441. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (iOSMessageBox)
  442. };
  443. //==============================================================================
  444. static int showDialog (const MessageBoxOptions& options,
  445. ModalComponentManager::Callback* callbackIn,
  446. AlertWindowMappings::MapFn mapFn)
  447. {
  448. #if JUCE_MODAL_LOOPS_PERMITTED
  449. if (callbackIn == nullptr)
  450. {
  451. JUCE_AUTORELEASEPOOL
  452. {
  453. jassert (mapFn != nullptr);
  454. iOSMessageBox messageBox (options, nullptr, false);
  455. return mapFn (messageBox.getResult());
  456. }
  457. }
  458. #endif
  459. const auto showBox = [options, callbackIn, mapFn]
  460. {
  461. new iOSMessageBox (options,
  462. AlertWindowMappings::getWrappedCallback (callbackIn, mapFn),
  463. true);
  464. };
  465. if (MessageManager::getInstance()->isThisTheMessageThread())
  466. showBox();
  467. else
  468. MessageManager::callAsync (showBox);
  469. return 0;
  470. }
  471. #if JUCE_MODAL_LOOPS_PERMITTED
  472. void JUCE_CALLTYPE NativeMessageBox::showMessageBox (MessageBoxIconType /*iconType*/,
  473. const String& title, const String& message,
  474. Component* /*associatedComponent*/)
  475. {
  476. showDialog (MessageBoxOptions()
  477. .withTitle (title)
  478. .withMessage (message)
  479. .withButton (TRANS("OK")),
  480. nullptr, AlertWindowMappings::messageBox);
  481. }
  482. int JUCE_CALLTYPE NativeMessageBox::show (const MessageBoxOptions& options)
  483. {
  484. return showDialog (options, nullptr, AlertWindowMappings::noMapping);
  485. }
  486. #endif
  487. void JUCE_CALLTYPE NativeMessageBox::showMessageBoxAsync (MessageBoxIconType /*iconType*/,
  488. const String& title, const String& message,
  489. Component* /*associatedComponent*/,
  490. ModalComponentManager::Callback* callback)
  491. {
  492. showDialog (MessageBoxOptions()
  493. .withTitle (title)
  494. .withMessage (message)
  495. .withButton (TRANS("OK")),
  496. callback, AlertWindowMappings::messageBox);
  497. }
  498. bool JUCE_CALLTYPE NativeMessageBox::showOkCancelBox (MessageBoxIconType /*iconType*/,
  499. const String& title, const String& message,
  500. Component* /*associatedComponent*/,
  501. ModalComponentManager::Callback* callback)
  502. {
  503. return showDialog (MessageBoxOptions()
  504. .withTitle (title)
  505. .withMessage (message)
  506. .withButton (TRANS("OK"))
  507. .withButton (TRANS("Cancel")),
  508. callback, AlertWindowMappings::okCancel) != 0;
  509. }
  510. int JUCE_CALLTYPE NativeMessageBox::showYesNoCancelBox (MessageBoxIconType /*iconType*/,
  511. const String& title, const String& message,
  512. Component* /*associatedComponent*/,
  513. ModalComponentManager::Callback* callback)
  514. {
  515. return showDialog (MessageBoxOptions()
  516. .withTitle (title)
  517. .withMessage (message)
  518. .withButton (TRANS("Yes"))
  519. .withButton (TRANS("No"))
  520. .withButton (TRANS("Cancel")),
  521. callback, AlertWindowMappings::yesNoCancel);
  522. }
  523. int JUCE_CALLTYPE NativeMessageBox::showYesNoBox (MessageBoxIconType /*iconType*/,
  524. const String& title, const String& message,
  525. Component* /*associatedComponent*/,
  526. ModalComponentManager::Callback* callback)
  527. {
  528. return showDialog (MessageBoxOptions()
  529. .withTitle (title)
  530. .withMessage (message)
  531. .withButton (TRANS("Yes"))
  532. .withButton (TRANS("No")),
  533. callback, AlertWindowMappings::okCancel);
  534. }
  535. void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options,
  536. ModalComponentManager::Callback* callback)
  537. {
  538. showDialog (options, callback, AlertWindowMappings::noMapping);
  539. }
  540. void JUCE_CALLTYPE NativeMessageBox::showAsync (const MessageBoxOptions& options,
  541. std::function<void (int)> callback)
  542. {
  543. showAsync (options, ModalCallbackFunction::create (callback));
  544. }
  545. //==============================================================================
  546. bool DragAndDropContainer::performExternalDragDropOfFiles (const StringArray&, bool, Component*, std::function<void()>)
  547. {
  548. jassertfalse; // no such thing on iOS!
  549. return false;
  550. }
  551. bool DragAndDropContainer::performExternalDragDropOfText (const String&, Component*, std::function<void()>)
  552. {
  553. jassertfalse; // no such thing on iOS!
  554. return false;
  555. }
  556. //==============================================================================
  557. void Desktop::setScreenSaverEnabled (const bool isEnabled)
  558. {
  559. if (! SystemStats::isRunningInAppExtensionSandbox())
  560. [[UIApplication sharedApplication] setIdleTimerDisabled: ! isEnabled];
  561. }
  562. bool Desktop::isScreenSaverEnabled()
  563. {
  564. if (SystemStats::isRunningInAppExtensionSandbox())
  565. return true;
  566. return ! [[UIApplication sharedApplication] isIdleTimerDisabled];
  567. }
  568. //==============================================================================
  569. bool juce_areThereAnyAlwaysOnTopWindows()
  570. {
  571. return false;
  572. }
  573. //==============================================================================
  574. Image juce_createIconForFile (const File&)
  575. {
  576. return Image();
  577. }
  578. //==============================================================================
  579. void SystemClipboard::copyTextToClipboard (const String& text)
  580. {
  581. [[UIPasteboard generalPasteboard] setValue: juceStringToNS (text)
  582. forPasteboardType: @"public.text"];
  583. }
  584. String SystemClipboard::getTextFromClipboard()
  585. {
  586. return nsStringToJuce ([[UIPasteboard generalPasteboard] string]);
  587. }
  588. //==============================================================================
  589. bool MouseInputSource::SourceList::addSource()
  590. {
  591. addSource (sources.size(), MouseInputSource::InputSourceType::touch);
  592. return true;
  593. }
  594. bool MouseInputSource::SourceList::canUseTouch()
  595. {
  596. return true;
  597. }
  598. bool Desktop::canUseSemiTransparentWindows() noexcept
  599. {
  600. return true;
  601. }
  602. bool Desktop::isDarkModeActive() const
  603. {
  604. if (@available (iOS 12.0, *))
  605. return [[[UIScreen mainScreen] traitCollection] userInterfaceStyle] == UIUserInterfaceStyleDark;
  606. return false;
  607. }
  608. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  609. static const auto darkModeSelector = @selector (darkModeChanged:);
  610. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  611. class Desktop::NativeDarkModeChangeDetectorImpl
  612. {
  613. public:
  614. NativeDarkModeChangeDetectorImpl()
  615. {
  616. static DelegateClass delegateClass;
  617. delegate.reset ([delegateClass.createInstance() init]);
  618. observer.emplace (delegate.get(), darkModeSelector, UIViewComponentPeer::getDarkModeNotificationName(), nil);
  619. }
  620. private:
  621. struct DelegateClass : public ObjCClass<NSObject>
  622. {
  623. DelegateClass() : ObjCClass<NSObject> ("JUCEDelegate_")
  624. {
  625. addMethod (darkModeSelector, [] (id, SEL, NSNotification*) { Desktop::getInstance().darkModeChanged(); });
  626. registerClass();
  627. }
  628. };
  629. NSUniquePtr<NSObject> delegate;
  630. Optional<ScopedNotificationCenterObserver> observer;
  631. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (NativeDarkModeChangeDetectorImpl)
  632. };
  633. std::unique_ptr<Desktop::NativeDarkModeChangeDetectorImpl> Desktop::createNativeDarkModeChangeDetectorImpl()
  634. {
  635. return std::make_unique<NativeDarkModeChangeDetectorImpl>();
  636. }
  637. //==============================================================================
  638. Point<float> MouseInputSource::getCurrentRawMousePosition()
  639. {
  640. return juce_lastMousePos;
  641. }
  642. void MouseInputSource::setRawMousePosition (Point<float>)
  643. {
  644. }
  645. double Desktop::getDefaultMasterScale()
  646. {
  647. return 1.0;
  648. }
  649. Desktop::DisplayOrientation Desktop::getCurrentOrientation() const
  650. {
  651. UIInterfaceOrientation orientation = SystemStats::isRunningInAppExtensionSandbox() ? UIInterfaceOrientationPortrait
  652. : getWindowOrientation();
  653. return Orientations::convertToJuce (orientation);
  654. }
  655. template <typename Value>
  656. static BorderSize<Value> operator/ (BorderSize<Value> border, Value scale)
  657. {
  658. return { border.getTop() / scale,
  659. border.getLeft() / scale,
  660. border.getBottom() / scale,
  661. border.getRight() / scale };
  662. }
  663. template <typename Value>
  664. static BorderSize<int> roundToInt (BorderSize<Value> border)
  665. {
  666. return { roundToInt (border.getTop()),
  667. roundToInt (border.getLeft()),
  668. roundToInt (border.getBottom()),
  669. roundToInt (border.getRight()) };
  670. }
  671. // The most straightforward way of retrieving the screen area available to an iOS app
  672. // seems to be to create a new window (which will take up all available space) and to
  673. // query its frame.
  674. struct TemporaryWindow
  675. {
  676. UIWindow* window = [[UIWindow alloc] init];
  677. ~TemporaryWindow() noexcept { [window release]; }
  678. };
  679. static Rectangle<int> getRecommendedWindowBounds()
  680. {
  681. return convertToRectInt (TemporaryWindow().window.frame);
  682. }
  683. static BorderSize<int> getSafeAreaInsets (float masterScale)
  684. {
  685. if (@available (iOS 11.0, *))
  686. {
  687. UIEdgeInsets safeInsets = TemporaryWindow().window.safeAreaInsets;
  688. return roundToInt (BorderSize<double> { safeInsets.top,
  689. safeInsets.left,
  690. safeInsets.bottom,
  691. safeInsets.right } / (double) masterScale);
  692. }
  693. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  694. auto statusBarSize = [UIApplication sharedApplication].statusBarFrame.size;
  695. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  696. auto statusBarHeight = jmin (statusBarSize.width, statusBarSize.height);
  697. return { roundToInt (statusBarHeight / masterScale), 0, 0, 0 };
  698. }
  699. //==============================================================================
  700. void Displays::findDisplays (float masterScale)
  701. {
  702. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  703. static const auto keyboardShownSelector = @selector (keyboardShown:);
  704. static const auto keyboardHiddenSelector = @selector (keyboardHidden:);
  705. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  706. class OnScreenKeyboardChangeDetectorImpl
  707. {
  708. public:
  709. OnScreenKeyboardChangeDetectorImpl()
  710. {
  711. static DelegateClass delegateClass;
  712. delegate.reset ([delegateClass.createInstance() init]);
  713. object_setInstanceVariable (delegate.get(), "owner", this);
  714. observers.emplace_back (delegate.get(), keyboardShownSelector, UIKeyboardDidShowNotification, nil);
  715. observers.emplace_back (delegate.get(), keyboardHiddenSelector, UIKeyboardDidHideNotification, nil);
  716. }
  717. auto getInsets() const { return insets; }
  718. private:
  719. struct DelegateClass : public ObjCClass<NSObject>
  720. {
  721. DelegateClass() : ObjCClass<NSObject> ("JUCEOnScreenKeyboardObserver_")
  722. {
  723. addIvar<OnScreenKeyboardChangeDetectorImpl*> ("owner");
  724. addMethod (keyboardShownSelector, [] (id self, SEL, NSNotification* notification)
  725. {
  726. setKeyboardScreenBounds (self, [&]() -> BorderSize<double>
  727. {
  728. auto* info = [notification userInfo];
  729. if (info == nullptr)
  730. return {};
  731. auto* value = static_cast<NSValue*> ([info objectForKey: UIKeyboardFrameEndUserInfoKey]);
  732. if (value == nullptr)
  733. return {};
  734. auto* display = getPrimaryDisplayImpl (Desktop::getInstance().getDisplays());
  735. if (display == nullptr)
  736. return {};
  737. const auto rect = convertToRectInt ([value CGRectValue]);
  738. BorderSize<double> result;
  739. if (rect.getY() == display->totalArea.getY())
  740. result.setTop (rect.getHeight());
  741. if (rect.getBottom() == display->totalArea.getBottom())
  742. result.setBottom (rect.getHeight());
  743. return result;
  744. }());
  745. });
  746. addMethod (keyboardHiddenSelector, [] (id self, SEL, NSNotification*)
  747. {
  748. setKeyboardScreenBounds (self, {});
  749. });
  750. registerClass();
  751. }
  752. private:
  753. static void setKeyboardScreenBounds (id self, BorderSize<double> insets)
  754. {
  755. if (std::exchange (getIvar<OnScreenKeyboardChangeDetectorImpl*> (self, "owner")->insets, insets) != insets)
  756. Desktop::getInstance().displays->refresh();
  757. }
  758. };
  759. BorderSize<double> insets;
  760. NSUniquePtr<NSObject> delegate;
  761. std::vector<ScopedNotificationCenterObserver> observers;
  762. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (OnScreenKeyboardChangeDetectorImpl)
  763. };
  764. JUCE_AUTORELEASEPOOL
  765. {
  766. static OnScreenKeyboardChangeDetectorImpl keyboardChangeDetector;
  767. UIScreen* s = [UIScreen mainScreen];
  768. Display d;
  769. d.totalArea = convertToRectInt ([s bounds]) / masterScale;
  770. d.userArea = getRecommendedWindowBounds() / masterScale;
  771. d.safeAreaInsets = getSafeAreaInsets (masterScale);
  772. d.keyboardInsets = roundToInt (keyboardChangeDetector.getInsets() / (double) masterScale);
  773. d.isMain = true;
  774. d.scale = masterScale * s.scale;
  775. d.dpi = 160 * d.scale;
  776. displays.add (d);
  777. }
  778. }
  779. } // namespace juce