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.

558 lines
23KB

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