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.

juce_mac_PushNotifications.cpp 23KB

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