electron/brightray/browser/win/windows_toast_notification.cc

448 lines
13 KiB
C++
Raw Normal View History

2017-03-23 19:48:22 +00:00
// Copyright (c) 2015 Felix Rieseberg <feriese@microsoft.com> and Jason Poon
// <jason.poon@microsoft.com>. All rights reserved.
// Copyright (c) 2015 Ryan McShane <rmcshane@bandwidth.com> and Brandon Smith
// <bsmith@bandwidth.com>
// Thanks to both of those folks mentioned above who first thought up a bunch of
// this code
// and released it as MIT to the world.
#include "brightray/browser/win/windows_toast_notification.h"
2015-11-20 05:28:37 +00:00
#include <shlobj.h>
#include <vector>
2015-11-20 05:28:37 +00:00
#include "base/environment.h"
2015-11-10 11:50:38 +00:00
#include "base/strings/utf_string_conversions.h"
#include "brightray/browser/notification_delegate.h"
#include "brightray/browser/win/notification_presenter_win.h"
#include "brightray/browser/win/scoped_hstring.h"
#include "brightray/common/application_info.h"
#include "content/public/browser/browser_thread.h"
2017-03-23 19:48:22 +00:00
using ABI::Windows::Data::Xml::Dom::IXmlAttribute;
using ABI::Windows::Data::Xml::Dom::IXmlDocument;
using ABI::Windows::Data::Xml::Dom::IXmlElement;
using ABI::Windows::Data::Xml::Dom::IXmlNamedNodeMap;
using ABI::Windows::Data::Xml::Dom::IXmlNode;
using ABI::Windows::Data::Xml::Dom::IXmlNodeList;
using ABI::Windows::Data::Xml::Dom::IXmlText;
2015-11-10 12:07:12 +00:00
namespace brightray {
2015-11-16 08:47:34 +00:00
2015-11-20 05:28:37 +00:00
namespace {
bool IsDebuggingNotifications() {
return base::Environment::Create()->HasVar("ELECTRON_DEBUG_NOTIFICATIONS");
}
2015-11-20 05:28:37 +00:00
} // namespace
// static
ComPtr<ABI::Windows::UI::Notifications::IToastNotificationManagerStatics>
WindowsToastNotification::toast_manager_;
// static
ComPtr<ABI::Windows::UI::Notifications::IToastNotifier>
WindowsToastNotification::toast_notifier_;
// static
bool WindowsToastNotification::Initialize() {
// Just initialize, don't care if it fails or already initialized.
Windows::Foundation::Initialize(RO_INIT_MULTITHREADED);
2015-11-16 08:47:34 +00:00
2015-11-10 11:50:38 +00:00
ScopedHString toast_manager_str(
RuntimeClass_Windows_UI_Notifications_ToastNotificationManager);
if (!toast_manager_str.success())
return false;
if (FAILED(Windows::Foundation::GetActivationFactory(toast_manager_str,
&toast_manager_)))
return false;
2015-11-10 11:50:38 +00:00
if (IsRunningInDesktopBridge()) {
// Ironically, the Desktop Bridge / UWP environment
// requires us to not give Windows an appUserModelId.
return SUCCEEDED(toast_manager_->CreateToastNotifier(&toast_notifier_));
} else {
ScopedHString app_id;
if (!GetAppUserModelID(&app_id))
return false;
return SUCCEEDED(
toast_manager_->CreateToastNotifierWithId(app_id, &toast_notifier_));
}
}
2015-11-10 11:50:38 +00:00
WindowsToastNotification::WindowsToastNotification(
2015-12-25 03:05:48 +00:00
NotificationDelegate* delegate,
NotificationPresenter* presenter)
2017-03-23 19:48:22 +00:00
: Notification(delegate, presenter) {}
WindowsToastNotification::~WindowsToastNotification() {
// Remove the notification on exit.
if (toast_notification_) {
RemoveCallbacks(toast_notification_.Get());
Dismiss();
}
}
2017-06-24 11:03:27 +00:00
void WindowsToastNotification::Show(const NotificationOptions& options) {
auto* presenter_win = static_cast<NotificationPresenterWin*>(presenter());
2018-04-18 01:56:12 +00:00
std::wstring icon_path =
presenter_win->SaveIconToFilesystem(options.icon, options.icon_url);
2017-12-14 00:31:02 +00:00
2015-11-10 11:50:38 +00:00
ComPtr<IXmlDocument> toast_xml;
2017-06-24 11:03:27 +00:00
if (FAILED(GetToastXml(toast_manager_.Get(), options.title, options.msg,
icon_path, options.silent, &toast_xml))) {
NotificationFailed();
2015-11-10 11:50:38 +00:00
return;
}
2015-11-10 11:50:38 +00:00
ScopedHString toast_str(
RuntimeClass_Windows_UI_Notifications_ToastNotification);
if (!toast_str.success()) {
NotificationFailed();
2015-11-10 11:50:38 +00:00
return;
}
2017-03-23 19:48:22 +00:00
ComPtr<ABI::Windows::UI::Notifications::IToastNotificationFactory>
toast_factory;
2015-11-10 12:23:08 +00:00
if (FAILED(Windows::Foundation::GetActivationFactory(toast_str,
&toast_factory))) {
NotificationFailed();
2015-11-10 11:50:38 +00:00
return;
}
2015-11-10 12:23:08 +00:00
if (FAILED(toast_factory->CreateToastNotification(toast_xml.Get(),
&toast_notification_))) {
NotificationFailed();
2015-11-10 11:50:38 +00:00
return;
}
if (!SetupCallbacks(toast_notification_.Get())) {
NotificationFailed();
2015-11-10 11:50:38 +00:00
return;
}
if (FAILED(toast_notifier_->Show(toast_notification_.Get()))) {
NotificationFailed();
2015-11-10 11:50:38 +00:00
return;
}
2018-04-18 01:56:12 +00:00
if (IsDebuggingNotifications())
LOG(INFO) << "Notification created";
2017-05-30 09:06:51 +00:00
if (delegate())
delegate()->NotificationDisplayed();
}
2015-12-25 03:05:48 +00:00
void WindowsToastNotification::Dismiss() {
2018-04-18 01:56:12 +00:00
if (IsDebuggingNotifications())
LOG(INFO) << "Hiding notification";
2015-11-10 12:23:08 +00:00
toast_notifier_->Hide(toast_notification_.Get());
}
2015-11-10 11:50:38 +00:00
bool WindowsToastNotification::GetToastXml(
2017-03-23 19:48:22 +00:00
ABI::Windows::UI::Notifications::IToastNotificationManagerStatics*
toastManager,
2015-11-10 12:07:12 +00:00
const std::wstring& title,
const std::wstring& msg,
const std::wstring& icon_path,
2017-05-30 09:06:51 +00:00
bool silent,
2015-11-10 11:50:38 +00:00
IXmlDocument** toast_xml) {
2015-11-11 02:04:09 +00:00
ABI::Windows::UI::Notifications::ToastTemplateType template_type;
2015-11-10 12:07:12 +00:00
if (title.empty() || msg.empty()) {
2015-11-10 11:50:38 +00:00
// Single line toast.
2017-03-23 19:48:22 +00:00
template_type =
icon_path.empty()
? ABI::Windows::UI::Notifications::ToastTemplateType_ToastText01
: ABI::Windows::UI::Notifications::
ToastTemplateType_ToastImageAndText01;
2015-11-10 11:50:38 +00:00
if (FAILED(toast_manager_->GetTemplateContent(template_type, toast_xml)))
return false;
2015-11-10 12:07:12 +00:00
if (!SetXmlText(*toast_xml, title.empty() ? msg : title))
2015-11-10 11:50:38 +00:00
return false;
} else {
2015-11-10 11:50:38 +00:00
// Title and body toast.
2017-03-23 19:48:22 +00:00
template_type =
icon_path.empty()
? ABI::Windows::UI::Notifications::ToastTemplateType_ToastText02
: ABI::Windows::UI::Notifications::
ToastTemplateType_ToastImageAndText02;
2017-12-14 00:31:02 +00:00
if (FAILED(toastManager->GetTemplateContent(template_type, toast_xml))) {
if (IsDebuggingNotifications())
LOG(INFO) << "Fetching XML template failed";
2015-11-10 11:50:38 +00:00
return false;
2017-12-14 00:31:02 +00:00
}
if (!SetXmlText(*toast_xml, title, msg)) {
if (IsDebuggingNotifications())
LOG(INFO) << "Setting text fields on template failed";
2015-11-10 11:50:38 +00:00
return false;
2017-12-14 00:31:02 +00:00
}
}
2016-03-08 06:02:42 +00:00
// Configure the toast's notification sound
if (silent) {
2017-12-14 00:31:02 +00:00
if (FAILED(SetXmlAudioSilent(*toast_xml))) {
if (IsDebuggingNotifications()) {
2017-12-14 00:31:02 +00:00
LOG(INFO) << "Setting \"silent\" option on notification failed";
}
2017-12-14 00:31:02 +00:00
return false;
2017-12-14 00:31:02 +00:00
}
}
// Configure the toast's image
2015-11-10 11:50:38 +00:00
if (!icon_path.empty())
return SetXmlImage(*toast_xml, icon_path);
2015-11-10 11:50:38 +00:00
return true;
}
2017-03-23 19:48:22 +00:00
bool WindowsToastNotification::SetXmlAudioSilent(IXmlDocument* doc) {
ScopedHString tag(L"toast");
if (!tag.success())
return false;
ComPtr<IXmlNodeList> node_list;
if (FAILED(doc->GetElementsByTagName(tag, &node_list)))
return false;
ComPtr<IXmlNode> root;
if (FAILED(node_list->Item(0, &root)))
return false;
2016-03-08 06:02:42 +00:00
ComPtr<IXmlElement> audio_element;
ScopedHString audio_str(L"audio");
if (FAILED(doc->CreateElement(audio_str, &audio_element)))
return false;
2016-03-08 06:02:42 +00:00
ComPtr<IXmlNode> audio_node_tmp;
if (FAILED(audio_element.As(&audio_node_tmp)))
return false;
2016-03-08 06:02:42 +00:00
// Append audio node to toast xml
ComPtr<IXmlNode> audio_node;
if (FAILED(root->AppendChild(audio_node_tmp.Get(), &audio_node)))
return false;
2016-03-08 06:02:42 +00:00
// Create silent attribute
ComPtr<IXmlNamedNodeMap> attributes;
if (FAILED(audio_node->get_Attributes(&attributes)))
return false;
2016-03-08 06:02:42 +00:00
ComPtr<IXmlAttribute> silent_attribute;
ScopedHString silent_str(L"silent");
if (FAILED(doc->CreateAttribute(silent_str, &silent_attribute)))
return false;
2016-03-08 06:02:42 +00:00
ComPtr<IXmlNode> silent_attribute_node;
if (FAILED(silent_attribute.As(&silent_attribute_node)))
return false;
2016-03-08 06:02:42 +00:00
// Set silent attribute to true
ScopedHString silent_value(L"true");
if (!silent_value.success())
return false;
2016-03-08 06:02:42 +00:00
ComPtr<IXmlText> silent_text;
if (FAILED(doc->CreateTextNode(silent_value, &silent_text)))
return false;
ComPtr<IXmlNode> silent_node;
if (FAILED(silent_text.As(&silent_node)))
return false;
2016-03-08 06:02:42 +00:00
ComPtr<IXmlNode> child_node;
2017-03-23 19:48:22 +00:00
if (FAILED(
silent_attribute_node->AppendChild(silent_node.Get(), &child_node)))
return false;
2016-03-08 06:02:42 +00:00
ComPtr<IXmlNode> silent_attribute_pnode;
2017-03-23 19:48:22 +00:00
return SUCCEEDED(attributes.Get()->SetNamedItem(silent_attribute_node.Get(),
&silent_attribute_pnode));
}
2017-03-23 19:48:22 +00:00
bool WindowsToastNotification::SetXmlText(IXmlDocument* doc,
const std::wstring& text) {
2015-11-10 11:50:38 +00:00
ScopedHString tag;
ComPtr<IXmlNodeList> node_list;
if (!GetTextNodeList(&tag, doc, &node_list, 1))
return false;
2015-11-10 11:50:38 +00:00
ComPtr<IXmlNode> node;
if (FAILED(node_list->Item(0, &node)))
return false;
2015-11-10 11:50:38 +00:00
return AppendTextToXml(doc, node.Get(), text);
}
2017-03-23 19:48:22 +00:00
bool WindowsToastNotification::SetXmlText(IXmlDocument* doc,
const std::wstring& title,
const std::wstring& body) {
2015-11-10 11:50:38 +00:00
ScopedHString tag;
ComPtr<IXmlNodeList> node_list;
if (!GetTextNodeList(&tag, doc, &node_list, 2))
return false;
2015-11-10 11:50:38 +00:00
ComPtr<IXmlNode> node;
if (FAILED(node_list->Item(0, &node)))
return false;
2015-11-10 11:50:38 +00:00
if (!AppendTextToXml(doc, node.Get(), title))
return false;
2015-11-10 11:50:38 +00:00
if (FAILED(node_list->Item(1, &node)))
return false;
2015-11-10 11:50:38 +00:00
return AppendTextToXml(doc, node.Get(), body);
}
2017-03-23 19:48:22 +00:00
bool WindowsToastNotification::SetXmlImage(IXmlDocument* doc,
const std::wstring& icon_path) {
ScopedHString tag(L"image");
2015-11-10 11:50:38 +00:00
if (!tag.success())
return false;
2015-11-10 11:50:38 +00:00
ComPtr<IXmlNodeList> node_list;
if (FAILED(doc->GetElementsByTagName(tag, &node_list)))
return false;
2015-11-10 11:50:38 +00:00
ComPtr<IXmlNode> image_node;
if (FAILED(node_list->Item(0, &image_node)))
return false;
2015-11-10 11:50:38 +00:00
ComPtr<IXmlNamedNodeMap> attrs;
if (FAILED(image_node->get_Attributes(&attrs)))
return false;
2015-11-10 11:50:38 +00:00
ScopedHString src(L"src");
if (!src.success())
return false;
2015-11-10 11:50:38 +00:00
ComPtr<IXmlNode> src_attr;
if (FAILED(attrs->GetNamedItem(src, &src_attr)))
return false;
ScopedHString img_path(icon_path.c_str());
2015-11-10 11:50:38 +00:00
if (!img_path.success())
return false;
2015-11-10 11:50:38 +00:00
ComPtr<IXmlText> src_text;
if (FAILED(doc->CreateTextNode(img_path, &src_text)))
return false;
2015-11-10 11:50:38 +00:00
ComPtr<IXmlNode> src_node;
if (FAILED(src_text.As(&src_node)))
return false;
2015-11-10 11:50:38 +00:00
ComPtr<IXmlNode> child_node;
return SUCCEEDED(src_attr->AppendChild(src_node.Get(), &child_node));
}
2017-03-23 19:48:22 +00:00
bool WindowsToastNotification::GetTextNodeList(ScopedHString* tag,
IXmlDocument* doc,
IXmlNodeList** node_list,
uint32_t req_length) {
tag->Reset(L"text");
2015-11-10 11:50:38 +00:00
if (!tag->success())
return false;
2015-11-10 11:50:38 +00:00
if (FAILED(doc->GetElementsByTagName(*tag, node_list)))
return false;
2016-03-08 06:02:42 +00:00
uint32_t node_length;
2015-11-10 11:50:38 +00:00
if (FAILED((*node_list)->get_Length(&node_length)))
return false;
2015-11-10 11:50:38 +00:00
return node_length >= req_length;
}
2017-03-23 19:48:22 +00:00
bool WindowsToastNotification::AppendTextToXml(IXmlDocument* doc,
IXmlNode* node,
const std::wstring& text) {
2015-11-10 11:50:38 +00:00
ScopedHString str(text);
if (!str.success())
return false;
2015-11-10 11:50:38 +00:00
ComPtr<IXmlText> xml_text;
if (FAILED(doc->CreateTextNode(str, &xml_text)))
return false;
2015-11-10 11:50:38 +00:00
ComPtr<IXmlNode> text_node;
if (FAILED(xml_text.As(&text_node)))
return false;
2015-11-10 11:50:38 +00:00
ComPtr<IXmlNode> append_node;
return SUCCEEDED(node->AppendChild(text_node.Get(), &append_node));
}
2015-12-25 03:05:48 +00:00
bool WindowsToastNotification::SetupCallbacks(
ABI::Windows::UI::Notifications::IToastNotification* toast) {
2015-11-10 11:50:38 +00:00
event_handler_ = Make<ToastEventHandler>(this);
2015-12-25 03:05:48 +00:00
if (FAILED(toast->add_Activated(event_handler_.Get(), &activated_token_)))
return false;
if (FAILED(toast->add_Dismissed(event_handler_.Get(), &dismissed_token_)))
return false;
return SUCCEEDED(toast->add_Failed(event_handler_.Get(), &failed_token_));
}
bool WindowsToastNotification::RemoveCallbacks(
ABI::Windows::UI::Notifications::IToastNotification* toast) {
if (FAILED(toast->remove_Activated(activated_token_)))
2015-11-10 11:50:38 +00:00
return false;
2015-12-25 03:05:48 +00:00
if (FAILED(toast->remove_Dismissed(dismissed_token_)))
return false;
2015-12-25 03:05:48 +00:00
return SUCCEEDED(toast->remove_Failed(failed_token_));
}
/*
/ Toast Event Handler
*/
ToastEventHandler::ToastEventHandler(Notification* notification)
2017-03-23 19:48:22 +00:00
: notification_(notification->GetWeakPtr()) {}
2017-03-23 19:48:22 +00:00
ToastEventHandler::~ToastEventHandler() {}
IFACEMETHODIMP ToastEventHandler::Invoke(
2017-03-23 19:48:22 +00:00
ABI::Windows::UI::Notifications::IToastNotification* sender,
IInspectable* args) {
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::Bind(&Notification::NotificationClicked, notification_));
2018-04-18 01:56:12 +00:00
if (IsDebuggingNotifications())
LOG(INFO) << "Notification clicked";
return S_OK;
}
IFACEMETHODIMP ToastEventHandler::Invoke(
2015-11-11 02:04:09 +00:00
ABI::Windows::UI::Notifications::IToastNotification* sender,
ABI::Windows::UI::Notifications::IToastDismissedEventArgs* e) {
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::Bind(&Notification::NotificationDismissed, notification_));
2018-04-18 01:56:12 +00:00
if (IsDebuggingNotifications())
LOG(INFO) << "Notification dismissed";
2017-12-14 00:31:02 +00:00
return S_OK;
}
IFACEMETHODIMP ToastEventHandler::Invoke(
ABI::Windows::UI::Notifications::IToastNotification* sender,
ABI::Windows::UI::Notifications::IToastFailedEventArgs* e) {
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::Bind(&Notification::NotificationFailed, notification_));
2018-04-18 01:56:12 +00:00
if (IsDebuggingNotifications())
LOG(INFO) << "Notification failed";
return S_OK;
}
2015-11-10 12:07:12 +00:00
} // namespace brightray