diff --git a/atom/browser/api/atom_api_notification.cc b/atom/browser/api/atom_api_notification.cc new file mode 100644 index 000000000000..4cedaa85d612 --- /dev/null +++ b/atom/browser/api/atom_api_notification.cc @@ -0,0 +1,126 @@ +// 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 + +#include "atom/browser/api/atom_api_menu.h" +#include "atom/browser/browser.h" +#include "atom/common/api/atom_api_native_image.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/threading/thread_task_runner_handle.h" +#include "native_mate/constructor.h" +#include "native_mate/dictionary.h" +#include "ui/gfx/image/image.h" + +namespace atom { + +namespace api { + +int id_counter = 1; +std::map notifications_; + +Notification::Notification(v8::Isolate* isolate, v8::Local wrapper, mate::Arguments* args) { + InitWith(isolate, wrapper); + + mate::Dictionary opts; + if (args->GetNext(&opts)) { + opts.Get("title", &title_); + opts.Get("body", &body_); + has_icon_ = opts.Get("icon", &icon_); + opts.Get("silent", &silent_); + opts.Get("replyPlaceholder", &reply_placeholder_); + opts.Get("hasReply", &has_reply_); + id_ = id_counter++; + } + notifications_[id_] = this; + OnInitialProps(); +} + +Notification::~Notification() {} + +// 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); +} + +bool Notification::HasID(int id) { + return notifications_.find(id) != notifications_.end(); +} + +Notification* Notification::FromID(int id) { + return notifications_[id]; +} + +// Getters +int Notification::GetID() { return id_; } +std::string Notification::GetTitle() { return title_; } +std::string Notification::GetBody() { return body_; } +bool Notification::GetSilent() { return silent_; } +std::string Notification::GetReplyPlaceholder() { return reply_placeholder_; } +bool Notification::GetHasReply() { return has_reply_; } + +// Setters +void Notification::SetTitle(std::string new_title) { title_ = new_title; NotifyPropsUpdated(); } +void Notification::SetBody(std::string new_body) { body_ = new_body; NotifyPropsUpdated(); } +void Notification::SetSilent(bool new_silent) { silent_ = new_silent; NotifyPropsUpdated(); } +void Notification::SetReplyPlaceholder(std::string new_reply_placeholder) { reply_placeholder_ = new_reply_placeholder; NotifyPropsUpdated(); } +void Notification::SetHasReply(bool new_has_reply) { has_reply_ = new_has_reply; NotifyPropsUpdated(); } + +void Notification::OnClicked() { + Emit("click"); +} + +void Notification::OnReplied(std::string reply) { + Emit("reply", reply); +} + +void Notification::OnShown() { + Emit("show"); +} + +// static +void Notification::BuildPrototype(v8::Isolate* isolate, + v8::Local prototype) { + prototype->SetClassName(mate::StringToV8(isolate, "Notification")); + mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) + .MakeDestroyable() + .SetMethod("show", &Notification::Show) + .SetProperty("id", &Notification::GetID) + .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 exports, v8::Local unused, + v8::Local 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()); +} + +} // namespace + +NODE_MODULE_CONTEXT_AWARE_BUILTIN(atom_common_notification, Initialize) diff --git a/atom/browser/api/atom_api_notification.h b/atom/browser/api/atom_api_notification.h new file mode 100644 index 000000000000..030dee21b49d --- /dev/null +++ b/atom/browser/api/atom_api_notification.h @@ -0,0 +1,78 @@ +// 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 +#include +#include + +#include "atom/browser/api/trackable_object.h" +#include "atom/browser/ui/notification_observer.h" +#include "native_mate/handle.h" +#include "ui/gfx/image/image.h" + +namespace atom { + +namespace api { + +class Notification : public mate::TrackableObject, + public NotifictionObserver { + public: + static mate::WrappableBase* New(mate::Arguments* args); + static bool HasID(int id); + static Notification* FromID(int id); + + static void BuildPrototype(v8::Isolate* isolate, + v8::Local prototype); + + // NotificationObserver: + void OnClicked() override; + void OnReplied(std::string reply) override; + void OnShown() override; + + protected: + Notification(v8::Isolate* isolate, v8::Local wrapper, mate::Arguments* args); + ~Notification() override; + + void OnInitialProps(); + void Show(); + + // Prop Getters + int GetID(); + std::string GetTitle(); + std::string GetBody(); + bool GetSilent(); + std::string GetReplyPlaceholder(); + bool GetHasReply(); + + // Prop Setters + void SetTitle(std::string new_title); + void SetBody(std::string new_body); + void SetSilent(bool new_silent); + void SetReplyPlaceholder(std::string new_reply_placeholder); + void SetHasReply(bool new_has_reply); + + void NotifyPropsUpdated(); + + private: + std::string title_ = ""; + std::string body_ = ""; + gfx::Image icon_; + bool has_icon_ = false; + bool silent_ = false; + std::string reply_placeholder_ = ""; + bool has_reply_ = false; + + int id_; + + DISALLOW_COPY_AND_ASSIGN(Notification); +}; + +} // namespace api + +} // namespace atom + +#endif // ATOM_BROWSER_API_ATOM_API_NOTIFICATION_H_ diff --git a/atom/browser/api/atom_api_notification_mac.mm b/atom/browser/api/atom_api_notification_mac.mm new file mode 100644 index 000000000000..1c3699aa21fa --- /dev/null +++ b/atom/browser/api/atom_api_notification_mac.mm @@ -0,0 +1,97 @@ +// 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" + +#import + +#include "atom/browser/browser.h" +#include "base/mac/mac_util.h" +#include "base/mac/scoped_nsobject.h" +#include "base/strings/sys_string_conversions.h" + +std::map> native_notifications_; + +@interface AtomNotificationCenter : NSObject { +} +@end + +@implementation AtomNotificationCenter +- (BOOL)userNotificationCenter:(NSUserNotificationCenter *)center + shouldPresentNotification:(NSUserNotification *)notification { + return YES; + } + +- (void)userNotificationCenter:(NSUserNotificationCenter *)center + didActivateNotification:(NSUserNotification *)notification { + int n_id = [[notification.userInfo objectForKey:@"id"] intValue]; + if (atom::api::Notification::HasID(n_id)) { + auto atomNotification = atom::api::Notification::FromID(n_id); + + if (notification.activationType == NSUserNotificationActivationTypeReplied){ + atomNotification->OnReplied([notification.response.string UTF8String]); + } else { + atomNotification->OnClicked(); + } + } + } +@end + +namespace atom { + +namespace api { + +AtomNotificationCenter* del = [[AtomNotificationCenter alloc] init]; +bool set_del_ = false; + +void Notification::Show() { + base::scoped_nsobject notification_ = native_notifications_[id_]; + [NSUserNotificationCenter.defaultUserNotificationCenter + deliverNotification:notification_]; + OnShown(); +} + +void Notification::OnInitialProps() { + if (!set_del_) { + [[NSUserNotificationCenter defaultUserNotificationCenter] setDelegate:del]; + set_del_ = true; + } + + base::scoped_nsobject notification_; + notification_.reset([[NSUserNotification alloc] init]); + + native_notifications_[id_] = notification_; + + NotifyPropsUpdated(); +} + +void Notification::NotifyPropsUpdated() { + base::scoped_nsobject notification_ = native_notifications_[id_]; + + [notification_ setTitle:base::SysUTF8ToNSString(title_)]; + [notification_ setInformativeText:base::SysUTF8ToNSString(body_)]; + + NSDictionary * userInfo = [NSMutableDictionary dictionary]; + [userInfo setValue:[NSNumber numberWithInt:id_] forKey:@"id"]; + [notification_ setUserInfo:userInfo]; + + if ([notification_ respondsToSelector:@selector(setContentImage:)] && has_icon_) { + [notification_ setContentImage:icon_.AsNSImage()]; + } + + if (has_reply_) { + [notification_ setResponsePlaceholder:base::SysUTF8ToNSString(reply_placeholder_)]; + [notification_ setHasReplyButton:true]; + } + + if (silent_) { + [notification_ setSoundName:nil]; + } else { + [notification_ setSoundName:NSUserNotificationDefaultSoundName]; + } +} + +} // namespace api + +} // namespace atom \ No newline at end of file diff --git a/atom/browser/ui/notification_observer.h b/atom/browser/ui/notification_observer.h new file mode 100644 index 000000000000..8619e5d3609b --- /dev/null +++ b/atom/browser/ui/notification_observer.h @@ -0,0 +1,22 @@ +// 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_UI_NOTIFICATION_OBSERVER_H_ +#define ATOM_BROWSER_UI_NOTIFICATION_OBSERVER_H_ + +namespace atom { + +class NotifictionObserver { + public: + virtual void OnClicked() {} + virtual void OnReplied(std::string reply) {} + virtual void OnShown() {} + + protected: + virtual ~NotifictionObserver() {} +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_NOTIFICATION_OBSERVER_H_ diff --git a/atom/common/node_bindings.cc b/atom/common/node_bindings.cc index 2a7be5762769..03e96ae8a4f3 100644 --- a/atom/common/node_bindings.cc +++ b/atom/common/node_bindings.cc @@ -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); diff --git a/default_app/default_app.js b/default_app/default_app.js index 2a3ce3a85ec1..446c159b6c1a 100644 --- a/default_app/default_app.js +++ b/default_app/default_app.js @@ -1,4 +1,4 @@ -const {app, BrowserWindow} = require('electron') +const {app, BrowserWindow, Notification} = require('electron') const path = require('path') let mainWindow = null @@ -27,5 +27,19 @@ exports.load = (appUrl) => { mainWindow = new BrowserWindow(options) mainWindow.loadURL(appUrl) mainWindow.focus() + + const n = new Notification({ + title: 'Hello World', + body: 'This is the long and complicated body for this notification that just goes on and on and on and never really seems to stop', + silent: true, + icon: '/Users/samuel/Downloads/ninja.png', + hasReply: true, + replyPlacehodler: 'Type Here!!' + }); + n.on('show', () => console.log('showed')); + n.on('click', () => console.info('clicked!!')); + n.on('reply', (e, reply) => console.log('Replied:', reply)); + + n.show(); }) } diff --git a/filenames.gypi b/filenames.gypi index 63b46cb40e57..e670f3c8f0da 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -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,9 @@ '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_notification_mac.mm', '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', @@ -310,6 +314,7 @@ 'atom/browser/ui/message_box_gtk.cc', 'atom/browser/ui/message_box_mac.mm', 'atom/browser/ui/message_box_win.cc', + 'atom/browser/ui/notification_observer.h', 'atom/browser/ui/tray_icon.cc', 'atom/browser/ui/tray_icon.h', 'atom/browser/ui/tray_icon_gtk.cc', diff --git a/lib/browser/api/module-list.js b/lib/browser/api/module-list.js index 64b2829064b6..d8b20c5bec19 100644 --- a/lib/browser/api/module-list.js +++ b/lib/browser/api/module-list.js @@ -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'}, diff --git a/lib/browser/api/notification.js b/lib/browser/api/notification.js new file mode 100644 index 000000000000..953f7d8d4987 --- /dev/null +++ b/lib/browser/api/notification.js @@ -0,0 +1,6 @@ +const {EventEmitter} = require('events') +const {Notification} = process.atomBinding('notification') + +Object.setPrototypeOf(Notification.prototype, EventEmitter.prototype) + +module.exports = Notification