diff --git a/docs/api/notification.md b/docs/api/notification.md index 27f5bc34ba671..32a24a0d0fe9d 100644 --- a/docs/api/notification.md +++ b/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. diff --git a/shell/browser/api/atom_api_notification.cc b/shell/browser/api/atom_api_notification.cc index f36f6640ef923..8a2f98943b7bf 100644 --- a/shell/browser/api/atom_api_notification.cc +++ b/shell/browser/api/atom_api_notification.cc @@ -69,6 +69,7 @@ Notification::Notification(v8::Local 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, diff --git a/shell/browser/api/atom_api_notification.h b/shell/browser/api/atom_api_notification.h index 8e77fc893b96e..9aa84f5b29cca 100644 --- a/shell/browser/api/atom_api_notification.h +++ b/shell/browser/api/atom_api_notification.h @@ -54,6 +54,7 @@ class Notification : public mate::TrackableObject, 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, 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& actions); @@ -81,6 +83,7 @@ class Notification : public mate::TrackableObject, 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_; diff --git a/shell/browser/notifications/linux/libnotify_notification.cc b/shell/browser/notifications/linux/libnotify_notification.cc index d286d559fcebb..dc52fa5af5b1f 100644 --- a/shell/browser/notifications/linux/libnotify_notification.cc +++ b/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); diff --git a/shell/browser/notifications/notification.h b/shell/browser/notifications/notification.h index 74dd8f964ad28..1a465bdbef881 100644 --- a/shell/browser/notifications/notification.h +++ b/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 diff --git a/shell/browser/notifications/win/windows_toast_notification.cc b/shell/browser/notifications/win/windows_toast_notification.cc index c73cdf39c8a4d..b5ca4dc62853f 100644 --- a/shell/browser/notifications/win/windows_toast_notification.cc +++ b/shell/browser/notifications/win/windows_toast_notification.cc @@ -94,7 +94,8 @@ void WindowsToastNotification::Show(const NotificationOptions& options) { ComPtr 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 node_list; + if (FAILED(doc->GetElementsByTagName(tag, &node_list))) + return false; + + // Check that root "toast" node exists + ComPtr root; + if (FAILED(node_list->Item(0, &root))) + return false; + + // get attributes of root "toast" node + ComPtr attributes; + if (FAILED(root->get_Attributes(&attributes))) + return false; + + ComPtr scenario_attribute; + ScopedHString scenario_str(L"scenario"); + if (FAILED(doc->CreateAttribute(scenario_str, &scenario_attribute))) + return false; + + ComPtr 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 scenario_text; + if (FAILED(doc->CreateTextNode(scenario_value, &scenario_text))) + return false; + + ComPtr scenario_node; + if (FAILED(scenario_text.As(&scenario_node))) + return false; + + ComPtr child_node; + if (FAILED(scenario_attribute_node->AppendChild(scenario_node.Get(), + &child_node))) + return false; + + ComPtr 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()) diff --git a/shell/browser/notifications/win/windows_toast_notification.h b/shell/browser/notifications/win/windows_toast_notification.h index 10c378480782e..eed2e2bed5d08 100644 --- a/shell/browser/notifications/win/windows_toast_notification.h +++ b/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,