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.

565 lines
23KB

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