diff --git a/brightray/brightray.gyp b/brightray/brightray.gyp index 184a60efa9..db04f5672c 100644 --- a/brightray/brightray.gyp +++ b/brightray/brightray.gyp @@ -161,6 +161,14 @@ ] }], # OS=="mac" ['OS=="win"', { + 'msvs_settings': { + 'VCLinkerTool': { + 'AdditionalDependencies': [ + 'runtimeobject.lib', + 'windowsapp.lib' + ], + }, + }, 'conditions': [ ['libchromiumcontent_component', { # sandbox, base_static, devtools_discovery, devtools_http_handler, @@ -195,6 +203,8 @@ 'msvs_settings': { 'VCLinkerTool': { 'AdditionalDependencies': [ + 'Shlwapi.lib', + 'Crypt32.lib', 'advapi32.lib', 'dbghelp.lib', 'delayimp.lib', diff --git a/brightray/browser/platform_notification_service_impl.cc b/brightray/browser/platform_notification_service_impl.cc index d2b0f679a6..db8572ee89 100644 --- a/brightray/browser/platform_notification_service_impl.cc +++ b/brightray/browser/platform_notification_service_impl.cc @@ -20,10 +20,8 @@ PlatformNotificationServiceImpl::PlatformNotificationServiceImpl() {} PlatformNotificationServiceImpl::~PlatformNotificationServiceImpl() {} NotificationPresenter* PlatformNotificationServiceImpl::notification_presenter() { -#if defined(OS_MACOSX) || defined(OS_LINUX) if (!notification_presenter_) notification_presenter_.reset(NotificationPresenter::Create()); -#endif return notification_presenter_.get(); } diff --git a/brightray/browser/win/notification_presenter_win.cc b/brightray/browser/win/notification_presenter_win.cc new file mode 100644 index 0000000000..8870de36e3 --- /dev/null +++ b/brightray/browser/win/notification_presenter_win.cc @@ -0,0 +1,67 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Copyright (c) 2015 Felix Rieseberg and Jason Poon . All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE-CHROMIUM file. + +#include "browser/win/notification_presenter_win.h" +#include "base/win/windows_version.h" +#include "content/public/browser/desktop_notification_delegate.h" +#include "content/public/common/platform_notification_data.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "common/application_info.h" + +#pragma comment(lib, "runtimeobject.lib") +#pragma comment(lib, "Crypt32.lib") + +using namespace WinToasts; +using namespace Microsoft::WRL; +using namespace ABI::Windows::UI::Notifications; +using namespace ABI::Windows::Data::Xml::Dom; +using namespace ABI::Windows::Foundation; + +namespace brightray { + +// static +NotificationPresenter* NotificationPresenter::Create() { + return new NotificationPresenterWin; +} + +NotificationPresenterWin::NotificationPresenterWin() { + m_lastNotification = nullptr; +} + +NotificationPresenterWin::~NotificationPresenterWin() { +} + +void NotificationPresenterWin::ShowNotification( + const content::PlatformNotificationData& data, + const SkBitmap& icon, + scoped_ptr delegate_ptr, + base::Closure* cancel_callback) { + + std::wstring title = data.title; + std::wstring body = data.body; + std::string iconPath = data.icon.spec(); + std::string appName = GetApplicationName(); + + // toast notification supported in version >= Windows 8 + // for prior versions, use Tray.displayBalloon + if (base::win::GetVersion() >= base::win::VERSION_WIN8) { + wtn = new WindowsToastNotification(appName.c_str(), delegate_ptr.release()); + wtn->ShowNotification(title.c_str(), body.c_str(), iconPath, m_lastNotification); + } + + if (cancel_callback) { + *cancel_callback = base::Bind( + &NotificationPresenterWin::RemoveNotification, + base::Unretained(this)); + } +} + +void NotificationPresenterWin::RemoveNotification() { + if (m_lastNotification != nullptr && wtn != NULL) { + wtn->DismissNotification(m_lastNotification); + } +} + +} // namespace brightray \ No newline at end of file diff --git a/brightray/browser/win/notification_presenter_win.h b/brightray/browser/win/notification_presenter_win.h new file mode 100644 index 0000000000..a2ecd81b95 --- /dev/null +++ b/brightray/browser/win/notification_presenter_win.h @@ -0,0 +1,51 @@ +// Copyright (c) 2012 The Chromium Authors. All rights reserved. +// Copyright (c) 2015 Felix Rieseberg and Jason Poon . All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE-CHROMIUM file. + +// Usage Example (JavaScript: +// var windowsNotification = new Notification("Test Title", { +// body: "Hi, I'm an example body. How are you?", +// icon: "file:///C:/Path/To/Your/Image.png" +// }); + +// windowsNotification.onshow = function () { console.log("Notification shown") }; +// windowsNotification.onclick = function () { console.log("Notification clicked") }; +// windowsNotification.onclose = function () { console.log("Notification dismissed") }; + + +#ifndef BRIGHTRAY_BROWSER_NOTIFICATION_PRESENTER_WIN_H_ +#define BRIGHTRAY_BROWSER_NOTIFICATION_PRESENTER_WIN_H_ + +#include "base/compiler_specific.h" +#include "browser/notification_presenter.h" +#include "windows_toast_notification.h" + +#include +#include +#include +#include + +namespace brightray { + +class NotificationPresenterWin : public NotificationPresenter { + public: + NotificationPresenterWin(); + ~NotificationPresenterWin(); + + void ShowNotification( + const content::PlatformNotificationData&, + const SkBitmap& icon, + scoped_ptr delegate, + base::Closure* cancel_callback) override; + + void RemoveNotification(); + + private: + WinToasts::WindowsToastNotification* wtn; + Microsoft::WRL::ComPtr m_lastNotification; +}; + +} // namespace + +#endif // BRIGHTRAY_BROWSER_NOTIFICATION_PRESENTER_WIN_H_ diff --git a/brightray/browser/win/windows_toast_notification.cc b/brightray/browser/win/windows_toast_notification.cc new file mode 100644 index 0000000000..5d306032d6 --- /dev/null +++ b/brightray/browser/win/windows_toast_notification.cc @@ -0,0 +1,373 @@ +// Copyright (c) 2015 Felix Rieseberg and Jason Poon . All rights reserved. +// Copyright (c) 2015 Ryan McShane and Brandon Smith +// 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 "windows_toast_notification.h" +#include "content/public/browser/desktop_notification_delegate.h" + +using namespace WinToasts; +using namespace Microsoft::WRL; +using namespace ABI::Windows::UI::Notifications; +using namespace ABI::Windows::Data::Xml::Dom; + +#define BREAK_IF_BAD(hr) if(!SUCCEEDED(hr)) break; + +namespace WinToasts { + +// Initialize Windows Runtime +static HRESULT init = Windows::Foundation::Initialize(RO_INIT_MULTITHREADED); + +WindowsToastNotification::WindowsToastNotification(const char* appName, content::DesktopNotificationDelegate* delegate) +{ + HSTRING toastNotifMgrStr = NULL; + HSTRING appId = NULL; + + HRESULT hr = CreateHString(RuntimeClass_Windows_UI_Notifications_ToastNotificationManager, &toastNotifMgrStr); + + hr = Windows::Foundation::GetActivationFactory(toastNotifMgrStr, &m_toastManager); + + WCHAR wAppName[MAX_PATH]; + swprintf(wAppName, ARRAYSIZE(wAppName), L"%S", appName); + hr = CreateHString(wAppName, &appId); + + m_toastManager->CreateToastNotifierWithId(appId, &m_toastNotifier); + + if (toastNotifMgrStr != NULL) { + WindowsDeleteString(toastNotifMgrStr); + } + + if (appId != NULL) { + WindowsDeleteString(appId); + } + + m_eventHandler = NULL; + n_delegate = delegate; +} + +WindowsToastNotification::~WindowsToastNotification() +{ + if (m_eventHandler) { + delete m_eventHandler; + } + + if (n_delegate) { + delete n_delegate; + } +} + +void WindowsToastNotification::ShowNotification(const WCHAR* title, const WCHAR* msg, std::string iconPath, ComPtr& toast) +{ + HRESULT hr; + HSTRING toastNotifStr = NULL; + + do { + ComPtr toastXml; + hr = GetToastXml(m_toastManager.Get(), title, msg, iconPath, &toastXml); + BREAK_IF_BAD(hr); + + hr = CreateHString(RuntimeClass_Windows_UI_Notifications_ToastNotification, &toastNotifStr); + BREAK_IF_BAD(hr); + + ComPtr toastFactory; + hr = Windows::Foundation::GetActivationFactory(toastNotifStr, &toastFactory); + BREAK_IF_BAD(hr); + + hr = toastFactory->CreateToastNotification(toastXml.Get(), &toast); + BREAK_IF_BAD(hr); + + hr = SetupCallbacks(toast.Get()); + BREAK_IF_BAD(hr); + + hr = m_toastNotifier->Show(toast.Get()); + BREAK_IF_BAD(hr); + + n_delegate->NotificationDisplayed(); + } while (FALSE); + + if (toastNotifStr != NULL) { + WindowsDeleteString(toastNotifStr); + } +} + +void WindowsToastNotification::DismissNotification(ComPtr toast) +{ + m_toastNotifier->Hide(toast.Get()); +} + +void WindowsToastNotification::NotificationClicked() +{ + delete this; +} + +void WindowsToastNotification::NotificationDismissed() +{ + delete this; +} + +HRESULT WindowsToastNotification::GetToastXml( + IToastNotificationManagerStatics* toastManager, + const WCHAR* title, + const WCHAR* msg, + std::string iconPath, + IXmlDocument** toastXml) { + + HRESULT hr; + ToastTemplateType templateType; + if (title == NULL || msg == NULL) { + // Single line toast + templateType = iconPath.length() == 0 ? ToastTemplateType_ToastText01 : ToastTemplateType_ToastImageAndText01; + hr = m_toastManager->GetTemplateContent(templateType, toastXml); + if (SUCCEEDED(hr)) { + const WCHAR* text = title != NULL ? title : msg; + hr = SetXmlText(*toastXml, text); + } + } else { + // Title and body toast + templateType = iconPath.length() == 0 ? ToastTemplateType_ToastText02 : ToastTemplateType_ToastImageAndText02; + hr = toastManager->GetTemplateContent(templateType, toastXml); + if (SUCCEEDED(hr)) { + hr = SetXmlText(*toastXml, title, msg); + } + } + + if (iconPath.length() != 0 && SUCCEEDED(hr)) { + // Toast has image + if (SUCCEEDED(hr)) { + hr = SetXmlImage(*toastXml, iconPath); + } + + // Don't stop a notification from showing just because an image couldn't be displayed. By default the app icon will be shown. + hr = S_OK; + } + + return hr; +} + +HRESULT WindowsToastNotification::SetXmlText(IXmlDocument* doc, const WCHAR* text) +{ + HSTRING tag = NULL; + + ComPtr nodeList; + HRESULT hr = GetTextNodeList(&tag, doc, &nodeList, 1); + do { + BREAK_IF_BAD(hr); + + ComPtr node; + hr = nodeList->Item(0, &node); + BREAK_IF_BAD(hr); + + hr = AppendTextToXml(doc, node.Get(), text); + } while (FALSE); + + if (tag != NULL) { + WindowsDeleteString(tag); + } + + return hr; +} + +HRESULT WindowsToastNotification::SetXmlText(IXmlDocument* doc, const WCHAR* title, const WCHAR* body) +{ + HSTRING tag = NULL; + ComPtr nodeList; + HRESULT hr = GetTextNodeList(&tag, doc, &nodeList, 2); + do { + BREAK_IF_BAD(hr); + + ComPtr node; + hr = nodeList->Item(0, &node); + BREAK_IF_BAD(hr); + + hr = AppendTextToXml(doc, node.Get(), title); + BREAK_IF_BAD(hr); + + hr = nodeList->Item(1, &node); + BREAK_IF_BAD(hr); + + hr = AppendTextToXml(doc, node.Get(), body); + } while (FALSE); + + if (tag != NULL) { + WindowsDeleteString(tag); + } + + return hr; +} + +HRESULT WindowsToastNotification::SetXmlImage(IXmlDocument* doc, std::string iconPath) +{ + HSTRING tag = NULL; + HSTRING src = NULL; + HSTRING imgPath = NULL; + HRESULT hr = CreateHString(L"image", &tag); + + do { + BREAK_IF_BAD(hr); + + ComPtr nodeList; + hr = doc->GetElementsByTagName(tag, &nodeList); + BREAK_IF_BAD(hr); + + ComPtr imageNode; + hr = nodeList->Item(0, &imageNode); + BREAK_IF_BAD(hr); + + ComPtr attrs; + hr = imageNode->get_Attributes(&attrs); + BREAK_IF_BAD(hr); + + hr = CreateHString(L"src", &src); + BREAK_IF_BAD(hr); + + ComPtr srcAttr; + hr = attrs->GetNamedItem(src, &srcAttr); + BREAK_IF_BAD(hr); + + WCHAR xmlPath[MAX_PATH]; + swprintf(xmlPath, ARRAYSIZE(xmlPath), L"%S", iconPath); + hr = CreateHString(xmlPath, &imgPath); + BREAK_IF_BAD(hr); + + ComPtr srcText; + hr = doc->CreateTextNode(imgPath, &srcText); + BREAK_IF_BAD(hr); + + ComPtr srcNode; + hr = srcText.As(&srcNode); + BREAK_IF_BAD(hr); + + ComPtr childNode; + hr = srcAttr->AppendChild(srcNode.Get(), &childNode); + } while (FALSE); + + if (tag != NULL) { + WindowsDeleteString(tag); + } + if (src != NULL) { + WindowsDeleteString(src); + } + if (imgPath != NULL) { + WindowsDeleteString(imgPath); + } + + return hr; +} + +HRESULT WindowsToastNotification::GetTextNodeList(HSTRING* tag, IXmlDocument* doc, IXmlNodeList** nodeList, UINT32 reqLength) +{ + HRESULT hr = CreateHString(L"text", tag); + do{ + BREAK_IF_BAD(hr); + + hr = doc->GetElementsByTagName(*tag, nodeList); + BREAK_IF_BAD(hr); + + UINT32 nodeLength; + hr = (*nodeList)->get_Length(&nodeLength); + BREAK_IF_BAD(hr); + + if (nodeLength < reqLength) { + hr = E_INVALIDARG; + } + } while (FALSE); + + if (!SUCCEEDED(hr)) { + // Allow the caller to delete this string on success + WindowsDeleteString(*tag); + } + + return hr; +} + +HRESULT WindowsToastNotification::AppendTextToXml(IXmlDocument* doc, IXmlNode* node, const WCHAR* text) +{ + HSTRING str = NULL; + HRESULT hr = CreateHString(text, &str); + do { + BREAK_IF_BAD(hr); + + ComPtr xmlText; + hr = doc->CreateTextNode(str, &xmlText); + BREAK_IF_BAD(hr); + + ComPtr textNode; + hr = xmlText.As(&textNode); + BREAK_IF_BAD(hr); + + ComPtr appendNode; + hr = node->AppendChild(textNode.Get(), &appendNode); + } while (FALSE); + + if (str != NULL) { + WindowsDeleteString(str); + } + + return hr; +} + +HRESULT WindowsToastNotification::SetupCallbacks(IToastNotification* toast) +{ + EventRegistrationToken activatedToken, dismissedToken; + m_eventHandler = new ToastEventHandler(this, n_delegate); + ComPtr eventHandler(m_eventHandler); + HRESULT hr = toast->add_Activated(eventHandler.Get(), &activatedToken); + + if (SUCCEEDED(hr)) { + hr = toast->add_Dismissed(eventHandler.Get(), &dismissedToken); + } + + return hr; +} + +HRESULT WindowsToastNotification::CreateHString(const WCHAR* source, HSTRING* dest) +{ + if (source == NULL || dest == NULL) { + return E_INVALIDARG; + } + + HRESULT hr = WindowsCreateString(source, wcslen(source), dest); + return hr; +} + +/* +/ Toast Event Handler +*/ +ToastEventHandler::ToastEventHandler(WindowsToastNotification* notification, content::DesktopNotificationDelegate* delegate) +{ + m_ref = 1; + m_notification = notification; + n_delegate = delegate; +} + +ToastEventHandler::~ToastEventHandler() +{ + // Empty +} + +IFACEMETHODIMP ToastEventHandler::Invoke(IToastNotification* sender, IInspectable* args) +{ + // Notification "activated" (clicked) + n_delegate->NotificationClick(); + + if (m_notification != NULL) { + m_notification->NotificationClicked(); + } + + return S_OK; +} + +IFACEMETHODIMP ToastEventHandler::Invoke(IToastNotification* sender, IToastDismissedEventArgs* e) +{ + // Notification dismissed + n_delegate->NotificationClosed(); + + if (m_notification != NULL) { + m_notification->NotificationDismissed(); + + } + + return S_OK; +} + +} //namespace \ No newline at end of file diff --git a/brightray/browser/win/windows_toast_notification.h b/brightray/browser/win/windows_toast_notification.h new file mode 100644 index 0000000000..474d55c767 --- /dev/null +++ b/brightray/browser/win/windows_toast_notification.h @@ -0,0 +1,97 @@ +// Copyright (c) 2015 Felix Rieseberg and Jason Poon . All rights reserved. +// Copyright (c) 2015 Ryan McShane and Brandon Smith +// 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. + +#ifndef __WINDOWS_TOAST_NOTIFICATION_H__ +#define __WINDOWS_TOAST_NOTIFICATION_H__ + +#include "content/public/browser/desktop_notification_delegate.h" +#include "content/public/common/platform_notification_data.h" +#include "base/bind.h" + +#include +#include +#include + +using namespace Microsoft::WRL; +using namespace ABI::Windows::UI::Notifications; +using namespace ABI::Windows::Foundation; + +namespace WinToasts { + + typedef ITypedEventHandler DesktopToastActivatedEventHandler; + typedef ITypedEventHandler DesktopToastDismissedEventHandler; + + class ToastEventHandler; + + class WindowsToastNotification + { + public: + WindowsToastNotification(const char* appName, content::DesktopNotificationDelegate* delegate); + ~WindowsToastNotification(); + void ShowNotification(const WCHAR* title, const WCHAR* msg, std::string iconPath, ComPtr& toast); + void DismissNotification(ComPtr toast); + void NotificationClicked(); + void NotificationDismissed(); + + private: + ToastEventHandler* m_eventHandler; + + content::DesktopNotificationDelegate* n_delegate; + ComPtr m_toastManager; + ComPtr m_toastNotifier; + + HRESULT GetToastXml(IToastNotificationManagerStatics* toastManager, const WCHAR* title, const WCHAR* msg, std::string iconPath, ABI::Windows::Data::Xml::Dom::IXmlDocument** toastXml); + HRESULT SetXmlText(ABI::Windows::Data::Xml::Dom::IXmlDocument* doc, const WCHAR* text); + HRESULT SetXmlText(ABI::Windows::Data::Xml::Dom::IXmlDocument* doc, const WCHAR* title, const WCHAR* body); + HRESULT SetXmlImage(ABI::Windows::Data::Xml::Dom::IXmlDocument* doc, std::string iconPath); + HRESULT GetTextNodeList(HSTRING* tag, ABI::Windows::Data::Xml::Dom::IXmlDocument* doc, ABI::Windows::Data::Xml::Dom::IXmlNodeList** nodeList, UINT32 reqLength); + HRESULT AppendTextToXml(ABI::Windows::Data::Xml::Dom::IXmlDocument* doc, ABI::Windows::Data::Xml::Dom::IXmlNode* node, const WCHAR* text); + HRESULT SetupCallbacks(IToastNotification* toast); + HRESULT CreateHString(const WCHAR* source, HSTRING* dest); + }; + + + class ToastEventHandler : + public Implements + { + public: + ToastEventHandler(WindowsToastNotification* notification, content::DesktopNotificationDelegate* delegate); + ~ToastEventHandler(); + IFACEMETHODIMP Invoke(IToastNotification* sender, IInspectable* args); + IFACEMETHODIMP Invoke(IToastNotification* sender, IToastDismissedEventArgs* e); + IFACEMETHODIMP_(ULONG) AddRef() { return InterlockedIncrement(&m_ref); } + + IFACEMETHODIMP_(ULONG) Release() { + ULONG l = InterlockedDecrement(&m_ref); + if (l == 0) delete this; + return l; + } + + IFACEMETHODIMP QueryInterface(_In_ REFIID riid, _COM_Outptr_ void **ppv) { + if (IsEqualIID(riid, IID_IUnknown)) + *ppv = static_cast(static_cast(this)); + else if (IsEqualIID(riid, __uuidof(DesktopToastActivatedEventHandler))) + *ppv = static_cast(this); + else if (IsEqualIID(riid, __uuidof(DesktopToastDismissedEventHandler))) + *ppv = static_cast(this); + else *ppv = nullptr; + + if (*ppv) { + reinterpret_cast(*ppv)->AddRef(); + return S_OK; + } + + return E_NOINTERFACE; + } + + private: + ULONG m_ref; + WindowsToastNotification* m_notification; + content::DesktopNotificationDelegate* n_delegate; + }; + +} // namespace + +#endif //__WINDOWS_TOAST_NOTIFICATION_H__ \ No newline at end of file diff --git a/brightray/filenames.gypi b/brightray/filenames.gypi index bbde28694d..84dc4291ed 100644 --- a/brightray/filenames.gypi +++ b/brightray/filenames.gypi @@ -63,6 +63,10 @@ 'browser/platform_notification_service_impl.h', 'browser/linux/notification_presenter_linux.h', 'browser/linux/notification_presenter_linux.cc', + 'browser/win/notification_presenter_win.h', + 'browser/win/notification_presenter_win.cc', + 'browser/win/windows_toast_notification.h', + 'browser/win/windows_toast_notification.cc', 'browser/special_storage_policy.cc', 'browser/special_storage_policy.h', 'browser/url_request_context_getter.cc',