Browse Source

feat: allow Linux/Windows users to set notification timeout (#20153)

* feat: allow Linux users to set notification timeout

* implement on windows
Shelley Vohr 5 years ago
parent
commit
f80a17c5be

+ 7 - 0
docs/api/notification.md

@@ -35,6 +35,7 @@ Returns `Boolean` - Whether or not desktop notifications are supported on the cu
   * `silent` Boolean (optional) - Whether or not to emit an OS notification noise when showing the notification.
   * `icon` (String | [NativeImage](native-image.md)) (optional) - An icon to use in the notification.
   * `hasReply` Boolean (optional) _macOS_ - Whether or not to add an inline reply option to the notification.
+  * `timeoutType` String (optional) _Linux_ _Windows_ - The timeout duration of the notification. Can be 'default' or 'never'.
   * `replyPlaceholder` String (optional) _macOS_ - The placeholder to write in the inline reply input field.
   * `sound` String (optional) _macOS_ - The name of the sound file to play when the notification is shown.
   * `urgency` String (optional) _Linux_ - The urgency level of the notification. Can be 'normal', 'critical', or 'low'.
@@ -151,6 +152,12 @@ A `String` property representing the urgency level of the notification. Can be '
 
 Default is 'low' - see [NotifyUrgency](https://developer.gnome.org/notification-spec/#urgency-levels) for more information.
 
+#### `notification.timeoutType` _Linux_ _Windows_
+
+A `String` property representing the type of timeout duration for the notification. Can be 'default' or 'never'.
+
+If `timeoutType` is set to 'never', the notification never expires. It stays open until closed by the calling API or the user.
+
 #### `notification.actions`
 
 A [`NotificationAction[]`](structures/notification-action.md) property representing the actions of the notification.

+ 12 - 0
shell/browser/api/atom_api_notification.cc

@@ -69,6 +69,7 @@ Notification::Notification(v8::Local<v8::Object> wrapper,
     opts.Get("replyPlaceholder", &reply_placeholder_);
     opts.Get("urgency", &urgency_);
     opts.Get("hasReply", &has_reply_);
+    opts.Get("timeoutType", &timeout_type_);
     opts.Get("actions", &actions_);
     opts.Get("sound", &sound_);
     opts.Get("closeButtonText", &close_button_text_);
@@ -111,6 +112,10 @@ bool Notification::GetHasReply() const {
   return has_reply_;
 }
 
+base::string16 Notification::GetTimeoutType() const {
+  return timeout_type_;
+}
+
 base::string16 Notification::GetReplyPlaceholder() const {
   return reply_placeholder_;
 }
@@ -152,6 +157,10 @@ void Notification::SetHasReply(bool new_has_reply) {
   has_reply_ = new_has_reply;
 }
 
+void Notification::SetTimeoutType(const base::string16& new_timeout_type) {
+  timeout_type_ = new_timeout_type;
+}
+
 void Notification::SetReplyPlaceholder(const base::string16& new_placeholder) {
   reply_placeholder_ = new_placeholder;
 }
@@ -216,6 +225,7 @@ void Notification::Show() {
       options.icon = icon_.AsBitmap();
       options.silent = silent_;
       options.has_reply = has_reply_;
+      options.timeout_type = timeout_type_;
       options.reply_placeholder = reply_placeholder_;
       options.actions = actions_;
       options.sound = sound_;
@@ -246,6 +256,8 @@ void Notification::BuildPrototype(v8::Isolate* isolate,
       .SetProperty("silent", &Notification::GetSilent, &Notification::SetSilent)
       .SetProperty("hasReply", &Notification::GetHasReply,
                    &Notification::SetHasReply)
+      .SetProperty("timeoutType", &Notification::GetTimeoutType,
+                   &Notification::SetTimeoutType)
       .SetProperty("replyPlaceholder", &Notification::GetReplyPlaceholder,
                    &Notification::SetReplyPlaceholder)
       .SetProperty("urgency", &Notification::GetUrgency,

+ 3 - 0
shell/browser/api/atom_api_notification.h

@@ -54,6 +54,7 @@ class Notification : public mate::TrackableObject<Notification>,
   base::string16 GetBody() const;
   bool GetSilent() const;
   bool GetHasReply() const;
+  base::string16 GetTimeoutType() const;
   base::string16 GetReplyPlaceholder() const;
   base::string16 GetUrgency() const;
   base::string16 GetSound() const;
@@ -67,6 +68,7 @@ class Notification : public mate::TrackableObject<Notification>,
   void SetSilent(bool new_silent);
   void SetHasReply(bool new_has_reply);
   void SetUrgency(const base::string16& new_urgency);
+  void SetTimeoutType(const base::string16& new_timeout_type);
   void SetReplyPlaceholder(const base::string16& new_reply_placeholder);
   void SetSound(const base::string16& sound);
   void SetActions(const std::vector<electron::NotificationAction>& actions);
@@ -81,6 +83,7 @@ class Notification : public mate::TrackableObject<Notification>,
   bool has_icon_ = false;
   bool silent_ = false;
   bool has_reply_ = false;
+  base::string16 timeout_type_;
   base::string16 reply_placeholder_;
   base::string16 sound_;
   base::string16 urgency_;

+ 5 - 2
shell/browser/notifications/linux/libnotify_notification.cc

@@ -114,11 +114,14 @@ void LibnotifyNotification::Show(const NotificationOptions& options) {
     GdkPixbuf* pixbuf = libgtkui::GdkPixbufFromSkBitmap(options.icon);
     libnotify_loader_.notify_notification_set_image_from_pixbuf(notification_,
                                                                 pixbuf);
-    libnotify_loader_.notify_notification_set_timeout(notification_,
-                                                      NOTIFY_EXPIRES_DEFAULT);
     g_object_unref(pixbuf);
   }
 
+  // Set the timeout duration for the notification
+  bool neverTimeout = options.timeout_type == base::ASCIIToUTF16("never");
+  int timeout = (neverTimeout) ? NOTIFY_EXPIRES_NEVER : NOTIFY_EXPIRES_DEFAULT;
+  libnotify_loader_.notify_notification_set_timeout(notification_, timeout);
+
   if (!options.tag.empty()) {
     GQuark id = g_quark_from_string(options.tag.c_str());
     g_object_set(G_OBJECT(notification_), "id", id, NULL);

+ 1 - 0
shell/browser/notifications/notification.h

@@ -32,6 +32,7 @@ struct NotificationOptions {
   GURL icon_url;
   SkBitmap icon;
   bool has_reply;
+  base::string16 timeout_type;
   base::string16 reply_placeholder;
   base::string16 sound;
   base::string16 urgency;  // Linux

+ 62 - 1
shell/browser/notifications/win/windows_toast_notification.cc

@@ -94,7 +94,8 @@ void WindowsToastNotification::Show(const NotificationOptions& options) {
 
   ComPtr<IXmlDocument> toast_xml;
   if (FAILED(GetToastXml(toast_manager_.Get(), options.title, options.msg,
-                         icon_path, options.silent, &toast_xml))) {
+                         icon_path, options.timeout_type, options.silent,
+                         &toast_xml))) {
     NotificationFailed();
     return;
   }
@@ -149,6 +150,7 @@ bool WindowsToastNotification::GetToastXml(
     const std::wstring& title,
     const std::wstring& msg,
     const std::wstring& icon_path,
+    const std::wstring& timeout_type,
     bool silent,
     IXmlDocument** toast_xml) {
   ABI::Windows::UI::Notifications::ToastTemplateType template_type;
@@ -183,6 +185,15 @@ bool WindowsToastNotification::GetToastXml(
     }
   }
 
+  // Configure the toast's timeout settings
+  if (timeout_type == base::ASCIIToUTF16("never")) {
+    if (FAILED(SetXmlScenarioReminder(*toast_xml))) {
+      if (IsDebuggingNotifications())
+        LOG(INFO) << "Setting \"scenario\" option on notification failed";
+      return false;
+    }
+  }
+
   // Configure the toast's notification sound
   if (silent) {
     if (FAILED(SetXmlAudioSilent(*toast_xml))) {
@@ -201,6 +212,56 @@ bool WindowsToastNotification::GetToastXml(
   return true;
 }
 
+bool WindowsToastNotification::SetXmlScenarioReminder(IXmlDocument* doc) {
+  ScopedHString tag(L"toast");
+  if (!tag.success())
+    return false;
+
+  ComPtr<IXmlNodeList> node_list;
+  if (FAILED(doc->GetElementsByTagName(tag, &node_list)))
+    return false;
+
+  // Check that root "toast" node exists
+  ComPtr<IXmlNode> root;
+  if (FAILED(node_list->Item(0, &root)))
+    return false;
+
+  // get attributes of root "toast" node
+  ComPtr<IXmlNamedNodeMap> attributes;
+  if (FAILED(root->get_Attributes(&attributes)))
+    return false;
+
+  ComPtr<IXmlAttribute> scenario_attribute;
+  ScopedHString scenario_str(L"scenario");
+  if (FAILED(doc->CreateAttribute(scenario_str, &scenario_attribute)))
+    return false;
+
+  ComPtr<IXmlNode> scenario_attribute_node;
+  if (FAILED(scenario_attribute.As(&scenario_attribute_node)))
+    return false;
+
+  ScopedHString scenario_value(L"reminder");
+  if (!scenario_value.success())
+    return false;
+
+  ComPtr<IXmlText> scenario_text;
+  if (FAILED(doc->CreateTextNode(scenario_value, &scenario_text)))
+    return false;
+
+  ComPtr<IXmlNode> scenario_node;
+  if (FAILED(scenario_text.As(&scenario_node)))
+    return false;
+
+  ComPtr<IXmlNode> child_node;
+  if (FAILED(scenario_attribute_node->AppendChild(scenario_node.Get(),
+                                                  &child_node)))
+    return false;
+
+  ComPtr<IXmlNode> scenario_attribute_pnode;
+  return SUCCEEDED(attributes.Get()->SetNamedItem(scenario_attribute_node.Get(),
+                                                  &scenario_attribute_pnode));
+}
+
 bool WindowsToastNotification::SetXmlAudioSilent(IXmlDocument* doc) {
   ScopedHString tag(L"toast");
   if (!tag.success())

+ 2 - 0
shell/browser/notifications/win/windows_toast_notification.h

@@ -63,9 +63,11 @@ class WindowsToastNotification : public Notification {
       const std::wstring& title,
       const std::wstring& msg,
       const std::wstring& icon_path,
+      const std::wstring& timeout_type,
       const bool silent,
       ABI::Windows::Data::Xml::Dom::IXmlDocument** toastXml);
   bool SetXmlAudioSilent(ABI::Windows::Data::Xml::Dom::IXmlDocument* doc);
+  bool SetXmlScenarioReminder(ABI::Windows::Data::Xml::Dom::IXmlDocument* doc);
   bool SetXmlText(ABI::Windows::Data::Xml::Dom::IXmlDocument* doc,
                   const std::wstring& text);
   bool SetXmlText(ABI::Windows::Data::Xml::Dom::IXmlDocument* doc,