Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 2 commits into from Oct 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions docs/api/notification.md
Expand Up @@ -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'.
Expand Down Expand Up @@ -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.
Expand Down
12 changes: 12 additions & 0 deletions shell/browser/api/atom_api_notification.cc
Expand Up @@ -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_);
Expand Down Expand Up @@ -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_;
}
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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_;
Expand Down Expand Up @@ -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,
Expand Down
3 changes: 3 additions & 0 deletions shell/browser/api/atom_api_notification.h
Expand Up @@ -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;
Expand All @@ -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);
Expand All @@ -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_;
codebytere marked this conversation as resolved.
Show resolved Hide resolved
base::string16 reply_placeholder_;
base::string16 sound_;
base::string16 urgency_;
Expand Down
7 changes: 5 additions & 2 deletions shell/browser/notifications/linux/libnotify_notification.cc
Expand Up @@ -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);
Expand Down
1 change: 1 addition & 0 deletions shell/browser/notifications/notification.h
Expand Up @@ -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
Expand Down
63 changes: 62 additions & 1 deletion shell/browser/notifications/win/windows_toast_notification.cc
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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))) {
Expand All @@ -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())
Expand Down
2 changes: 2 additions & 0 deletions shell/browser/notifications/win/windows_toast_notification.h
Expand Up @@ -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,
Expand Down