Audio plugin host https://kx.studio/carla
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.

562 lines
23KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 7 technical preview.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. You may use this code under the terms of the GPL v3
  6. (see www.gnu.org/licenses).
  7. For the technical preview this file cannot be licensed commercially.
  8. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  9. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  10. DISCLAIMED.
  11. ==============================================================================
  12. */
  13. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations")
  14. namespace juce
  15. {
  16. namespace PushNotificationsDelegateDetailsOsx
  17. {
  18. using Action = PushNotifications::Notification::Action;
  19. //==============================================================================
  20. NSUserNotification* juceNotificationToNSUserNotification (const PushNotifications::Notification& n,
  21. bool isEarlierThanMavericks,
  22. bool isEarlierThanYosemite)
  23. {
  24. auto notification = [[NSUserNotification alloc] init];
  25. notification.title = juceStringToNS (n.title);
  26. notification.subtitle = juceStringToNS (n.subtitle);
  27. notification.informativeText = juceStringToNS (n.body);
  28. notification.userInfo = varObjectToNSDictionary (n.properties);
  29. auto triggerTime = Time::getCurrentTime() + RelativeTime (n.triggerIntervalSec);
  30. notification.deliveryDate = [NSDate dateWithTimeIntervalSince1970: triggerTime.toMilliseconds() / 1000.];
  31. if (n.repeat && n.triggerIntervalSec >= 60)
  32. {
  33. auto dateComponents = [[NSDateComponents alloc] init];
  34. auto intervalSec = NSInteger (n.triggerIntervalSec);
  35. dateComponents.second = intervalSec;
  36. dateComponents.nanosecond = NSInteger ((n.triggerIntervalSec - intervalSec) * 1000000000);
  37. notification.deliveryRepeatInterval = dateComponents;
  38. [dateComponents autorelease];
  39. }
  40. auto soundToPlayString = n.soundToPlay.toString (true);
  41. if (soundToPlayString == "default_os_sound")
  42. {
  43. notification.soundName = NSUserNotificationDefaultSoundName;
  44. }
  45. else if (soundToPlayString.isNotEmpty())
  46. {
  47. auto* soundName = juceStringToNS (soundToPlayString.fromLastOccurrenceOf ("/", false, false)
  48. .upToLastOccurrenceOf (".", false, false));
  49. notification.soundName = soundName;
  50. }
  51. notification.hasActionButton = n.actions.size() > 0;
  52. if (n.actions.size() > 0)
  53. notification.actionButtonTitle = juceStringToNS (n.actions.getReference (0).title);
  54. if (! isEarlierThanMavericks)
  55. {
  56. notification.identifier = juceStringToNS (n.identifier);
  57. if (n.actions.size() > 0)
  58. {
  59. notification.hasReplyButton = n.actions.getReference (0).style == Action::text;
  60. notification.responsePlaceholder = juceStringToNS (n.actions.getReference (0).textInputPlaceholder);
  61. }
  62. auto* imageDirectory = n.icon.contains ("/")
  63. ? juceStringToNS (n.icon.upToLastOccurrenceOf ("/", false, true))
  64. : [NSString string];
  65. auto* imageName = juceStringToNS (n.icon.fromLastOccurrenceOf ("/", false, false)
  66. .upToLastOccurrenceOf (".", false, false));
  67. auto* imageExtension = juceStringToNS (n.icon.fromLastOccurrenceOf (".", false, false));
  68. NSString* imagePath = nil;
  69. if ([imageDirectory length] == NSUInteger (0))
  70. {
  71. imagePath = [[NSBundle mainBundle] pathForResource: imageName
  72. ofType: imageExtension];
  73. }
  74. else
  75. {
  76. imagePath = [[NSBundle mainBundle] pathForResource: imageName
  77. ofType: imageExtension
  78. inDirectory: imageDirectory];
  79. }
  80. notification.contentImage = [[NSImage alloc] initWithContentsOfFile: imagePath];
  81. if (! isEarlierThanYosemite)
  82. {
  83. if (n.actions.size() > 1)
  84. {
  85. auto additionalActions = [NSMutableArray arrayWithCapacity: (NSUInteger) n.actions.size() - 1];
  86. for (int a = 1; a < n.actions.size(); ++a)
  87. [additionalActions addObject: [NSUserNotificationAction actionWithIdentifier: juceStringToNS (n.actions[a].identifier)
  88. title: juceStringToNS (n.actions[a].title)]];
  89. notification.additionalActions = additionalActions;
  90. }
  91. }
  92. }
  93. [notification autorelease];
  94. return notification;
  95. }
  96. //==============================================================================
  97. PushNotifications::Notification nsUserNotificationToJuceNotification (NSUserNotification* n,
  98. bool isEarlierThanMavericks,
  99. bool isEarlierThanYosemite)
  100. {
  101. PushNotifications::Notification notif;
  102. notif.title = nsStringToJuce (n.title);
  103. notif.subtitle = nsStringToJuce (n.subtitle);
  104. notif.body = nsStringToJuce (n.informativeText);
  105. notif.repeat = n.deliveryRepeatInterval != nil;
  106. if (n.deliveryRepeatInterval != nil)
  107. {
  108. notif.triggerIntervalSec = n.deliveryRepeatInterval.second + (n.deliveryRepeatInterval.nanosecond / 1000000000.);
  109. }
  110. else
  111. {
  112. NSDate* dateNow = [NSDate date];
  113. NSDate* deliveryDate = n.deliveryDate;
  114. notif.triggerIntervalSec = [dateNow timeIntervalSinceDate: deliveryDate];
  115. }
  116. notif.soundToPlay = URL (nsStringToJuce (n.soundName));
  117. notif.properties = nsDictionaryToVar (n.userInfo);
  118. if (! isEarlierThanMavericks)
  119. {
  120. notif.identifier = nsStringToJuce (n.identifier);
  121. if (n.contentImage != nil)
  122. notif.icon = nsStringToJuce ([n.contentImage name]);
  123. }
  124. Array<Action> actions;
  125. if (n.actionButtonTitle != nil)
  126. {
  127. Action action;
  128. action.title = nsStringToJuce (n.actionButtonTitle);
  129. if (! isEarlierThanMavericks)
  130. {
  131. if (n.hasReplyButton)
  132. action.style = Action::text;
  133. if (n.responsePlaceholder != nil)
  134. action.textInputPlaceholder = nsStringToJuce (n.responsePlaceholder);
  135. }
  136. actions.add (action);
  137. }
  138. if (! isEarlierThanYosemite)
  139. {
  140. if (n.additionalActions != nil)
  141. {
  142. for (NSUserNotificationAction* a in n.additionalActions)
  143. {
  144. Action action;
  145. action.identifier = nsStringToJuce (a.identifier);
  146. action.title = nsStringToJuce (a.title);
  147. actions.add (action);
  148. }
  149. }
  150. }
  151. return notif;
  152. }
  153. //==============================================================================
  154. var getNotificationPropertiesFromDictionaryVar (const var& dictionaryVar)
  155. {
  156. auto* dictionaryVarObject = dictionaryVar.getDynamicObject();
  157. if (dictionaryVarObject == nullptr)
  158. return {};
  159. const auto& properties = dictionaryVarObject->getProperties();
  160. DynamicObject::Ptr propsVarObject = new DynamicObject();
  161. for (int i = 0; i < properties.size(); ++i)
  162. {
  163. auto propertyName = properties.getName (i).toString();
  164. if (propertyName == "aps")
  165. continue;
  166. propsVarObject->setProperty (propertyName, properties.getValueAt (i));
  167. }
  168. return var (propsVarObject.get());
  169. }
  170. PushNotifications::Notification nsDictionaryToJuceNotification (NSDictionary* dictionary)
  171. {
  172. const var dictionaryVar = nsDictionaryToVar (dictionary);
  173. const var apsVar = dictionaryVar.getProperty ("aps", {});
  174. if (! apsVar.isObject())
  175. return {};
  176. var alertVar = apsVar.getProperty ("alert", {});
  177. const var titleVar = alertVar.getProperty ("title", {});
  178. const var bodyVar = alertVar.isObject() ? alertVar.getProperty ("body", {}) : alertVar;
  179. const var categoryVar = apsVar.getProperty ("category", {});
  180. const var soundVar = apsVar.getProperty ("sound", {});
  181. const var badgeVar = apsVar.getProperty ("badge", {});
  182. const var threadIdVar = apsVar.getProperty ("thread-id", {});
  183. PushNotifications::Notification notification;
  184. notification.title = titleVar .toString();
  185. notification.body = bodyVar .toString();
  186. notification.groupId = threadIdVar.toString();
  187. notification.category = categoryVar.toString();
  188. notification.soundToPlay = URL (soundVar.toString());
  189. notification.badgeNumber = (int) badgeVar;
  190. notification.properties = getNotificationPropertiesFromDictionaryVar (dictionaryVar);
  191. return notification;
  192. }
  193. }
  194. //==============================================================================
  195. struct PushNotificationsDelegate
  196. {
  197. PushNotificationsDelegate() : delegate ([getClass().createInstance() init])
  198. {
  199. Class::setThis (delegate.get(), this);
  200. id<NSApplicationDelegate> appDelegate = [[NSApplication sharedApplication] delegate];
  201. JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector")
  202. if ([appDelegate respondsToSelector: @selector (setPushNotificationsDelegate:)])
  203. [appDelegate performSelector: @selector (setPushNotificationsDelegate:) withObject: delegate.get()];
  204. JUCE_END_IGNORE_WARNINGS_GCC_LIKE
  205. [NSUserNotificationCenter defaultUserNotificationCenter].delegate = delegate.get();
  206. }
  207. virtual ~PushNotificationsDelegate()
  208. {
  209. [NSUserNotificationCenter defaultUserNotificationCenter].delegate = nil;
  210. }
  211. virtual void registeredForRemoteNotifications (NSData* deviceToken) = 0;
  212. virtual void failedToRegisterForRemoteNotifications (NSError* error) = 0;
  213. virtual void didReceiveRemoteNotification (NSDictionary* userInfo) = 0;
  214. virtual void didDeliverNotification (NSUserNotification* notification) = 0;
  215. virtual void didActivateNotification (NSUserNotification* notification) = 0;
  216. virtual bool shouldPresentNotification (NSUserNotification* notification) = 0;
  217. protected:
  218. NSUniquePtr<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>> delegate;
  219. private:
  220. struct Class : public ObjCClass<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>>
  221. {
  222. Class() : ObjCClass<NSObject<NSApplicationDelegate, NSUserNotificationCenterDelegate>> ("JucePushNotificationsDelegate_")
  223. {
  224. addIvar<PushNotificationsDelegate*> ("self");
  225. addMethod (@selector (application:didRegisterForRemoteNotificationsWithDeviceToken:), registeredForRemoteNotifications);
  226. addMethod (@selector (application:didFailToRegisterForRemoteNotificationsWithError:), failedToRegisterForRemoteNotifications);
  227. addMethod (@selector (application:didReceiveRemoteNotification:), didReceiveRemoteNotification);
  228. addMethod (@selector (userNotificationCenter:didDeliverNotification:), didDeliverNotification);
  229. addMethod (@selector (userNotificationCenter:didActivateNotification:), didActivateNotification);
  230. addMethod (@selector (userNotificationCenter:shouldPresentNotification:), shouldPresentNotification);
  231. registerClass();
  232. }
  233. //==============================================================================
  234. static PushNotificationsDelegate& getThis (id self) { return *getIvar<PushNotificationsDelegate*> (self, "self"); }
  235. static void setThis (id self, PushNotificationsDelegate* d) { object_setInstanceVariable (self, "self", d); }
  236. //==============================================================================
  237. static void registeredForRemoteNotifications (id self, SEL, NSApplication*,
  238. NSData* deviceToken) { getThis (self).registeredForRemoteNotifications (deviceToken); }
  239. static void failedToRegisterForRemoteNotifications (id self, SEL, NSApplication*,
  240. NSError* error) { getThis (self).failedToRegisterForRemoteNotifications (error); }
  241. static void didReceiveRemoteNotification (id self, SEL, NSApplication*,
  242. NSDictionary* userInfo) { getThis (self).didReceiveRemoteNotification (userInfo); }
  243. static void didDeliverNotification (id self, SEL, NSUserNotificationCenter*,
  244. NSUserNotification* notification) { getThis (self).didDeliverNotification (notification); }
  245. static void didActivateNotification (id self, SEL, NSUserNotificationCenter*,
  246. NSUserNotification* notification) { getThis (self).didActivateNotification (notification); }
  247. static bool shouldPresentNotification (id self, SEL, NSUserNotificationCenter*,
  248. NSUserNotification* notification) { return getThis (self).shouldPresentNotification (notification); }
  249. };
  250. //==============================================================================
  251. static Class& getClass()
  252. {
  253. static Class c;
  254. return c;
  255. }
  256. };
  257. //==============================================================================
  258. bool PushNotifications::Notification::isValid() const noexcept { return true; }
  259. //==============================================================================
  260. struct PushNotifications::Pimpl : private PushNotificationsDelegate
  261. {
  262. Pimpl (PushNotifications& p)
  263. : owner (p)
  264. {
  265. }
  266. void requestPermissionsWithSettings (const PushNotifications::Settings& settingsToUse)
  267. {
  268. if (isEarlierThanLion)
  269. return;
  270. settings = settingsToUse;
  271. NSRemoteNotificationType types = NSUInteger ((bool) settings.allowBadge);
  272. if (isAtLeastMountainLion)
  273. types |= (NSUInteger) ((bool) settings.allowSound << 1 | (bool) settings.allowAlert << 2);
  274. [[NSApplication sharedApplication] registerForRemoteNotificationTypes: types];
  275. }
  276. void requestSettingsUsed()
  277. {
  278. if (isEarlierThanLion)
  279. {
  280. // no settings available
  281. owner.listeners.call ([] (Listener& l) { l.notificationSettingsReceived ({}); });
  282. return;
  283. }
  284. settings.allowBadge = [NSApplication sharedApplication].enabledRemoteNotificationTypes & NSRemoteNotificationTypeBadge;
  285. if (isAtLeastMountainLion)
  286. {
  287. settings.allowSound = [NSApplication sharedApplication].enabledRemoteNotificationTypes & NSRemoteNotificationTypeSound;
  288. settings.allowAlert = [NSApplication sharedApplication].enabledRemoteNotificationTypes & NSRemoteNotificationTypeAlert;
  289. }
  290. owner.listeners.call ([&] (Listener& l) { l.notificationSettingsReceived (settings); });
  291. }
  292. bool areNotificationsEnabled() const { return true; }
  293. void sendLocalNotification (const Notification& n)
  294. {
  295. auto* notification = PushNotificationsDelegateDetailsOsx::juceNotificationToNSUserNotification (n, isEarlierThanMavericks, isEarlierThanYosemite);
  296. [[NSUserNotificationCenter defaultUserNotificationCenter] scheduleNotification: notification];
  297. }
  298. void getDeliveredNotifications() const
  299. {
  300. Array<PushNotifications::Notification> notifs;
  301. for (NSUserNotification* n in [NSUserNotificationCenter defaultUserNotificationCenter].deliveredNotifications)
  302. notifs.add (PushNotificationsDelegateDetailsOsx::nsUserNotificationToJuceNotification (n, isEarlierThanMavericks, isEarlierThanYosemite));
  303. owner.listeners.call ([&] (Listener& l) { l.deliveredNotificationsListReceived (notifs); });
  304. }
  305. void removeAllDeliveredNotifications()
  306. {
  307. [[NSUserNotificationCenter defaultUserNotificationCenter] removeAllDeliveredNotifications];
  308. }
  309. void removeDeliveredNotification (const String& identifier)
  310. {
  311. PushNotifications::Notification n;
  312. n.identifier = identifier;
  313. auto nsNotification = PushNotificationsDelegateDetailsOsx::juceNotificationToNSUserNotification (n, isEarlierThanMavericks, isEarlierThanYosemite);
  314. [[NSUserNotificationCenter defaultUserNotificationCenter] removeDeliveredNotification: nsNotification];
  315. }
  316. void setupChannels (const Array<ChannelGroup>& groups, const Array<Channel>& channels)
  317. {
  318. ignoreUnused (groups, channels);
  319. }
  320. void getPendingLocalNotifications() const
  321. {
  322. Array<PushNotifications::Notification> notifs;
  323. for (NSUserNotification* n in [NSUserNotificationCenter defaultUserNotificationCenter].scheduledNotifications)
  324. notifs.add (PushNotificationsDelegateDetailsOsx::nsUserNotificationToJuceNotification (n, isEarlierThanMavericks, isEarlierThanYosemite));
  325. owner.listeners.call ([&] (Listener& l) { l.pendingLocalNotificationsListReceived (notifs); });
  326. }
  327. void removePendingLocalNotification (const String& identifier)
  328. {
  329. PushNotifications::Notification n;
  330. n.identifier = identifier;
  331. auto nsNotification = PushNotificationsDelegateDetailsOsx::juceNotificationToNSUserNotification (n, isEarlierThanMavericks, isEarlierThanYosemite);
  332. [[NSUserNotificationCenter defaultUserNotificationCenter] removeScheduledNotification: nsNotification];
  333. }
  334. void removeAllPendingLocalNotifications()
  335. {
  336. for (NSUserNotification* n in [NSUserNotificationCenter defaultUserNotificationCenter].scheduledNotifications)
  337. [[NSUserNotificationCenter defaultUserNotificationCenter] removeScheduledNotification: n];
  338. }
  339. String getDeviceToken()
  340. {
  341. // You need to call requestPermissionsWithSettings() first.
  342. jassert (initialised);
  343. return deviceToken;
  344. }
  345. //==============================================================================
  346. //PushNotificationsDelegate
  347. void registeredForRemoteNotifications (NSData* deviceTokenToUse) override
  348. {
  349. deviceToken = [deviceTokenToUse]() -> String
  350. {
  351. auto length = deviceTokenToUse.length;
  352. if (auto* buffer = (const unsigned char*) deviceTokenToUse.bytes)
  353. {
  354. NSMutableString* hexString = [NSMutableString stringWithCapacity: (length * 2)];
  355. for (NSUInteger i = 0; i < length; ++i)
  356. [hexString appendFormat:@"%02x", buffer[i]];
  357. return nsStringToJuce ([hexString copy]);
  358. }
  359. return {};
  360. }();
  361. initialised = true;
  362. owner.listeners.call ([&] (Listener& l) { l.deviceTokenRefreshed (deviceToken); });
  363. }
  364. void failedToRegisterForRemoteNotifications (NSError* error) override
  365. {
  366. ignoreUnused (error);
  367. deviceToken.clear();
  368. }
  369. void didReceiveRemoteNotification (NSDictionary* userInfo) override
  370. {
  371. auto n = PushNotificationsDelegateDetailsOsx::nsDictionaryToJuceNotification (userInfo);
  372. owner.listeners.call ([&] (Listener& l) { l.handleNotification (true, n); });
  373. }
  374. void didDeliverNotification (NSUserNotification* notification) override
  375. {
  376. ignoreUnused (notification);
  377. }
  378. void didActivateNotification (NSUserNotification* notification) override
  379. {
  380. auto n = PushNotificationsDelegateDetailsOsx::nsUserNotificationToJuceNotification (notification, isEarlierThanMavericks, isEarlierThanYosemite);
  381. if (notification.activationType == NSUserNotificationActivationTypeContentsClicked)
  382. {
  383. owner.listeners.call ([&] (Listener& l) { l.handleNotification (notification.remote, n); });
  384. }
  385. else
  386. {
  387. auto actionIdentifier = (! isEarlierThanYosemite && notification.additionalActivationAction != nil)
  388. ? nsStringToJuce (notification.additionalActivationAction.identifier)
  389. : nsStringToJuce (notification.actionButtonTitle);
  390. auto reply = notification.activationType == NSUserNotificationActivationTypeReplied
  391. ? nsStringToJuce ([notification.response string])
  392. : String();
  393. owner.listeners.call ([&] (Listener& l) { l.handleNotificationAction (notification.remote, n, actionIdentifier, reply); });
  394. }
  395. }
  396. bool shouldPresentNotification (NSUserNotification*) override { return true; }
  397. void subscribeToTopic (const String& topic) { ignoreUnused (topic); }
  398. void unsubscribeFromTopic (const String& topic) { ignoreUnused (topic); }
  399. void sendUpstreamMessage (const String& serverSenderId,
  400. const String& collapseKey,
  401. const String& messageId,
  402. const String& messageType,
  403. int timeToLive,
  404. const StringPairArray& additionalData)
  405. {
  406. ignoreUnused (serverSenderId, collapseKey, messageId, messageType);
  407. ignoreUnused (timeToLive, additionalData);
  408. }
  409. private:
  410. PushNotifications& owner;
  411. const bool isEarlierThanLion = std::floor (NSFoundationVersionNumber) < std::floor (NSFoundationVersionNumber10_7);
  412. const bool isAtLeastMountainLion = std::floor (NSFoundationVersionNumber) >= NSFoundationVersionNumber10_7;
  413. const bool isEarlierThanMavericks = std::floor (NSFoundationVersionNumber) < NSFoundationVersionNumber10_9;
  414. const bool isEarlierThanYosemite = std::floor (NSFoundationVersionNumber) <= NSFoundationVersionNumber10_9;
  415. bool initialised = false;
  416. String deviceToken;
  417. PushNotifications::Settings settings;
  418. };
  419. } // namespace juce
  420. JUCE_END_IGNORE_WARNINGS_GCC_LIKE