Merge pull request #9269 from electron/main-notifications

Notifications from the main process
This commit is contained in:
Cheng Zhao 2017-05-31 17:21:05 +09:00 committed by GitHub
commit e82af41591
22 changed files with 438 additions and 25 deletions

View file

@ -0,0 +1,174 @@
// Copyright (c) 2014 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/api/atom_api_notification.h"
#include "atom/browser/api/atom_api_menu.h"
#include "atom/browser/browser.h"
#include "atom/common/native_mate_converters/gfx_converter.h"
#include "atom/common/native_mate_converters/image_converter.h"
#include "atom/common/native_mate_converters/string16_converter.h"
#include "atom/common/node_includes.h"
#include "base/strings/utf_string_conversions.h"
#include "brightray/browser/browser_client.h"
#include "native_mate/constructor.h"
#include "native_mate/dictionary.h"
#include "url/gurl.h"
namespace atom {
namespace api {
Notification::Notification(v8::Isolate* isolate,
v8::Local<v8::Object> wrapper,
mate::Arguments* args) {
InitWith(isolate, wrapper);
presenter_ = brightray::BrowserClient::Get()->GetNotificationPresenter();
mate::Dictionary opts;
if (args->GetNext(&opts)) {
opts.Get("title", &title_);
opts.Get("body", &body_);
has_icon_ = opts.Get("icon", &icon_);
if (has_icon_) {
opts.Get("icon", &icon_path_);
}
opts.Get("silent", &silent_);
opts.Get("replyPlaceholder", &reply_placeholder_);
opts.Get("hasReply", &has_reply_);
}
}
Notification::~Notification() {
if (notification_)
notification_->set_delegate(nullptr);
}
// static
mate::WrappableBase* Notification::New(mate::Arguments* args) {
if (!Browser::Get()->is_ready()) {
args->ThrowError("Cannot create Notification before app is ready");
return nullptr;
}
return new Notification(args->isolate(), args->GetThis(), args);
}
// Getters
base::string16 Notification::GetTitle() {
return title_;
}
base::string16 Notification::GetBody() {
return body_;
}
bool Notification::GetSilent() {
return silent_;
}
base::string16 Notification::GetReplyPlaceholder() {
return reply_placeholder_;
}
bool Notification::GetHasReply() {
return has_reply_;
}
// Setters
void Notification::SetTitle(const base::string16& new_title) {
title_ = new_title;
}
void Notification::SetBody(const base::string16& new_body) {
body_ = new_body;
}
void Notification::SetSilent(bool new_silent) {
silent_ = new_silent;
}
void Notification::SetReplyPlaceholder(const base::string16& new_placeholder) {
reply_placeholder_ = new_placeholder;
}
void Notification::SetHasReply(bool new_has_reply) {
has_reply_ = new_has_reply;
}
void Notification::NotificationClick() {
Emit("click");
}
void Notification::NotificationReplied(const std::string& reply) {
Emit("reply", reply);
}
void Notification::NotificationDisplayed() {
Emit("show");
}
void Notification::NotificationDestroyed() {
Emit("close");
}
void Notification::NotificationClosed() {
}
// Showing notifications
void Notification::Show() {
if (presenter_) {
notification_ = presenter_->CreateNotification(this);
if (notification_) {
notification_->Show(title_, body_, "", GURL(), icon_.AsBitmap(), silent_,
has_reply_, reply_placeholder_);
}
}
}
bool Notification::IsSupported() {
return !!brightray::BrowserClient::Get()->GetNotificationPresenter();
}
// static
void Notification::BuildPrototype(v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> prototype) {
prototype->SetClassName(mate::StringToV8(isolate, "Notification"));
mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
.MakeDestroyable()
.SetMethod("show", &Notification::Show)
.SetProperty("title", &Notification::GetTitle, &Notification::SetTitle)
.SetProperty("body", &Notification::GetBody, &Notification::SetBody)
.SetProperty("silent", &Notification::GetSilent, &Notification::SetSilent)
.SetProperty("replyPlaceholder", &Notification::GetReplyPlaceholder,
&Notification::SetReplyPlaceholder)
.SetProperty("hasReply", &Notification::GetHasReply,
&Notification::SetHasReply);
}
} // namespace api
} // namespace atom
namespace {
using atom::api::Notification;
void Initialize(v8::Local<v8::Object> exports,
v8::Local<v8::Value> unused,
v8::Local<v8::Context> context,
void* priv) {
v8::Isolate* isolate = context->GetIsolate();
Notification::SetConstructor(isolate, base::Bind(&Notification::New));
mate::Dictionary dict(isolate, exports);
dict.Set("Notification",
Notification::GetConstructor(isolate)->GetFunction());
dict.SetMethod("isSupported", &Notification::IsSupported);
}
} // namespace
NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_common_notification, Initialize)

View file

@ -0,0 +1,83 @@
// Copyright (c) 2014 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_API_ATOM_API_NOTIFICATION_H_
#define ATOM_BROWSER_API_ATOM_API_NOTIFICATION_H_
#include <memory>
#include <string>
#include <vector>
#include "atom/browser/api/trackable_object.h"
#include "base/strings/utf_string_conversions.h"
#include "brightray/browser/notification.h"
#include "brightray/browser/notification_delegate.h"
#include "brightray/browser/notification_presenter.h"
#include "native_mate/handle.h"
#include "ui/gfx/image/image.h"
namespace atom {
namespace api {
class Notification : public mate::TrackableObject<Notification>,
public brightray::NotificationDelegate {
public:
static mate::WrappableBase* New(mate::Arguments* args);
static bool IsSupported();
static void BuildPrototype(v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> prototype);
// NotificationDelegate:
void NotificationClick() override;
void NotificationReplied(const std::string& reply) override;
void NotificationDisplayed() override;
void NotificationDestroyed() override;
void NotificationClosed() override;
protected:
Notification(v8::Isolate* isolate,
v8::Local<v8::Object> wrapper,
mate::Arguments* args);
~Notification() override;
void Show();
// Prop Getters
base::string16 GetTitle();
base::string16 GetBody();
bool GetSilent();
base::string16 GetReplyPlaceholder();
bool GetHasReply();
// Prop Setters
void SetTitle(const base::string16& new_title);
void SetBody(const base::string16& new_body);
void SetSilent(bool new_silent);
void SetReplyPlaceholder(const base::string16& new_reply_placeholder);
void SetHasReply(bool new_has_reply);
private:
base::string16 title_;
base::string16 body_;
gfx::Image icon_;
base::string16 icon_path_;
bool has_icon_ = false;
bool silent_ = false;
base::string16 reply_placeholder_;
bool has_reply_ = false;
brightray::NotificationPresenter* presenter_;
base::WeakPtr<brightray::Notification> notification_;
DISALLOW_COPY_AND_ASSIGN(Notification);
};
} // namespace api
} // namespace atom
#endif // ATOM_BROWSER_API_ATOM_API_NOTIFICATION_H_

View file

@ -56,6 +56,7 @@ REFERENCE_MODULE(atom_common_asar);
REFERENCE_MODULE(atom_common_clipboard);
REFERENCE_MODULE(atom_common_crash_reporter);
REFERENCE_MODULE(atom_common_native_image);
REFERENCE_MODULE(atom_common_notification);
REFERENCE_MODULE(atom_common_screen);
REFERENCE_MODULE(atom_common_shell);
REFERENCE_MODULE(atom_common_v8_util);

View file

@ -36,7 +36,7 @@ BrowserClient::~BrowserClient() {
NotificationPresenter* BrowserClient::GetNotificationPresenter() {
if (!notification_presenter_) {
// Create a new presenter if on OS X, Linux, or Windows 8+
// Create a new presenter if on OS X, Linux, or Windows 7+
notification_presenter_.reset(NotificationPresenter::Create());
}
return notification_presenter_.get();

View file

@ -88,7 +88,9 @@ void LibnotifyNotification::Show(const base::string16& title,
const std::string& tag,
const GURL& icon_url,
const SkBitmap& icon,
const bool silent) {
bool silent,
bool has_reply,
const base::string16& reply_placeholder) {
notification_ = libnotify_loader_.notify_notification_new(
base::UTF16ToUTF8(title).c_str(),
base::UTF16ToUTF8(body).c_str(),
@ -137,6 +139,7 @@ void LibnotifyNotification::Show(const base::string16& title,
return;
}
if (delegate())
delegate()->NotificationDisplayed();
}

View file

@ -27,7 +27,9 @@ class LibnotifyNotification : public Notification {
const std::string& tag,
const GURL& icon_url,
const SkBitmap& icon,
const bool silent) override;
bool silent,
bool has_reply,
const base::string16& reply_placeholder) override;
void Dismiss() override;
private:

View file

@ -26,10 +26,13 @@ class CocoaNotification : public Notification {
const std::string& tag,
const GURL& icon_url,
const SkBitmap& icon,
const bool silent) override;
bool silent,
const bool has_reply,
const base::string16& reply_placeholder) override;
void Dismiss() override;
void NotificationDisplayed();
void NotificationReplied(const std::string& reply);
NSUserNotification* notification() const { return notification_; }

View file

@ -28,7 +28,9 @@ void CocoaNotification::Show(const base::string16& title,
const std::string& tag,
const GURL& icon_url,
const SkBitmap& icon,
const bool silent) {
bool silent,
bool has_reply,
const base::string16& reply_placeholder) {
notification_.reset([[NSUserNotification alloc] init]);
[notification_ setTitle:base::SysUTF16ToNSString(title)];
[notification_ setInformativeText:base::SysUTF16ToNSString(body)];
@ -46,6 +48,11 @@ void CocoaNotification::Show(const base::string16& title,
[notification_ setSoundName:NSUserNotificationDefaultSoundName];
}
if (has_reply) {
[notification_ setResponsePlaceholder:base::SysUTF16ToNSString(reply_placeholder)];
[notification_ setHasReplyButton:true];
}
[NSUserNotificationCenter.defaultUserNotificationCenter
deliverNotification:notification_];
}
@ -58,7 +65,13 @@ void CocoaNotification::Dismiss() {
}
void CocoaNotification::NotificationDisplayed() {
if (delegate())
delegate()->NotificationDisplayed();
}
void CocoaNotification::NotificationReplied(const std::string& reply) {
if (delegate())
delegate()->NotificationReplied(reply);
}
} // namespace brightray

View file

@ -28,9 +28,14 @@
- (void)userNotificationCenter:(NSUserNotificationCenter*)center
didActivateNotification:(NSUserNotification *)notif {
auto notification = presenter_->GetNotification(notif);
if (notification)
if (notification) {
if (notif.activationType == NSUserNotificationActivationTypeReplied){
notification->NotificationReplied([notif.response.string UTF8String]);
} else {
notification->NotificationClicked();
}
}
}
- (BOOL)userNotificationCenter:(NSUserNotificationCenter*)center
shouldPresentNotification:(NSUserNotification*)notification {

View file

@ -17,20 +17,24 @@ Notification::Notification(NotificationDelegate* delegate,
}
Notification::~Notification() {
if (delegate())
delegate()->NotificationDestroyed();
}
void Notification::NotificationClicked() {
if (delegate())
delegate()->NotificationClick();
Destroy();
}
void Notification::NotificationDismissed() {
if (delegate())
delegate()->NotificationClosed();
Destroy();
}
void Notification::NotificationFailed() {
if (delegate())
delegate()->NotificationFailed();
Destroy();
}

View file

@ -20,13 +20,17 @@ class NotificationPresenter;
class Notification {
public:
virtual ~Notification();
// Shows the notification.
virtual void Show(const base::string16& title,
const base::string16& msg,
const std::string& tag,
const GURL& icon_url,
const SkBitmap& icon,
const bool silent) = 0;
bool silent,
bool has_reply,
const base::string16& reply_placeholder) = 0;
// Closes the notification, this instance will be destroyed after the
// notification gets closed.
virtual void Dismiss() = 0;
@ -43,6 +47,7 @@ class Notification {
return weak_factory_.GetWeakPtr();
}
void set_delegate(NotificationDelegate* delegate) { delegate_ = delegate; }
NotificationDelegate* delegate() const { return delegate_; }
NotificationPresenter* presenter() const { return presenter_; }
@ -50,9 +55,6 @@ class Notification {
Notification(NotificationDelegate* delegate,
NotificationPresenter* presenter);
public:
virtual ~Notification();
private:
NotificationDelegate* delegate_;
NotificationPresenter* presenter_;

View file

@ -5,6 +5,8 @@
#ifndef BRIGHTRAY_BROWSER_NOTIFICATION_DELEGATE_H_
#define BRIGHTRAY_BROWSER_NOTIFICATION_DELEGATE_H_
#include <string>
#include "content/public/browser/desktop_notification_delegate.h"
namespace brightray {
@ -16,6 +18,9 @@ class NotificationDelegate : public content::DesktopNotificationDelegate {
// Failed to send the notification.
virtual void NotificationFailed() {}
// Notification was replied to
virtual void NotificationReplied(const std::string& reply) {}
};
} // namespace brightray

View file

@ -4,6 +4,7 @@
#include "brightray/browser/platform_notification_service.h"
#include "base/strings/utf_string_conversions.h"
#include "brightray/browser/browser_client.h"
#include "brightray/browser/notification.h"
#include "brightray/browser/notification_delegate_adapter.h"
@ -30,7 +31,8 @@ void OnWebNotificationAllowed(base::WeakPtr<Notification> notification,
return;
if (allowed)
notification->Show(data.title, data.body, data.tag, data.icon, icon,
audio_muted ? true : data.silent);
audio_muted ? true : data.silent, false,
base::UTF8ToUTF16(""));
else
notification->Destroy();
}

View file

@ -12,7 +12,8 @@ namespace brightray {
void Win32Notification::Show(
const base::string16& title, const base::string16& msg,
const std::string& tag, const GURL& icon_url,
const SkBitmap& icon, const bool silent) {
const SkBitmap& icon, bool silent,
bool has_reply, const base::string16& reply_placeholder) {
auto presenter = static_cast<NotificationPresenterWin7*>(this->presenter());
if (!presenter) return;

View file

@ -12,7 +12,8 @@ class Win32Notification : public brightray::Notification {
}
void Show(const base::string16& title, const base::string16& msg,
const std::string& tag, const GURL& icon_url,
const SkBitmap& icon, const bool silent) override;
const SkBitmap& icon, bool silent,
bool has_reply, const base::string16& reply_placeholder) override;
void Dismiss() override;
const DesktopNotificationController::Notification& GetRef() const {

View file

@ -89,7 +89,9 @@ void WindowsToastNotification::Show(const base::string16& title,
const std::string& tag,
const GURL& icon_url,
const SkBitmap& icon,
const bool silent) {
bool silent,
bool has_reply,
const base::string16& reply_placeholder) {
auto presenter_win = static_cast<NotificationPresenterWin*>(presenter());
std::wstring icon_path = presenter_win->SaveIconToFilesystem(icon, icon_url);
@ -131,6 +133,7 @@ void WindowsToastNotification::Show(const base::string16& title,
return;
}
if (delegate())
delegate()->NotificationDisplayed();
}
@ -144,7 +147,7 @@ bool WindowsToastNotification::GetToastXml(
const std::wstring& title,
const std::wstring& msg,
const std::wstring& icon_path,
const bool silent,
bool silent,
IXmlDocument** toast_xml) {
ABI::Windows::UI::Notifications::ToastTemplateType template_type;
if (title.empty() || msg.empty()) {

View file

@ -55,7 +55,9 @@ class WindowsToastNotification : public Notification {
const std::string& tag,
const GURL& icon_url,
const SkBitmap& icon,
const bool silent) override;
bool silent,
bool has_reply,
const base::string16& reply_placeholder) override;
void Dismiss() override;
private:

95
docs/api/notification.md Normal file
View file

@ -0,0 +1,95 @@
# Notification
> Create OS desktop notifications
Process: [Main](../glossary.md#main-process)
## Using in the renderer process
If you want to show Notifications from a renderer process you should use the [HTML5 Notification API](../tutorial/notifications.md)
## Class: Notification
> Create OS desktop notifications
Process: [Main](../glossary.md#main-process)
`Notification` is an
[EventEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter).
It creates a new `Notification` with native properties as set by the `options`.
### Static Methods
The `Notification` class has the following static methods:
#### `BrowserWindow.isSupported()`
Returns `Boolean` - Whether or not desktop notifications are supported on the current system
### `new Notification([options])` _Experimental_
* `options` Object
* `title` String - A title for the notification, which will be shown at the top of the notification window when it is shown
* `body` String - The body text of the notification, which will be displayed below the title
* `silent` Boolean - (Optional) Whether or not to emit an OS notification noise when showing the notification
* `icon` [NativeImage](native-image.md) - (Optional) An icon to use in the notification
* `hasReply` Boolean - (Optional) Whether or not to add an inline reply option to the notification. _macOS_
* `replyPlaceholder` String - (Optional) The placeholder to write in the inline reply input field. _macOS_
### Instance Events
Objects created with `new Notification` emit the following events:
**Note:** Some events are only available on specific operating systems and are
labeled as such.
#### Event: 'show'
Returns:
* `event` Event
Emitted when the notification is shown to the user, note this could be fired
multiple times as a notification can be shown multiple times through the
`show()` method.
#### Event: 'click'
Returns:
* `event` Event
Emitted when the notification is clicked by the user.
#### Event: 'close'
Returns:
* `event` Event
Emitted when the notification is closed by manual intervention from the user.
This event is not guarunteed to be emitted in all cases where the notification
is closed.
#### Event: 'reply' _macOS_
Returns:
* `event` Event
* `reply` String - The string the user entered into the inline reply field
Emitted when the user clicks the "Reply" button on a notification with `hasReply: true`.
### Instance Methods
Objects created with `new Notification` have the following instance methods:
#### `notification.show()`
Immediately shows the notification to the user, please note this means unlike the
HTML5 Notification implementation, simply instantiating a `new Notification` does
not immediately show it to the user, you need to call this method before the OS
will display it.

View file

@ -5,7 +5,9 @@ to the user. Electron conveniently allows developers to send notifications with
the [HTML5 Notification API](https://notifications.spec.whatwg.org/), using
the currently running operating system's native notification APIs to display it.
**Note:** Since this is an HTML5 API it is only available in the renderer process.
**Note:** Since this is an HTML5 API it is only available in the renderer process. If
you want to show Notifications in the main process please check out the
[Notification](../api/notification.md) module.
```javascript
let myNotification = new Notification('Title', {

View file

@ -26,6 +26,7 @@
'lib/browser/api/module-list.js',
'lib/browser/api/navigation-controller.js',
'lib/browser/api/net.js',
'lib/browser/api/notification.js',
'lib/browser/api/power-monitor.js',
'lib/browser/api/power-save-blocker.js',
'lib/browser/api/protocol.js',
@ -127,6 +128,8 @@
'atom/browser/api/atom_api_menu_views.h',
'atom/browser/api/atom_api_net.cc',
'atom/browser/api/atom_api_net.h',
'atom/browser/api/atom_api_notification.cc',
'atom/browser/api/atom_api_notification.h',
'atom/browser/api/atom_api_power_monitor.cc',
'atom/browser/api/atom_api_power_monitor.h',
'atom/browser/api/atom_api_power_save_blocker.cc',

View file

@ -11,6 +11,7 @@ module.exports = [
{name: 'Menu', file: 'menu'},
{name: 'MenuItem', file: 'menu-item'},
{name: 'net', file: 'net'},
{name: 'Notification', file: 'notification'},
{name: 'powerMonitor', file: 'power-monitor'},
{name: 'powerSaveBlocker', file: 'power-save-blocker'},
{name: 'protocol', file: 'protocol'},

View file

@ -0,0 +1,8 @@
const {EventEmitter} = require('events')
const {Notification, isSupported} = process.atomBinding('notification')
Object.setPrototypeOf(Notification.prototype, EventEmitter.prototype)
Notification.isSupported = isSupported
module.exports = Notification