refactor: rename the atom directory to shell

This commit is contained in:
Samuel Attard 2019-06-19 13:43:10 -07:00 committed by Samuel Attard
parent 4575a4aae3
commit d7f07e8a80
631 changed files with 0 additions and 0 deletions

View file

@ -0,0 +1,176 @@
// Copyright (c) 2015 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/notifications/linux/libnotify_notification.h"
#include <set>
#include <string>
#include <vector>
#include "atom/browser/notifications/notification_delegate.h"
#include "atom/common/application_info.h"
#include "atom/common/platform_util.h"
#include "base/files/file_enumerator.h"
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ui/libgtkui/gtk_util.h"
#include "chrome/browser/ui/libgtkui/skia_utils_gtk.h"
#include "third_party/skia/include/core/SkBitmap.h"
namespace atom {
namespace {
LibNotifyLoader libnotify_loader_;
const std::set<std::string>& GetServerCapabilities() {
static std::set<std::string> caps;
if (caps.empty()) {
auto* capabilities = libnotify_loader_.notify_get_server_caps();
for (auto* l = capabilities; l != nullptr; l = l->next)
caps.insert(static_cast<const char*>(l->data));
g_list_free_full(capabilities, g_free);
}
return caps;
}
bool HasCapability(const std::string& capability) {
return GetServerCapabilities().count(capability) != 0;
}
bool NotifierSupportsActions() {
if (getenv("ELECTRON_USE_UBUNTU_NOTIFIER"))
return false;
return HasCapability("actions");
}
void log_and_clear_error(GError* error, const char* context) {
LOG(ERROR) << context << ": domain=" << error->domain
<< " code=" << error->code << " message=\"" << error->message
<< '"';
g_error_free(error);
}
} // namespace
// static
bool LibnotifyNotification::Initialize() {
if (!libnotify_loader_.Load("libnotify.so.4") && // most common one
!libnotify_loader_.Load("libnotify.so.5") &&
!libnotify_loader_.Load("libnotify.so.1") &&
!libnotify_loader_.Load("libnotify.so")) {
LOG(WARNING) << "Unable to find libnotify; notifications disabled";
return false;
}
if (!libnotify_loader_.notify_is_initted() &&
!libnotify_loader_.notify_init(GetApplicationName().c_str())) {
LOG(WARNING) << "Unable to initialize libnotify; notifications disabled";
return false;
}
return true;
}
LibnotifyNotification::LibnotifyNotification(NotificationDelegate* delegate,
NotificationPresenter* presenter)
: Notification(delegate, presenter), notification_(nullptr) {}
LibnotifyNotification::~LibnotifyNotification() {
if (notification_) {
g_signal_handlers_disconnect_by_data(notification_, this);
g_object_unref(notification_);
}
}
void LibnotifyNotification::Show(const NotificationOptions& options) {
notification_ = libnotify_loader_.notify_notification_new(
base::UTF16ToUTF8(options.title).c_str(),
base::UTF16ToUTF8(options.msg).c_str(), nullptr);
g_signal_connect(notification_, "closed",
G_CALLBACK(OnNotificationClosedThunk), this);
// NB: On Unity and on any other DE using Notify-OSD, adding a notification
// action will cause the notification to display as a modal dialog box.
if (NotifierSupportsActions()) {
libnotify_loader_.notify_notification_add_action(
notification_, "default", "View", OnNotificationViewThunk, this,
nullptr);
}
if (!options.icon.drawsNothing()) {
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);
}
if (!options.tag.empty()) {
GQuark id = g_quark_from_string(options.tag.c_str());
g_object_set(G_OBJECT(notification_), "id", id, NULL);
}
// Always try to append notifications.
// Unique tags can be used to prevent this.
if (HasCapability("append")) {
libnotify_loader_.notify_notification_set_hint_string(notification_,
"append", "true");
} else if (HasCapability("x-canonical-append")) {
libnotify_loader_.notify_notification_set_hint_string(
notification_, "x-canonical-append", "true");
}
// Send the desktop name to identify the application
// The desktop-entry is the part before the .desktop
std::string desktop_id;
if (platform_util::GetDesktopName(&desktop_id)) {
const std::string suffix{".desktop"};
if (base::EndsWith(desktop_id, suffix,
base::CompareCase::INSENSITIVE_ASCII)) {
desktop_id.resize(desktop_id.size() - suffix.size());
}
libnotify_loader_.notify_notification_set_hint_string(
notification_, "desktop-entry", desktop_id.c_str());
}
GError* error = nullptr;
libnotify_loader_.notify_notification_show(notification_, &error);
if (error) {
log_and_clear_error(error, "notify_notification_show");
NotificationFailed();
return;
}
if (delegate())
delegate()->NotificationDisplayed();
}
void LibnotifyNotification::Dismiss() {
if (!notification_) {
Destroy();
return;
}
GError* error = nullptr;
libnotify_loader_.notify_notification_close(notification_, &error);
if (error) {
log_and_clear_error(error, "notify_notification_close");
Destroy();
}
}
void LibnotifyNotification::OnNotificationClosed(
NotifyNotification* notification) {
NotificationDismissed();
}
void LibnotifyNotification::OnNotificationView(NotifyNotification* notification,
char* action) {
NotificationClicked();
}
} // namespace atom

View file

@ -0,0 +1,47 @@
// Copyright (c) 2015 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_NOTIFICATIONS_LINUX_LIBNOTIFY_NOTIFICATION_H_
#define ATOM_BROWSER_NOTIFICATIONS_LINUX_LIBNOTIFY_NOTIFICATION_H_
#include <string>
#include <vector>
#include "atom/browser/notifications/notification.h"
#include "library_loaders/libnotify_loader.h"
#include "ui/base/glib/glib_signal.h"
namespace atom {
class LibnotifyNotification : public Notification {
public:
LibnotifyNotification(NotificationDelegate* delegate,
NotificationPresenter* presenter);
~LibnotifyNotification() override;
static bool Initialize();
// Notification:
void Show(const NotificationOptions& options) override;
void Dismiss() override;
private:
CHROMEG_CALLBACK_0(LibnotifyNotification,
void,
OnNotificationClosed,
NotifyNotification*);
CHROMEG_CALLBACK_1(LibnotifyNotification,
void,
OnNotificationView,
NotifyNotification*,
char*);
NotifyNotification* notification_;
DISALLOW_COPY_AND_ASSIGN(LibnotifyNotification);
};
} // namespace atom
#endif // ATOM_BROWSER_NOTIFICATIONS_LINUX_LIBNOTIFY_NOTIFICATION_H_

View file

@ -0,0 +1,28 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Copyright (c) 2013 Patrick Reynolds <piki@github.com>. 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 "atom/browser/notifications/linux/notification_presenter_linux.h"
#include "atom/browser/notifications/linux/libnotify_notification.h"
namespace atom {
// static
NotificationPresenter* NotificationPresenter::Create() {
if (!LibnotifyNotification::Initialize())
return nullptr;
return new NotificationPresenterLinux;
}
NotificationPresenterLinux::NotificationPresenterLinux() {}
NotificationPresenterLinux::~NotificationPresenterLinux() {}
Notification* NotificationPresenterLinux::CreateNotificationObject(
NotificationDelegate* delegate) {
return new LibnotifyNotification(delegate, this);
}
} // namespace atom

View file

@ -0,0 +1,27 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Copyright (c) 2013 Patrick Reynolds <piki@github.com>. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-CHROMIUM file.
#ifndef ATOM_BROWSER_NOTIFICATIONS_LINUX_NOTIFICATION_PRESENTER_LINUX_H_
#define ATOM_BROWSER_NOTIFICATIONS_LINUX_NOTIFICATION_PRESENTER_LINUX_H_
#include "atom/browser/notifications/notification_presenter.h"
namespace atom {
class NotificationPresenterLinux : public NotificationPresenter {
public:
NotificationPresenterLinux();
~NotificationPresenterLinux() override;
private:
Notification* CreateNotificationObject(
NotificationDelegate* delegate) override;
DISALLOW_COPY_AND_ASSIGN(NotificationPresenterLinux);
};
} // namespace atom
#endif // ATOM_BROWSER_NOTIFICATIONS_LINUX_NOTIFICATION_PRESENTER_LINUX_H_

View file

@ -0,0 +1,49 @@
// Copyright (c) 2015 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_NOTIFICATIONS_MAC_COCOA_NOTIFICATION_H_
#define ATOM_BROWSER_NOTIFICATIONS_MAC_COCOA_NOTIFICATION_H_
#import <Foundation/Foundation.h>
#include <map>
#include <string>
#include <vector>
#include "atom/browser/notifications/notification.h"
#include "base/mac/scoped_nsobject.h"
namespace atom {
class CocoaNotification : public Notification {
public:
CocoaNotification(NotificationDelegate* delegate,
NotificationPresenter* presenter);
~CocoaNotification() override;
// Notification:
void Show(const NotificationOptions& options) override;
void Dismiss() override;
void NotificationDisplayed();
void NotificationReplied(const std::string& reply);
void NotificationActivated();
void NotificationActivated(NSUserNotificationAction* action);
void NotificationDismissed();
NSUserNotification* notification() const { return notification_; }
private:
void LogAction(const char* action);
base::scoped_nsobject<NSUserNotification> notification_;
std::map<std::string, unsigned> additional_action_indices_;
unsigned action_index_;
DISALLOW_COPY_AND_ASSIGN(CocoaNotification);
};
} // namespace atom
#endif // ATOM_BROWSER_NOTIFICATIONS_MAC_COCOA_NOTIFICATION_H_

View file

@ -0,0 +1,173 @@
// Copyright (c) 2015 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/notifications/mac/cocoa_notification.h"
#include <string>
#include <utility>
#include "atom/browser/notifications/notification_delegate.h"
#include "atom/browser/notifications/notification_presenter.h"
#include "base/mac/mac_util.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "skia/ext/skia_utils_mac.h"
namespace atom {
CocoaNotification::CocoaNotification(NotificationDelegate* delegate,
NotificationPresenter* presenter)
: Notification(delegate, presenter) {}
CocoaNotification::~CocoaNotification() {
if (notification_)
[NSUserNotificationCenter.defaultUserNotificationCenter
removeDeliveredNotification:notification_];
}
void CocoaNotification::Show(const NotificationOptions& options) {
notification_.reset([[NSUserNotification alloc] init]);
NSString* identifier =
[NSString stringWithFormat:@"%@:notification:%@",
[[NSBundle mainBundle] bundleIdentifier],
[[[NSUUID alloc] init] UUIDString]];
[notification_ setTitle:base::SysUTF16ToNSString(options.title)];
[notification_ setSubtitle:base::SysUTF16ToNSString(options.subtitle)];
[notification_ setInformativeText:base::SysUTF16ToNSString(options.msg)];
[notification_ setIdentifier:identifier];
if (getenv("ELECTRON_DEBUG_NOTIFICATIONS")) {
LOG(INFO) << "Notification created (" << [identifier UTF8String] << ")";
}
if (!options.icon.drawsNothing()) {
NSImage* image = skia::SkBitmapToNSImageWithColorSpace(
options.icon, base::mac::GetGenericRGBColorSpace());
[notification_ setContentImage:image];
}
if (options.silent) {
[notification_ setSoundName:nil];
} else if (options.sound.empty()) {
[notification_ setSoundName:NSUserNotificationDefaultSoundName];
} else {
[notification_ setSoundName:base::SysUTF16ToNSString(options.sound)];
}
[notification_ setHasActionButton:false];
int i = 0;
action_index_ = UINT_MAX;
NSMutableArray* additionalActions =
[[[NSMutableArray alloc] init] autorelease];
for (const auto& action : options.actions) {
if (action.type == base::ASCIIToUTF16("button")) {
if (action_index_ == UINT_MAX) {
// First button observed is the displayed action
[notification_ setHasActionButton:true];
[notification_
setActionButtonTitle:base::SysUTF16ToNSString(action.text)];
action_index_ = i;
} else {
// All of the rest are appended to the list of additional actions
NSString* actionIdentifier =
[NSString stringWithFormat:@"%@Action%d", identifier, i];
NSUserNotificationAction* notificationAction = [NSUserNotificationAction
actionWithIdentifier:actionIdentifier
title:base::SysUTF16ToNSString(action.text)];
[additionalActions addObject:notificationAction];
additional_action_indices_.insert(
std::make_pair(base::SysNSStringToUTF8(actionIdentifier), i));
}
}
i++;
}
if ([additionalActions count] > 0) {
[notification_ setAdditionalActions:additionalActions];
}
if (options.has_reply) {
[notification_ setResponsePlaceholder:base::SysUTF16ToNSString(
options.reply_placeholder)];
[notification_ setHasReplyButton:true];
}
if (!options.close_button_text.empty()) {
[notification_ setOtherButtonTitle:base::SysUTF16ToNSString(
options.close_button_text)];
}
[NSUserNotificationCenter.defaultUserNotificationCenter
deliverNotification:notification_];
}
void CocoaNotification::Dismiss() {
if (notification_)
[NSUserNotificationCenter.defaultUserNotificationCenter
removeDeliveredNotification:notification_];
NotificationDismissed();
this->LogAction("dismissed");
notification_.reset(nil);
}
void CocoaNotification::NotificationDisplayed() {
if (delegate())
delegate()->NotificationDisplayed();
this->LogAction("displayed");
}
void CocoaNotification::NotificationReplied(const std::string& reply) {
if (delegate())
delegate()->NotificationReplied(reply);
this->LogAction("replied to");
}
void CocoaNotification::NotificationActivated() {
if (delegate())
delegate()->NotificationAction(action_index_);
this->LogAction("button clicked");
}
void CocoaNotification::NotificationActivated(
NSUserNotificationAction* action) {
if (delegate()) {
unsigned index = action_index_;
std::string identifier = base::SysNSStringToUTF8(action.identifier);
for (const auto& it : additional_action_indices_) {
if (it.first == identifier) {
index = it.second;
break;
}
}
delegate()->NotificationAction(index);
}
this->LogAction("button clicked");
}
void CocoaNotification::NotificationDismissed() {
if (delegate())
delegate()->NotificationClosed();
this->LogAction("dismissed");
}
void CocoaNotification::LogAction(const char* action) {
if (getenv("ELECTRON_DEBUG_NOTIFICATIONS")) {
NSString* identifier = [notification_ valueForKey:@"identifier"];
LOG(INFO) << "Notification " << action << " (" << [identifier UTF8String]
<< ")";
}
}
} // namespace atom

View file

@ -0,0 +1,22 @@
// Copyright (c) 2015 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_NOTIFICATIONS_MAC_NOTIFICATION_CENTER_DELEGATE_H_
#define ATOM_BROWSER_NOTIFICATIONS_MAC_NOTIFICATION_CENTER_DELEGATE_H_
#import <Foundation/Foundation.h>
namespace atom {
class NotificationPresenterMac;
}
@interface NotificationCenterDelegate
: NSObject <NSUserNotificationCenterDelegate> {
@private
atom::NotificationPresenterMac* presenter_;
}
- (instancetype)initWithPresenter:(atom::NotificationPresenterMac*)presenter;
@end
#endif // ATOM_BROWSER_NOTIFICATIONS_MAC_NOTIFICATION_CENTER_DELEGATE_H_

View file

@ -0,0 +1,90 @@
// Copyright (c) 2015 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/notifications/mac/notification_center_delegate.h"
#include <string>
#include "atom/browser/notifications/mac/cocoa_notification.h"
#include "atom/browser/notifications/mac/notification_presenter_mac.h"
@implementation NotificationCenterDelegate
- (instancetype)initWithPresenter:(atom::NotificationPresenterMac*)presenter {
self = [super init];
if (!self)
return nil;
presenter_ = presenter;
return self;
}
- (void)userNotificationCenter:(NSUserNotificationCenter*)center
didDeliverNotification:(NSUserNotification*)notif {
auto* notification = presenter_->GetNotification(notif);
if (notification)
notification->NotificationDisplayed();
}
- (void)userNotificationCenter:(NSUserNotificationCenter*)center
didActivateNotification:(NSUserNotification*)notif {
auto* notification = presenter_->GetNotification(notif);
if (getenv("ELECTRON_DEBUG_NOTIFICATIONS")) {
LOG(INFO) << "Notification activated (" << [notif.identifier UTF8String]
<< ")";
}
if (notification) {
// Ref:
// https://developer.apple.com/documentation/foundation/nsusernotificationactivationtype?language=objc
if (notif.activationType ==
NSUserNotificationActivationTypeContentsClicked) {
notification->NotificationClicked();
} else if (notif.activationType ==
NSUserNotificationActivationTypeActionButtonClicked) {
notification->NotificationActivated();
} else if (notif.activationType ==
NSUserNotificationActivationTypeReplied) {
notification->NotificationReplied([notif.response.string UTF8String]);
} else {
if (notif.activationType ==
NSUserNotificationActivationTypeAdditionalActionClicked) {
notification->NotificationActivated([notif additionalActivationAction]);
}
}
}
}
- (BOOL)userNotificationCenter:(NSUserNotificationCenter*)center
shouldPresentNotification:(NSUserNotification*)notification {
// Display notifications even if the app is active.
return YES;
}
#if !defined(MAS_BUILD)
// This undocumented method notifies us if a user closes "Alert" notifications
// https://chromium.googlesource.com/chromium/src/+/lkgr/chrome/browser/notifications/notification_platform_bridge_mac.mm
- (void)userNotificationCenter:(NSUserNotificationCenter*)center
didDismissAlert:(NSUserNotification*)notif {
auto* notification = presenter_->GetNotification(notif);
if (notification)
notification->NotificationDismissed();
}
#endif
#if !defined(MAS_BUILD)
// This undocumented method notifies us if a user closes "Banner" notifications
// https://github.com/mozilla/gecko-dev/blob/master/widget/cocoa/OSXNotificationCenter.mm
- (void)userNotificationCenter:(NSUserNotificationCenter*)center
didRemoveDeliveredNotifications:(NSArray*)notifications {
for (NSUserNotification* notif in notifications) {
auto* notification = presenter_->GetNotification(notif);
if (notification)
notification->NotificationDismissed();
}
}
#endif
@end

View file

@ -0,0 +1,36 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Copyright (c) 2013 Adam Roben <adam@roben.org>. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-CHROMIUM file.
#ifndef ATOM_BROWSER_NOTIFICATIONS_MAC_NOTIFICATION_PRESENTER_MAC_H_
#define ATOM_BROWSER_NOTIFICATIONS_MAC_NOTIFICATION_PRESENTER_MAC_H_
#include "atom/browser/notifications/mac/notification_center_delegate.h"
#include "atom/browser/notifications/notification_presenter.h"
#include "base/mac/scoped_nsobject.h"
namespace atom {
class CocoaNotification;
class NotificationPresenterMac : public NotificationPresenter {
public:
CocoaNotification* GetNotification(NSUserNotification* notif);
NotificationPresenterMac();
~NotificationPresenterMac() override;
private:
Notification* CreateNotificationObject(
NotificationDelegate* delegate) override;
base::scoped_nsobject<NotificationCenterDelegate>
notification_center_delegate_;
DISALLOW_COPY_AND_ASSIGN(NotificationPresenterMac);
};
} // namespace atom
#endif // ATOM_BROWSER_NOTIFICATIONS_MAC_NOTIFICATION_PRESENTER_MAC_H_

View file

@ -0,0 +1,50 @@
// Copyright (c) 2015 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/notifications/mac/notification_presenter_mac.h"
#include "atom/browser/notifications/mac/cocoa_notification.h"
#include "atom/browser/notifications/mac/notification_center_delegate.h"
namespace atom {
// static
NotificationPresenter* NotificationPresenter::Create() {
return new NotificationPresenterMac;
}
CocoaNotification* NotificationPresenterMac::GetNotification(
NSUserNotification* ns_notification) {
for (Notification* notification : notifications()) {
auto* native_notification = static_cast<CocoaNotification*>(notification);
if ([native_notification->notification().identifier
isEqual:ns_notification.identifier])
return native_notification;
}
if (getenv("ELECTRON_DEBUG_NOTIFICATIONS")) {
LOG(INFO) << "Could not find notification for "
<< [ns_notification.identifier UTF8String];
}
return nullptr;
}
NotificationPresenterMac::NotificationPresenterMac()
: notification_center_delegate_(
[[NotificationCenterDelegate alloc] initWithPresenter:this]) {
NSUserNotificationCenter.defaultUserNotificationCenter.delegate =
notification_center_delegate_;
}
NotificationPresenterMac::~NotificationPresenterMac() {
NSUserNotificationCenter.defaultUserNotificationCenter.delegate = nil;
}
Notification* NotificationPresenterMac::CreateNotificationObject(
NotificationDelegate* delegate) {
return new CocoaNotification(delegate, this);
}
} // namespace atom

View file

@ -0,0 +1,46 @@
// Copyright (c) 2015 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/notifications/notification.h"
#include "atom/browser/notifications/notification_delegate.h"
#include "atom/browser/notifications/notification_presenter.h"
namespace atom {
NotificationOptions::NotificationOptions() = default;
NotificationOptions::~NotificationOptions() = default;
Notification::Notification(NotificationDelegate* delegate,
NotificationPresenter* presenter)
: delegate_(delegate), presenter_(presenter), weak_factory_(this) {}
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();
}
void Notification::Destroy() {
presenter()->RemoveNotification(this);
}
} // namespace atom

View file

@ -0,0 +1,89 @@
// Copyright (c) 2015 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_NOTIFICATIONS_NOTIFICATION_H_
#define ATOM_BROWSER_NOTIFICATIONS_NOTIFICATION_H_
#include <string>
#include <vector>
#include "base/memory/weak_ptr.h"
#include "base/strings/string16.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "url/gurl.h"
namespace atom {
class NotificationDelegate;
class NotificationPresenter;
struct NotificationAction {
base::string16 type;
base::string16 text;
};
struct NotificationOptions {
base::string16 title;
base::string16 subtitle;
base::string16 msg;
std::string tag;
bool silent;
GURL icon_url;
SkBitmap icon;
bool has_reply;
base::string16 reply_placeholder;
base::string16 sound;
std::vector<NotificationAction> actions;
base::string16 close_button_text;
NotificationOptions();
~NotificationOptions();
};
class Notification {
public:
virtual ~Notification();
// Shows the notification.
virtual void Show(const NotificationOptions& options) = 0;
// Closes the notification, this instance will be destroyed after the
// notification gets closed.
virtual void Dismiss() = 0;
// Should be called by derived classes.
void NotificationClicked();
void NotificationDismissed();
void NotificationFailed();
// delete this.
void Destroy();
base::WeakPtr<Notification> GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
void set_delegate(NotificationDelegate* delegate) { delegate_ = delegate; }
void set_notification_id(const std::string& id) { notification_id_ = id; }
NotificationDelegate* delegate() const { return delegate_; }
NotificationPresenter* presenter() const { return presenter_; }
const std::string& notification_id() const { return notification_id_; }
protected:
Notification(NotificationDelegate* delegate,
NotificationPresenter* presenter);
private:
NotificationDelegate* delegate_;
NotificationPresenter* presenter_;
std::string notification_id_;
base::WeakPtrFactory<Notification> weak_factory_;
DISALLOW_COPY_AND_ASSIGN(Notification);
};
} // namespace atom
#endif // ATOM_BROWSER_NOTIFICATIONS_NOTIFICATION_H_

View file

@ -0,0 +1,35 @@
// Copyright (c) 2015 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_NOTIFICATIONS_NOTIFICATION_DELEGATE_H_
#define ATOM_BROWSER_NOTIFICATIONS_NOTIFICATION_DELEGATE_H_
#include <string>
namespace atom {
class NotificationDelegate {
public:
// The native Notification object is destroyed.
virtual void NotificationDestroyed() {}
// Failed to send the notification.
virtual void NotificationFailed() {}
// Notification was replied to
virtual void NotificationReplied(const std::string& reply) {}
virtual void NotificationAction(int index) {}
virtual void NotificationClick() {}
virtual void NotificationClosed() {}
virtual void NotificationDisplayed() {}
protected:
NotificationDelegate() = default;
~NotificationDelegate() = default;
};
} // namespace atom
#endif // ATOM_BROWSER_NOTIFICATIONS_NOTIFICATION_DELEGATE_H_

View file

@ -0,0 +1,44 @@
// Copyright (c) 2015 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/notifications/notification_presenter.h"
#include <algorithm>
#include "atom/browser/notifications/notification.h"
namespace atom {
NotificationPresenter::NotificationPresenter() {}
NotificationPresenter::~NotificationPresenter() {
for (Notification* notification : notifications_)
delete notification;
}
base::WeakPtr<Notification> NotificationPresenter::CreateNotification(
NotificationDelegate* delegate,
const std::string& notification_id) {
Notification* notification = CreateNotificationObject(delegate);
notification->set_notification_id(notification_id);
notifications_.insert(notification);
return notification->GetWeakPtr();
}
void NotificationPresenter::RemoveNotification(Notification* notification) {
notifications_.erase(notification);
delete notification;
}
void NotificationPresenter::CloseNotificationWithId(
const std::string& notification_id) {
auto it = std::find_if(notifications_.begin(), notifications_.end(),
[&notification_id](const Notification* n) {
return n->notification_id() == notification_id;
});
if (it != notifications_.end())
(*it)->Dismiss();
}
} // namespace atom

View file

@ -0,0 +1,48 @@
// Copyright (c) 2015 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_NOTIFICATIONS_NOTIFICATION_PRESENTER_H_
#define ATOM_BROWSER_NOTIFICATIONS_NOTIFICATION_PRESENTER_H_
#include <set>
#include <string>
#include "base/memory/weak_ptr.h"
namespace atom {
class Notification;
class NotificationDelegate;
class NotificationPresenter {
public:
static NotificationPresenter* Create();
virtual ~NotificationPresenter();
base::WeakPtr<Notification> CreateNotification(
NotificationDelegate* delegate,
const std::string& notification_id);
void CloseNotificationWithId(const std::string& notification_id);
std::set<Notification*> notifications() const { return notifications_; }
protected:
NotificationPresenter();
virtual Notification* CreateNotificationObject(
NotificationDelegate* delegate) = 0;
private:
friend class Notification;
void RemoveNotification(Notification* notification);
std::set<Notification*> notifications_;
DISALLOW_COPY_AND_ASSIGN(NotificationPresenter);
};
} // namespace atom
#endif // ATOM_BROWSER_NOTIFICATIONS_NOTIFICATION_PRESENTER_H_

View file

@ -0,0 +1,136 @@
// Copyright (c) 2015 The Chromium Authors. 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 "atom/browser/notifications/platform_notification_service.h"
#include "atom/browser/atom_browser_client.h"
#include "atom/browser/notifications/notification.h"
#include "atom/browser/notifications/notification_delegate.h"
#include "atom/browser/notifications/notification_presenter.h"
#include "base/strings/utf_string_conversions.h"
#include "content/public/browser/notification_event_dispatcher.h"
#include "content/public/browser/render_process_host.h"
#include "third_party/blink/public/common/notifications/notification_resources.h"
#include "third_party/blink/public/common/notifications/platform_notification_data.h"
#include "third_party/skia/include/core/SkBitmap.h"
namespace atom {
namespace {
void OnWebNotificationAllowed(base::WeakPtr<Notification> notification,
const SkBitmap& icon,
const blink::PlatformNotificationData& data,
bool audio_muted,
bool allowed) {
if (!notification)
return;
if (allowed) {
atom::NotificationOptions options;
options.title = data.title;
options.msg = data.body;
options.tag = data.tag;
options.icon_url = data.icon;
options.icon = icon;
options.silent = audio_muted ? true : data.silent;
options.has_reply = false;
notification->Show(options);
} else {
notification->Destroy();
}
}
class NotificationDelegateImpl final : public atom::NotificationDelegate {
public:
explicit NotificationDelegateImpl(const std::string& notification_id)
: notification_id_(notification_id) {}
void NotificationDestroyed() override { delete this; }
void NotificationClick() override {
content::NotificationEventDispatcher::GetInstance()
->DispatchNonPersistentClickEvent(notification_id_, base::DoNothing());
}
void NotificationClosed() override {
content::NotificationEventDispatcher::GetInstance()
->DispatchNonPersistentCloseEvent(notification_id_, base::DoNothing());
}
void NotificationDisplayed() override {
content::NotificationEventDispatcher::GetInstance()
->DispatchNonPersistentShowEvent(notification_id_);
}
private:
std::string notification_id_;
DISALLOW_COPY_AND_ASSIGN(NotificationDelegateImpl);
};
} // namespace
PlatformNotificationService::PlatformNotificationService(
AtomBrowserClient* browser_client)
: browser_client_(browser_client) {}
PlatformNotificationService::~PlatformNotificationService() {}
void PlatformNotificationService::DisplayNotification(
content::RenderProcessHost* render_process_host,
const std::string& notification_id,
const GURL& origin,
const blink::PlatformNotificationData& notification_data,
const blink::NotificationResources& notification_resources) {
auto* presenter = browser_client_->GetNotificationPresenter();
if (!presenter)
return;
NotificationDelegateImpl* delegate =
new NotificationDelegateImpl(notification_id);
auto notification = presenter->CreateNotification(delegate, notification_id);
if (notification) {
browser_client_->WebNotificationAllowed(
render_process_host->GetID(),
base::BindRepeating(&OnWebNotificationAllowed, notification,
notification_resources.notification_icon,
notification_data));
}
}
void PlatformNotificationService::DisplayPersistentNotification(
const std::string& notification_id,
const GURL& service_worker_scope,
const GURL& origin,
const blink::PlatformNotificationData& notification_data,
const blink::NotificationResources& notification_resources) {}
void PlatformNotificationService::ClosePersistentNotification(
const std::string& notification_id) {}
void PlatformNotificationService::CloseNotification(
const std::string& notification_id) {
auto* presenter = browser_client_->GetNotificationPresenter();
if (!presenter)
return;
presenter->CloseNotificationWithId(notification_id);
}
void PlatformNotificationService::GetDisplayedNotifications(
DisplayedNotificationsCallback callback) {}
int64_t PlatformNotificationService::ReadNextPersistentNotificationId() {
// Electron doesn't support persistent notifications.
return 0;
}
void PlatformNotificationService::RecordNotificationUkmEvent(
const content::NotificationDatabaseData& data) {}
void PlatformNotificationService::ScheduleTrigger(base::Time timestamp) {}
base::Time PlatformNotificationService::ReadNextTriggerTimestamp() {
return base::Time::Max();
}
} // namespace atom

View file

@ -0,0 +1,55 @@
// Copyright (c) 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE-CHROMIUM file.
#ifndef ATOM_BROWSER_NOTIFICATIONS_PLATFORM_NOTIFICATION_SERVICE_H_
#define ATOM_BROWSER_NOTIFICATIONS_PLATFORM_NOTIFICATION_SERVICE_H_
#include <set>
#include <string>
#include "content/public/browser/platform_notification_service.h"
namespace atom {
class AtomBrowserClient;
class PlatformNotificationService
: public content::PlatformNotificationService {
public:
explicit PlatformNotificationService(AtomBrowserClient* browser_client);
~PlatformNotificationService() override;
protected:
// content::PlatformNotificationService:
void DisplayNotification(
content::RenderProcessHost* render_process_host,
const std::string& notification_id,
const GURL& origin,
const blink::PlatformNotificationData& notification_data,
const blink::NotificationResources& notification_resources) override;
void DisplayPersistentNotification(
const std::string& notification_id,
const GURL& service_worker_scope,
const GURL& origin,
const blink::PlatformNotificationData& notification_data,
const blink::NotificationResources& notification_resources) override;
void ClosePersistentNotification(const std::string& notification_id) override;
void CloseNotification(const std::string& notification_id) override;
void GetDisplayedNotifications(
DisplayedNotificationsCallback callback) override;
int64_t ReadNextPersistentNotificationId() override;
void RecordNotificationUkmEvent(
const content::NotificationDatabaseData& data) override;
void ScheduleTrigger(base::Time timestamp) override;
base::Time ReadNextTriggerTimestamp() override;
private:
AtomBrowserClient* browser_client_;
DISALLOW_COPY_AND_ASSIGN(PlatformNotificationService);
};
} // namespace atom
#endif // ATOM_BROWSER_NOTIFICATIONS_PLATFORM_NOTIFICATION_SERVICE_H_

View file

@ -0,0 +1,100 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Copyright (c) 2015 Felix Rieseberg <feriese@microsoft.com> and
// Jason Poon <jason.poon@microsoft.com>. 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 "atom/browser/notifications/win/notification_presenter_win.h"
#include <memory>
#include <string>
#include <vector>
#include "atom/browser/notifications/win/notification_presenter_win7.h"
#include "atom/browser/notifications/win/windows_toast_notification.h"
#include "base/environment.h"
#include "base/files/file_util.h"
#include "base/hash/md5.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h"
#include "base/win/windows_version.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/codec/png_codec.h"
#pragma comment(lib, "runtimeobject.lib")
namespace atom {
namespace {
bool IsDebuggingNotifications() {
return base::Environment::Create()->HasVar("ELECTRON_DEBUG_NOTIFICATIONS");
}
bool SaveIconToPath(const SkBitmap& bitmap, const base::FilePath& path) {
std::vector<unsigned char> png_data;
if (!gfx::PNGCodec::EncodeBGRASkBitmap(bitmap, false, &png_data))
return false;
char* data = reinterpret_cast<char*>(&png_data[0]);
int size = static_cast<int>(png_data.size());
return base::WriteFile(path, data, size) == size;
}
} // namespace
// static
NotificationPresenter* NotificationPresenter::Create() {
auto version = base::win::GetVersion();
if (version < base::win::Version::WIN8)
return new NotificationPresenterWin7;
if (!WindowsToastNotification::Initialize())
return nullptr;
std::unique_ptr<NotificationPresenterWin> presenter(
new NotificationPresenterWin);
if (!presenter->Init())
return nullptr;
if (IsDebuggingNotifications())
LOG(INFO) << "Successfully created Windows notifications presenter";
return presenter.release();
}
NotificationPresenterWin::NotificationPresenterWin() {}
NotificationPresenterWin::~NotificationPresenterWin() {}
bool NotificationPresenterWin::Init() {
base::ThreadRestrictions::ScopedAllowIO allow_io;
return temp_dir_.CreateUniqueTempDir();
}
base::string16 NotificationPresenterWin::SaveIconToFilesystem(
const SkBitmap& icon,
const GURL& origin) {
std::string filename;
if (origin.is_valid()) {
filename = base::MD5String(origin.spec()) + ".png";
} else {
base::TimeTicks now = base::TimeTicks::Now();
filename = std::to_string(now.ToInternalValue()) + ".png";
}
base::ThreadRestrictions::ScopedAllowIO allow_io;
base::FilePath path = temp_dir_.GetPath().Append(base::UTF8ToUTF16(filename));
if (base::PathExists(path))
return path.value();
if (SaveIconToPath(icon, path))
return path.value();
return base::UTF8ToUTF16(origin.spec());
}
Notification* NotificationPresenterWin::CreateNotificationObject(
NotificationDelegate* delegate) {
return new WindowsToastNotification(delegate, this);
}
} // namespace atom

View file

@ -0,0 +1,55 @@
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Copyright (c) 2015 Felix Rieseberg <feriese@microsoft.com> and
// Jason Poon <jason.poon@microsoft.com>. 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 ATOM_BROWSER_NOTIFICATIONS_WIN_NOTIFICATION_PRESENTER_WIN_H_
#define ATOM_BROWSER_NOTIFICATIONS_WIN_NOTIFICATION_PRESENTER_WIN_H_
#include "atom/browser/notifications/notification_presenter.h"
#include "base/files/scoped_temp_dir.h"
#include "base/strings/string16.h"
class GURL;
class SkBitmap;
namespace atom {
class NotificationPresenterWin : public NotificationPresenter {
public:
NotificationPresenterWin();
~NotificationPresenterWin() override;
bool Init();
base::string16 SaveIconToFilesystem(const SkBitmap& icon, const GURL& origin);
private:
Notification* CreateNotificationObject(
NotificationDelegate* delegate) override;
base::ScopedTempDir temp_dir_;
DISALLOW_COPY_AND_ASSIGN(NotificationPresenterWin);
};
} // namespace atom
#endif // ATOM_BROWSER_NOTIFICATIONS_WIN_NOTIFICATION_PRESENTER_WIN_H_

View file

@ -0,0 +1,54 @@
// Copyright (c) 2015 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/notifications/win/notification_presenter_win7.h"
#include <string>
#include "atom/browser/notifications/win/win32_notification.h"
namespace atom {
atom::Notification* NotificationPresenterWin7::CreateNotificationObject(
NotificationDelegate* delegate) {
return new Win32Notification(delegate, this);
}
Win32Notification* NotificationPresenterWin7::GetNotificationObjectByRef(
const DesktopNotificationController::Notification& ref) {
for (auto* n : this->notifications()) {
auto* w32n = static_cast<Win32Notification*>(n);
if (w32n->GetRef() == ref)
return w32n;
}
return nullptr;
}
Win32Notification* NotificationPresenterWin7::GetNotificationObjectByTag(
const std::string& tag) {
for (auto* n : this->notifications()) {
auto* w32n = static_cast<Win32Notification*>(n);
if (w32n->GetTag() == tag)
return w32n;
}
return nullptr;
}
void NotificationPresenterWin7::OnNotificationClicked(
const Notification& notification) {
auto* n = GetNotificationObjectByRef(notification);
if (n)
n->NotificationClicked();
}
void NotificationPresenterWin7::OnNotificationDismissed(
const Notification& notification) {
auto* n = GetNotificationObjectByRef(notification);
if (n)
n->NotificationDismissed();
}
} // namespace atom

View file

@ -0,0 +1,39 @@
// Copyright (c) 2015 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_NOTIFICATIONS_WIN_NOTIFICATION_PRESENTER_WIN7_H_
#define ATOM_BROWSER_NOTIFICATIONS_WIN_NOTIFICATION_PRESENTER_WIN7_H_
#include <string>
#include "atom/browser/notifications/notification_presenter.h"
#include "atom/browser/notifications/win/win32_desktop_notifications/desktop_notification_controller.h"
namespace atom {
class Win32Notification;
class NotificationPresenterWin7 : public NotificationPresenter,
public DesktopNotificationController {
public:
NotificationPresenterWin7() = default;
Win32Notification* GetNotificationObjectByRef(
const DesktopNotificationController::Notification& ref);
Win32Notification* GetNotificationObjectByTag(const std::string& tag);
private:
atom::Notification* CreateNotificationObject(
NotificationDelegate* delegate) override;
void OnNotificationClicked(const Notification& notification) override;
void OnNotificationDismissed(const Notification& notification) override;
DISALLOW_COPY_AND_ASSIGN(NotificationPresenterWin7);
};
} // namespace atom
#endif // ATOM_BROWSER_NOTIFICATIONS_WIN_NOTIFICATION_PRESENTER_WIN7_H_

View file

@ -0,0 +1,68 @@
// Copyright (c) 2015 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_NOTIFICATIONS_WIN_WIN32_DESKTOP_NOTIFICATIONS_COMMON_H_
#define ATOM_BROWSER_NOTIFICATIONS_WIN_WIN32_DESKTOP_NOTIFICATIONS_COMMON_H_
#include <Windows.h>
namespace atom {
struct NotificationData {
DesktopNotificationController* controller = nullptr;
std::wstring caption;
std::wstring body_text;
HBITMAP image = NULL;
NotificationData() = default;
~NotificationData() {
if (image)
DeleteObject(image);
}
NotificationData(const NotificationData& other) = delete;
NotificationData& operator=(const NotificationData& other) = delete;
};
template <typename T>
constexpr T ScaleForDpi(T value, unsigned dpi, unsigned source_dpi = 96) {
return value * dpi / source_dpi;
}
struct ScreenMetrics {
UINT dpi_x, dpi_y;
ScreenMetrics() {
typedef HRESULT WINAPI GetDpiForMonitor_t(HMONITOR, int, UINT*, UINT*);
auto GetDpiForMonitor = reinterpret_cast<GetDpiForMonitor_t*>(
GetProcAddress(GetModuleHandle(TEXT("shcore")), "GetDpiForMonitor"));
if (GetDpiForMonitor) {
auto* monitor = MonitorFromPoint({}, MONITOR_DEFAULTTOPRIMARY);
if (GetDpiForMonitor(monitor, 0, &dpi_x, &dpi_y) == S_OK)
return;
}
HDC hdc = GetDC(NULL);
dpi_x = GetDeviceCaps(hdc, LOGPIXELSX);
dpi_y = GetDeviceCaps(hdc, LOGPIXELSY);
ReleaseDC(NULL, hdc);
}
template <class T>
T X(T value) const {
return ScaleForDpi(value, dpi_x);
}
template <class T>
T Y(T value) const {
return ScaleForDpi(value, dpi_y);
}
};
} // namespace atom
#endif // ATOM_BROWSER_NOTIFICATIONS_WIN_WIN32_DESKTOP_NOTIFICATIONS_COMMON_H_

View file

@ -0,0 +1,439 @@
// Copyright (c) 2015 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef NOMINMAX
#define NOMINMAX
#endif
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include "atom/browser/notifications/win/win32_desktop_notifications/desktop_notification_controller.h"
#include <windowsx.h>
#include <algorithm>
#include <utility>
#include <vector>
#include "atom/browser/notifications/win/win32_desktop_notifications/common.h"
#include "atom/browser/notifications/win/win32_desktop_notifications/toast.h"
using std::make_shared;
using std::shared_ptr;
namespace atom {
HBITMAP CopyBitmap(HBITMAP bitmap) {
HBITMAP ret = NULL;
BITMAP bm;
if (bitmap && GetObject(bitmap, sizeof(bm), &bm)) {
HDC hdc_screen = GetDC(NULL);
ret = CreateCompatibleBitmap(hdc_screen, bm.bmWidth, bm.bmHeight);
ReleaseDC(NULL, hdc_screen);
if (ret) {
HDC hdc_src = CreateCompatibleDC(NULL);
HDC hdc_dst = CreateCompatibleDC(NULL);
SelectBitmap(hdc_src, bitmap);
SelectBitmap(hdc_dst, ret);
BitBlt(hdc_dst, 0, 0, bm.bmWidth, bm.bmHeight, hdc_src, 0, 0, SRCCOPY);
DeleteDC(hdc_dst);
DeleteDC(hdc_src);
}
}
return ret;
}
const TCHAR DesktopNotificationController::class_name_[] =
TEXT("DesktopNotificationController");
HINSTANCE DesktopNotificationController::RegisterWndClasses() {
// We keep a static `module` variable which serves a dual purpose:
// 1. Stores the HINSTANCE where the window classes are registered,
// which can be passed to `CreateWindow`
// 2. Indicates whether we already attempted the registration so that
// we don't do it twice (we don't retry even if registration fails,
// as there is no point).
static HMODULE module = NULL;
if (!module) {
if (GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
reinterpret_cast<LPCWSTR>(&RegisterWndClasses),
&module)) {
Toast::Register(module);
WNDCLASSEX wc = {sizeof(wc)};
wc.lpfnWndProc = &WndProc;
wc.lpszClassName = class_name_;
wc.cbWndExtra = sizeof(DesktopNotificationController*);
wc.hInstance = module;
RegisterClassEx(&wc);
}
}
return module;
}
DesktopNotificationController::DesktopNotificationController(
unsigned maximum_toasts) {
instances_.reserve(maximum_toasts);
}
DesktopNotificationController::~DesktopNotificationController() {
for (auto&& inst : instances_)
DestroyToast(&inst);
if (hwnd_controller_)
DestroyWindow(hwnd_controller_);
ClearAssets();
}
LRESULT CALLBACK DesktopNotificationController::WndProc(HWND hwnd,
UINT message,
WPARAM wparam,
LPARAM lparam) {
switch (message) {
case WM_CREATE: {
auto*& cs = reinterpret_cast<const CREATESTRUCT*&>(lparam);
SetWindowLongPtr(hwnd, 0, (LONG_PTR)cs->lpCreateParams);
} break;
case WM_TIMER:
if (wparam == TimerID_Animate) {
Get(hwnd)->AnimateAll();
}
return 0;
case WM_DISPLAYCHANGE: {
auto* inst = Get(hwnd);
inst->ClearAssets();
inst->AnimateAll();
} break;
case WM_SETTINGCHANGE:
if (wparam == SPI_SETWORKAREA) {
Get(hwnd)->AnimateAll();
}
break;
}
return DefWindowProc(hwnd, message, wparam, lparam);
}
void DesktopNotificationController::StartAnimation() {
DCHECK(hwnd_controller_);
if (!is_animating_ && hwnd_controller_) {
// NOTE: 15ms is shorter than what we'd need for 60 fps, but since
// the timer is not accurate we must request a higher frame rate
// to get at least 60
SetTimer(hwnd_controller_, TimerID_Animate, 15, nullptr);
is_animating_ = true;
}
}
HFONT DesktopNotificationController::GetCaptionFont() {
InitializeFonts();
return caption_font_;
}
HFONT DesktopNotificationController::GetBodyFont() {
InitializeFonts();
return body_font_;
}
void DesktopNotificationController::InitializeFonts() {
if (!body_font_) {
NONCLIENTMETRICS metrics = {sizeof(metrics)};
if (SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, &metrics, 0)) {
auto base_height = metrics.lfMessageFont.lfHeight;
HDC hdc = GetDC(NULL);
auto base_dpi_y = GetDeviceCaps(hdc, LOGPIXELSY);
ReleaseDC(NULL, hdc);
ScreenMetrics scr;
metrics.lfMessageFont.lfHeight =
(LONG)ScaleForDpi(base_height * 1.1f, scr.dpi_y, base_dpi_y);
body_font_ = CreateFontIndirect(&metrics.lfMessageFont);
if (caption_font_)
DeleteFont(caption_font_);
metrics.lfMessageFont.lfHeight =
(LONG)ScaleForDpi(base_height * 1.4f, scr.dpi_y, base_dpi_y);
caption_font_ = CreateFontIndirect(&metrics.lfMessageFont);
}
}
}
void DesktopNotificationController::ClearAssets() {
if (caption_font_) {
DeleteFont(caption_font_);
caption_font_ = NULL;
}
if (body_font_) {
DeleteFont(body_font_);
body_font_ = NULL;
}
}
void DesktopNotificationController::AnimateAll() {
// NOTE: This function refreshes position and size of all toasts according
// to all current conditions. Animation time is only one of the variables
// influencing them. Screen resolution is another.
bool keep_animating = false;
if (!instances_.empty()) {
RECT work_area;
if (SystemParametersInfo(SPI_GETWORKAREA, 0, &work_area, 0)) {
ScreenMetrics metrics;
POINT origin = {work_area.right,
work_area.bottom - metrics.Y(toast_margin_)};
auto* hdwp = BeginDeferWindowPos(static_cast<int>(instances_.size()));
for (auto&& inst : instances_) {
if (!inst.hwnd)
continue;
auto* notification = Toast::Get(inst.hwnd);
hdwp = notification->Animate(hdwp, origin);
if (!hdwp)
break;
keep_animating |= notification->IsAnimationActive();
}
if (hdwp)
EndDeferWindowPos(hdwp);
}
}
if (!keep_animating) {
DCHECK(hwnd_controller_);
if (hwnd_controller_)
KillTimer(hwnd_controller_, TimerID_Animate);
is_animating_ = false;
}
// Purge dismissed notifications and collapse the stack between
// items which are highlighted
if (!instances_.empty()) {
auto is_alive = [](ToastInstance& inst) {
return inst.hwnd && IsWindowVisible(inst.hwnd);
};
auto is_highlighted = [](ToastInstance& inst) {
return inst.hwnd && Toast::Get(inst.hwnd)->IsHighlighted();
};
for (auto it = instances_.begin();; ++it) {
// find next highlighted item
auto it2 = find_if(it, instances_.end(), is_highlighted);
// collapse the stack in front of the highlighted item
it = stable_partition(it, it2, is_alive);
// purge the dead items
for_each(it, it2, [this](auto&& inst) { DestroyToast(&inst); });
if (it2 == instances_.end()) {
instances_.erase(it, it2);
break;
}
it = move(it2);
}
}
// Set new toast positions
if (!instances_.empty()) {
ScreenMetrics metrics;
auto margin = metrics.Y(toast_margin_);
int target_pos = 0;
for (auto&& inst : instances_) {
if (inst.hwnd) {
auto* toast = Toast::Get(inst.hwnd);
if (toast->IsHighlighted())
target_pos = toast->GetVerticalPosition();
else
toast->SetVerticalPosition(target_pos);
target_pos += toast->GetHeight() + margin;
}
}
}
// Create new toasts from the queue
CheckQueue();
}
DesktopNotificationController::Notification
DesktopNotificationController::AddNotification(std::wstring caption,
std::wstring body_text,
HBITMAP image) {
NotificationLink data(this);
data->caption = move(caption);
data->body_text = move(body_text);
data->image = CopyBitmap(image);
// Enqueue new notification
Notification ret{*queue_.insert(queue_.end(), move(data))};
CheckQueue();
return ret;
}
void DesktopNotificationController::CloseNotification(
const Notification& notification) {
// Remove it from the queue
auto it = find(queue_.begin(), queue_.end(), notification.data_);
if (it != queue_.end()) {
queue_.erase(it);
this->OnNotificationClosed(notification);
return;
}
// Dismiss active toast
auto* hwnd = GetToast(notification.data_.get());
if (hwnd) {
auto* toast = Toast::Get(hwnd);
toast->Dismiss();
}
}
void DesktopNotificationController::CheckQueue() {
while (instances_.size() < instances_.capacity() && !queue_.empty()) {
CreateToast(move(queue_.front()));
queue_.pop_front();
}
}
void DesktopNotificationController::CreateToast(NotificationLink&& data) {
auto* hinstance = RegisterWndClasses();
auto* hwnd = Toast::Create(hinstance, data);
if (hwnd) {
int toast_pos = 0;
if (!instances_.empty()) {
auto& item = instances_.back();
DCHECK(item.hwnd);
ScreenMetrics scr;
auto* toast = Toast::Get(item.hwnd);
toast_pos = toast->GetVerticalPosition() + toast->GetHeight() +
scr.Y(toast_margin_);
}
instances_.push_back({hwnd, move(data)});
if (!hwnd_controller_) {
// NOTE: We cannot use a message-only window because we need to
// receive system notifications
hwnd_controller_ = CreateWindow(class_name_, nullptr, 0, 0, 0, 0, 0, NULL,
NULL, hinstance, this);
}
auto* toast = Toast::Get(hwnd);
toast->PopUp(toast_pos);
}
}
HWND DesktopNotificationController::GetToast(
const NotificationData* data) const {
auto it =
find_if(instances_.cbegin(), instances_.cend(), [data](auto&& inst) {
if (!inst.hwnd)
return false;
auto toast = Toast::Get(inst.hwnd);
return data == toast->GetNotification().get();
});
return (it != instances_.cend()) ? it->hwnd : NULL;
}
void DesktopNotificationController::DestroyToast(ToastInstance* inst) {
if (inst->hwnd) {
auto data = Toast::Get(inst->hwnd)->GetNotification();
DestroyWindow(inst->hwnd);
inst->hwnd = NULL;
Notification notification(data);
OnNotificationClosed(notification);
}
}
DesktopNotificationController::Notification::Notification() = default;
DesktopNotificationController::Notification::Notification(
const DesktopNotificationController::Notification&) = default;
DesktopNotificationController::Notification::Notification(
const shared_ptr<NotificationData>& data)
: data_(data) {
DCHECK(data != nullptr);
}
DesktopNotificationController::Notification::~Notification() = default;
bool DesktopNotificationController::Notification::operator==(
const Notification& other) const {
return data_ == other.data_;
}
void DesktopNotificationController::Notification::Close() {
// No business calling this when not pointing to a valid instance
DCHECK(data_);
if (data_->controller)
data_->controller->CloseNotification(*this);
}
void DesktopNotificationController::Notification::Set(std::wstring caption,
std::wstring body_text,
HBITMAP image) {
// No business calling this when not pointing to a valid instance
DCHECK(data_);
// Do nothing when the notification has been closed
if (!data_->controller)
return;
if (data_->image)
DeleteBitmap(data_->image);
data_->caption = move(caption);
data_->body_text = move(body_text);
data_->image = CopyBitmap(image);
auto* hwnd = data_->controller->GetToast(data_.get());
if (hwnd) {
auto* toast = Toast::Get(hwnd);
toast->ResetContents();
}
// Change of contents can affect size and position of all toasts
data_->controller->StartAnimation();
}
DesktopNotificationController::NotificationLink::NotificationLink(
DesktopNotificationController* controller)
: shared_ptr(make_shared<NotificationData>()) {
get()->controller = controller;
}
DesktopNotificationController::NotificationLink::~NotificationLink() {
auto* p = get();
if (p)
p->controller = nullptr;
}
} // namespace atom

View file

@ -0,0 +1,114 @@
// Copyright (c) 2015 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_NOTIFICATIONS_WIN_WIN32_DESKTOP_NOTIFICATIONS_DESKTOP_NOTIFICATION_CONTROLLER_H_
#define ATOM_BROWSER_NOTIFICATIONS_WIN_WIN32_DESKTOP_NOTIFICATIONS_DESKTOP_NOTIFICATION_CONTROLLER_H_
#include <Windows.h>
#include <deque>
#include <memory>
#include <string>
#include <vector>
namespace atom {
struct NotificationData;
class DesktopNotificationController {
public:
explicit DesktopNotificationController(unsigned maximum_toasts = 3);
~DesktopNotificationController();
class Notification;
Notification AddNotification(std::wstring caption,
std::wstring body_text,
HBITMAP image);
void CloseNotification(const Notification& notification);
// Event handlers -- override to receive the events
private:
virtual void OnNotificationClosed(const Notification& notification) {}
virtual void OnNotificationClicked(const Notification& notification) {}
virtual void OnNotificationDismissed(const Notification& notification) {}
private:
static HINSTANCE RegisterWndClasses();
void StartAnimation();
HFONT GetCaptionFont();
HFONT GetBodyFont();
private:
enum TimerID { TimerID_Animate = 1 };
static constexpr int toast_margin_ = 20;
// Wrapper around `NotificationData` which makes sure that
// the `controller` member is cleared when the controller object
// stops tracking the notification
struct NotificationLink : std::shared_ptr<NotificationData> {
explicit NotificationLink(DesktopNotificationController* controller);
~NotificationLink();
NotificationLink(NotificationLink&&) = default;
NotificationLink(const NotificationLink&) = delete;
NotificationLink& operator=(NotificationLink&&) = default;
};
struct ToastInstance {
HWND hwnd;
NotificationLink data;
};
class Toast;
static LRESULT CALLBACK WndProc(HWND hwnd,
UINT message,
WPARAM wparam,
LPARAM lparam);
static DesktopNotificationController* Get(HWND hwnd) {
return reinterpret_cast<DesktopNotificationController*>(
GetWindowLongPtr(hwnd, 0));
}
DesktopNotificationController(const DesktopNotificationController&) = delete;
void InitializeFonts();
void ClearAssets();
void AnimateAll();
void CheckQueue();
void CreateToast(NotificationLink&& data);
HWND GetToast(const NotificationData* data) const;
void DestroyToast(ToastInstance* inst);
private:
static const TCHAR class_name_[];
HWND hwnd_controller_ = NULL;
HFONT caption_font_ = NULL, body_font_ = NULL;
std::vector<ToastInstance> instances_;
std::deque<NotificationLink> queue_;
bool is_animating_ = false;
};
class DesktopNotificationController::Notification {
public:
Notification();
explicit Notification(const std::shared_ptr<NotificationData>& data);
Notification(const Notification&);
~Notification();
bool operator==(const Notification& other) const;
void Close();
void Set(std::wstring caption, std::wstring body_text, HBITMAP image);
private:
std::shared_ptr<NotificationData> data_;
friend class DesktopNotificationController;
};
} // namespace atom
#endif // ATOM_BROWSER_NOTIFICATIONS_WIN_WIN32_DESKTOP_NOTIFICATIONS_DESKTOP_NOTIFICATION_CONTROLLER_H_

View file

@ -0,0 +1,865 @@
// Copyright (c) 2015 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include "atom/browser/notifications/win/win32_desktop_notifications/toast.h"
#include <combaseapi.h>
#include <UIAutomation.h>
#include <uxtheme.h>
#include <windowsx.h>
#include <algorithm>
#include <cmath>
#include <memory>
#include "atom/browser/notifications/win/win32_desktop_notifications/common.h"
#include "atom/browser/notifications/win/win32_desktop_notifications/toast_uia.h"
#include "base/logging.h"
#pragma comment(lib, "msimg32.lib")
#pragma comment(lib, "uxtheme.lib")
using std::min;
using std::shared_ptr;
namespace atom {
static COLORREF GetAccentColor() {
bool success = false;
if (IsAppThemed()) {
HKEY hkey;
if (RegOpenKeyEx(HKEY_CURRENT_USER,
TEXT("SOFTWARE\\Microsoft\\Windows\\DWM"), 0,
KEY_QUERY_VALUE, &hkey) == ERROR_SUCCESS) {
COLORREF color;
DWORD type, size;
if (RegQueryValueEx(hkey, TEXT("AccentColor"), nullptr, &type,
reinterpret_cast<BYTE*>(&color),
&(size = sizeof(color))) == ERROR_SUCCESS &&
type == REG_DWORD) {
// convert from RGBA
color = RGB(GetRValue(color), GetGValue(color), GetBValue(color));
success = true;
} else if (RegQueryValueEx(hkey, TEXT("ColorizationColor"), nullptr,
&type, reinterpret_cast<BYTE*>(&color),
&(size = sizeof(color))) == ERROR_SUCCESS &&
type == REG_DWORD) {
// convert from BGRA
color = RGB(GetBValue(color), GetGValue(color), GetRValue(color));
success = true;
}
RegCloseKey(hkey);
if (success)
return color;
}
}
return GetSysColor(COLOR_ACTIVECAPTION);
}
// Stretches a bitmap to the specified size, preserves alpha channel
static HBITMAP StretchBitmap(HBITMAP bitmap, unsigned width, unsigned height) {
// We use StretchBlt for the scaling, but that discards the alpha channel.
// So we first create a separate grayscale bitmap from the alpha channel,
// scale that separately, and copy it back to the scaled color bitmap.
BITMAP bm;
if (!GetObject(bitmap, sizeof(bm), &bm))
return NULL;
if (width == 0 || height == 0)
return NULL;
HBITMAP result_bitmap = NULL;
HDC hdc_screen = GetDC(NULL);
HBITMAP alpha_src_bitmap;
{
BITMAPINFOHEADER bmi = {sizeof(BITMAPINFOHEADER)};
bmi.biWidth = bm.bmWidth;
bmi.biHeight = bm.bmHeight;
bmi.biPlanes = bm.bmPlanes;
bmi.biBitCount = bm.bmBitsPixel;
bmi.biCompression = BI_RGB;
void* alpha_src_bits;
alpha_src_bitmap =
CreateDIBSection(NULL, reinterpret_cast<BITMAPINFO*>(&bmi),
DIB_RGB_COLORS, &alpha_src_bits, NULL, 0);
if (alpha_src_bitmap) {
if (GetDIBits(hdc_screen, bitmap, 0, 0, 0,
reinterpret_cast<BITMAPINFO*>(&bmi), DIB_RGB_COLORS) &&
bmi.biSizeImage > 0 && (bmi.biSizeImage % 4) == 0) {
auto* buf = reinterpret_cast<BYTE*>(
_aligned_malloc(bmi.biSizeImage, sizeof(DWORD)));
if (buf) {
GetDIBits(hdc_screen, bitmap, 0, bm.bmHeight, buf,
reinterpret_cast<BITMAPINFO*>(&bmi), DIB_RGB_COLORS);
const DWORD* src = reinterpret_cast<DWORD*>(buf);
const DWORD* end = reinterpret_cast<DWORD*>(buf + bmi.biSizeImage);
BYTE* dest = reinterpret_cast<BYTE*>(alpha_src_bits);
for (; src != end; ++src, ++dest) {
BYTE a = *src >> 24;
*dest++ = a;
*dest++ = a;
*dest++ = a;
}
_aligned_free(buf);
}
}
}
}
if (alpha_src_bitmap) {
BITMAPINFOHEADER bmi = {sizeof(BITMAPINFOHEADER)};
bmi.biWidth = width;
bmi.biHeight = height;
bmi.biPlanes = 1;
bmi.biBitCount = 32;
bmi.biCompression = BI_RGB;
void* color_bits;
auto* color_bitmap =
CreateDIBSection(NULL, reinterpret_cast<BITMAPINFO*>(&bmi),
DIB_RGB_COLORS, &color_bits, NULL, 0);
void* alpha_bits;
auto* alpha_bitmap =
CreateDIBSection(NULL, reinterpret_cast<BITMAPINFO*>(&bmi),
DIB_RGB_COLORS, &alpha_bits, NULL, 0);
HDC hdc = CreateCompatibleDC(NULL);
HDC hdc_src = CreateCompatibleDC(NULL);
if (color_bitmap && alpha_bitmap && hdc && hdc_src) {
SetStretchBltMode(hdc, HALFTONE);
// resize color channels
SelectObject(hdc, color_bitmap);
SelectObject(hdc_src, bitmap);
StretchBlt(hdc, 0, 0, width, height, hdc_src, 0, 0, bm.bmWidth,
bm.bmHeight, SRCCOPY);
// resize alpha channel
SelectObject(hdc, alpha_bitmap);
SelectObject(hdc_src, alpha_src_bitmap);
StretchBlt(hdc, 0, 0, width, height, hdc_src, 0, 0, bm.bmWidth,
bm.bmHeight, SRCCOPY);
// flush before touching the bits
GdiFlush();
// apply the alpha channel
auto* dest = reinterpret_cast<BYTE*>(color_bits);
auto* src = reinterpret_cast<const BYTE*>(alpha_bits);
auto* end = src + (width * height * 4);
while (src != end) {
dest[3] = src[0];
dest += 4;
src += 4;
}
// create the resulting bitmap
result_bitmap =
CreateDIBitmap(hdc_screen, &bmi, CBM_INIT, color_bits,
reinterpret_cast<BITMAPINFO*>(&bmi), DIB_RGB_COLORS);
}
if (hdc_src)
DeleteDC(hdc_src);
if (hdc)
DeleteDC(hdc);
if (alpha_bitmap)
DeleteObject(alpha_bitmap);
if (color_bitmap)
DeleteObject(color_bitmap);
DeleteObject(alpha_src_bitmap);
}
ReleaseDC(NULL, hdc_screen);
return result_bitmap;
}
const TCHAR DesktopNotificationController::Toast::class_name_[] =
TEXT("DesktopNotificationToast");
DesktopNotificationController::Toast::Toast(HWND hwnd,
shared_ptr<NotificationData>* data)
: hwnd_(hwnd), data_(*data) {
HDC hdc_screen = GetDC(NULL);
hdc_ = CreateCompatibleDC(hdc_screen);
ReleaseDC(NULL, hdc_screen);
}
DesktopNotificationController::Toast::~Toast() {
if (uia_) {
auto* UiaDisconnectProvider =
reinterpret_cast<decltype(&::UiaDisconnectProvider)>(GetProcAddress(
GetModuleHandle(L"uiautomationcore.dll"), "UiaDisconnectProvider"));
// first detach from the toast, then call UiaDisconnectProvider;
// UiaDisconnectProvider may call WM_GETOBJECT and we don't want
// it to return the object that we're disconnecting
uia_->DetachToast();
if (UiaDisconnectProvider)
UiaDisconnectProvider(uia_);
uia_->Release();
uia_ = nullptr;
}
DeleteDC(hdc_);
if (bitmap_)
DeleteBitmap(bitmap_);
if (scaled_image_)
DeleteBitmap(scaled_image_);
}
void DesktopNotificationController::Toast::Register(HINSTANCE hinstance) {
WNDCLASSEX wc = {sizeof(wc)};
wc.lpfnWndProc = &Toast::WndProc;
wc.lpszClassName = class_name_;
wc.cbWndExtra = sizeof(Toast*);
wc.hInstance = hinstance;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
RegisterClassEx(&wc);
}
LRESULT DesktopNotificationController::Toast::WndProc(HWND hwnd,
UINT message,
WPARAM wparam,
LPARAM lparam) {
switch (message) {
case WM_CREATE: {
auto*& cs = reinterpret_cast<const CREATESTRUCT*&>(lparam);
auto* data =
static_cast<shared_ptr<NotificationData>*>(cs->lpCreateParams);
auto* inst = new Toast(hwnd, data);
SetWindowLongPtr(hwnd, 0, (LONG_PTR)inst);
} break;
case WM_NCDESTROY:
delete Get(hwnd);
SetWindowLongPtr(hwnd, 0, 0);
return 0;
case WM_DESTROY:
if (Get(hwnd)->uia_) {
// free UI Automation resources associated with this window
UiaReturnRawElementProvider(hwnd, 0, 0, nullptr);
}
break;
case WM_MOUSEACTIVATE:
return MA_NOACTIVATE;
case WM_TIMER: {
if (wparam == TimerID_AutoDismiss) {
auto* inst = Get(hwnd);
Notification notification(inst->data_);
inst->data_->controller->OnNotificationDismissed(notification);
inst->AutoDismiss();
}
}
return 0;
case WM_LBUTTONDOWN: {
auto* inst = Get(hwnd);
inst->Dismiss();
Notification notification(inst->data_);
if (inst->is_close_hot_)
inst->data_->controller->OnNotificationDismissed(notification);
else
inst->data_->controller->OnNotificationClicked(notification);
}
return 0;
case WM_MOUSEMOVE: {
auto* inst = Get(hwnd);
if (!inst->is_highlighted_) {
inst->is_highlighted_ = true;
TRACKMOUSEEVENT tme = {sizeof(tme), TME_LEAVE, hwnd};
TrackMouseEvent(&tme);
}
POINT cursor = {GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam)};
inst->is_close_hot_ =
(PtInRect(&inst->close_button_rect_, cursor) != FALSE);
if (!inst->is_non_interactive_)
inst->CancelDismiss();
inst->UpdateContents();
}
return 0;
case WM_MOUSELEAVE: {
auto* inst = Get(hwnd);
inst->is_highlighted_ = false;
inst->is_close_hot_ = false;
inst->UpdateContents();
if (!inst->ease_out_active_ && inst->ease_in_pos_ == 1.0f)
inst->ScheduleDismissal();
// Make sure stack collapse happens if needed
inst->data_->controller->StartAnimation();
}
return 0;
case WM_WINDOWPOSCHANGED: {
auto*& wp = reinterpret_cast<WINDOWPOS*&>(lparam);
if (wp->flags & SWP_HIDEWINDOW) {
if (!IsWindowVisible(hwnd))
Get(hwnd)->is_highlighted_ = false;
}
} break;
case WM_GETOBJECT:
if (lparam == UiaRootObjectId) {
auto* inst = Get(hwnd);
if (!inst->uia_) {
inst->uia_ = new UIAutomationInterface(inst);
inst->uia_->AddRef();
}
// don't return the interface if it's being disconnected
if (!inst->uia_->IsDetached()) {
return UiaReturnRawElementProvider(hwnd, wparam, lparam, inst->uia_);
}
}
break;
}
return DefWindowProc(hwnd, message, wparam, lparam);
}
HWND DesktopNotificationController::Toast::Create(
HINSTANCE hinstance,
shared_ptr<NotificationData> data) {
return CreateWindowEx(WS_EX_LAYERED | WS_EX_NOACTIVATE | WS_EX_TOPMOST,
class_name_, nullptr, WS_POPUP, 0, 0, 0, 0, NULL, NULL,
hinstance, &data);
}
void DesktopNotificationController::Toast::Draw() {
const COLORREF accent = GetAccentColor();
COLORREF back_color;
{
// base background color is 2/3 of accent
// highlighted adds a bit of intensity to every channel
int h = is_highlighted_ ? (0xff / 20) : 0;
back_color = RGB(min(0xff, (GetRValue(accent) * 2 / 3) + h),
min(0xff, (GetGValue(accent) * 2 / 3) + h),
min(0xff, (GetBValue(accent) * 2 / 3) + h));
}
const float back_luma = (GetRValue(back_color) * 0.299f / 255) +
(GetGValue(back_color) * 0.587f / 255) +
(GetBValue(back_color) * 0.114f / 255);
const struct {
float r, g, b;
} back_f = {
GetRValue(back_color) / 255.0f,
GetGValue(back_color) / 255.0f,
GetBValue(back_color) / 255.0f,
};
COLORREF fore_color, dimmed_color;
{
// based on the lightness of background, we draw foreground in light
// or dark shades of gray blended onto the background with slight
// transparency to avoid sharp contrast
constexpr float alpha = 0.9f;
constexpr float intensity_light[] = {(1.0f * alpha), (0.8f * alpha)};
constexpr float intensity_dark[] = {(0.1f * alpha), (0.3f * alpha)};
// select foreground intensity values (light or dark)
auto& i = (back_luma < 0.6f) ? intensity_light : intensity_dark;
float r, g, b;
r = i[0] + back_f.r * (1 - alpha);
g = i[0] + back_f.g * (1 - alpha);
b = i[0] + back_f.b * (1 - alpha);
fore_color = RGB(r * 0xff, g * 0xff, b * 0xff);
r = i[1] + back_f.r * (1 - alpha);
g = i[1] + back_f.g * (1 - alpha);
b = i[1] + back_f.b * (1 - alpha);
dimmed_color = RGB(r * 0xff, g * 0xff, b * 0xff);
}
// Draw background
{
auto* brush = CreateSolidBrush(back_color);
RECT rc = {0, 0, toast_size_.cx, toast_size_.cy};
FillRect(hdc_, &rc, brush);
DeleteBrush(brush);
}
SetBkMode(hdc_, TRANSPARENT);
const auto close = L'\x2715';
auto* caption_font = data_->controller->GetCaptionFont();
auto* body_font = data_->controller->GetBodyFont();
TEXTMETRIC tm_cap;
SelectFont(hdc_, caption_font);
GetTextMetrics(hdc_, &tm_cap);
auto text_offset_x = margin_.cx;
BITMAP image_info = {};
if (scaled_image_) {
GetObject(scaled_image_, sizeof(image_info), &image_info);
text_offset_x += margin_.cx + image_info.bmWidth;
}
// calculate close button rect
POINT close_pos;
{
SIZE extent = {};
GetTextExtentPoint32W(hdc_, &close, 1, &extent);
close_button_rect_.right = toast_size_.cx;
close_button_rect_.top = 0;
close_pos.x = close_button_rect_.right - margin_.cy - extent.cx;
close_pos.y = close_button_rect_.top + margin_.cy;
close_button_rect_.left = close_pos.x - margin_.cy;
close_button_rect_.bottom = close_pos.y + extent.cy + margin_.cy;
}
// image
if (scaled_image_) {
HDC hdc_image = CreateCompatibleDC(NULL);
SelectBitmap(hdc_image, scaled_image_);
BLENDFUNCTION blend = {AC_SRC_OVER, 0, 255, AC_SRC_ALPHA};
AlphaBlend(hdc_, margin_.cx, margin_.cy, image_info.bmWidth,
image_info.bmHeight, hdc_image, 0, 0, image_info.bmWidth,
image_info.bmHeight, blend);
DeleteDC(hdc_image);
}
// caption
{
RECT rc = {text_offset_x, margin_.cy, close_button_rect_.left,
toast_size_.cy};
SelectFont(hdc_, caption_font);
SetTextColor(hdc_, fore_color);
DrawText(hdc_, data_->caption.data(), (UINT)data_->caption.length(), &rc,
DT_SINGLELINE | DT_END_ELLIPSIS | DT_NOPREFIX);
}
// body text
if (!data_->body_text.empty()) {
RECT rc = {text_offset_x, 2 * margin_.cy + tm_cap.tmAscent,
toast_size_.cx - margin_.cx, toast_size_.cy - margin_.cy};
SelectFont(hdc_, body_font);
SetTextColor(hdc_, dimmed_color);
DrawText(hdc_, data_->body_text.data(), (UINT)data_->body_text.length(),
&rc,
DT_LEFT | DT_WORDBREAK | DT_NOPREFIX | DT_END_ELLIPSIS |
DT_EDITCONTROL);
}
// close button
{
SelectFont(hdc_, caption_font);
SetTextColor(hdc_, is_close_hot_ ? fore_color : dimmed_color);
ExtTextOut(hdc_, close_pos.x, close_pos.y, 0, nullptr, &close, 1, nullptr);
}
is_content_updated_ = true;
}
void DesktopNotificationController::Toast::Invalidate() {
is_content_updated_ = false;
}
bool DesktopNotificationController::Toast::IsRedrawNeeded() const {
return !is_content_updated_;
}
void DesktopNotificationController::Toast::UpdateBufferSize() {
if (hdc_) {
SIZE new_size;
{
TEXTMETRIC tm_cap = {};
HFONT font = data_->controller->GetCaptionFont();
if (font) {
SelectFont(hdc_, font);
if (!GetTextMetrics(hdc_, &tm_cap))
return;
}
TEXTMETRIC tm_body = {};
font = data_->controller->GetBodyFont();
if (font) {
SelectFont(hdc_, font);
if (!GetTextMetrics(hdc_, &tm_body))
return;
}
this->margin_ = {tm_cap.tmAveCharWidth * 2, tm_cap.tmAscent / 2};
new_size.cx = margin_.cx + (32 * tm_cap.tmAveCharWidth) + margin_.cx;
new_size.cy = margin_.cy + (tm_cap.tmHeight) + margin_.cy;
if (!data_->body_text.empty())
new_size.cy += margin_.cy + (3 * tm_body.tmHeight);
if (data_->image) {
BITMAP bm;
if (GetObject(data_->image, sizeof(bm), &bm)) {
// cap the image size
const int max_dim_size = 80;
auto width = bm.bmWidth;
auto height = bm.bmHeight;
if (width < height) {
if (height > max_dim_size) {
width = width * max_dim_size / height;
height = max_dim_size;
}
} else {
if (width > max_dim_size) {
height = height * max_dim_size / width;
width = max_dim_size;
}
}
ScreenMetrics scr;
SIZE image_draw_size = {scr.X(width), scr.Y(height)};
new_size.cx += image_draw_size.cx + margin_.cx;
auto height_with_image =
margin_.cy + (image_draw_size.cy) + margin_.cy;
if (new_size.cy < height_with_image)
new_size.cy = height_with_image;
UpdateScaledImage(image_draw_size);
}
}
}
if (new_size.cx != this->toast_size_.cx ||
new_size.cy != this->toast_size_.cy) {
HDC hdc_screen = GetDC(NULL);
auto* new_bitmap =
CreateCompatibleBitmap(hdc_screen, new_size.cx, new_size.cy);
ReleaseDC(NULL, hdc_screen);
if (new_bitmap) {
if (SelectBitmap(hdc_, new_bitmap)) {
RECT dirty1 = {}, dirty2 = {};
if (toast_size_.cx < new_size.cx) {
dirty1 = {toast_size_.cx, 0, new_size.cx, toast_size_.cy};
}
if (toast_size_.cy < new_size.cy) {
dirty2 = {0, toast_size_.cy, new_size.cx, new_size.cy};
}
if (this->bitmap_)
DeleteBitmap(this->bitmap_);
this->bitmap_ = new_bitmap;
this->toast_size_ = new_size;
Invalidate();
// Resize also the DWM buffer to prevent flicker during
// window resizing. Make sure any existing data is not
// overwritten by marking the dirty region.
{
POINT origin = {0, 0};
UPDATELAYEREDWINDOWINFO ulw;
ulw.cbSize = sizeof(ulw);
ulw.hdcDst = NULL;
ulw.pptDst = nullptr;
ulw.psize = &toast_size_;
ulw.hdcSrc = hdc_;
ulw.pptSrc = &origin;
ulw.crKey = 0;
ulw.pblend = nullptr;
ulw.dwFlags = 0;
ulw.prcDirty = &dirty1;
auto b1 = UpdateLayeredWindowIndirect(hwnd_, &ulw);
ulw.prcDirty = &dirty2;
auto b2 = UpdateLayeredWindowIndirect(hwnd_, &ulw);
DCHECK(b1 && b2);
}
return;
}
DeleteBitmap(new_bitmap);
}
}
}
}
void DesktopNotificationController::Toast::UpdateScaledImage(const SIZE& size) {
BITMAP bm;
if (!GetObject(scaled_image_, sizeof(bm), &bm) || bm.bmWidth != size.cx ||
bm.bmHeight != size.cy) {
if (scaled_image_)
DeleteBitmap(scaled_image_);
scaled_image_ = StretchBitmap(data_->image, size.cx, size.cy);
}
}
void DesktopNotificationController::Toast::UpdateContents() {
Draw();
if (IsWindowVisible(hwnd_)) {
RECT rc;
GetWindowRect(hwnd_, &rc);
POINT origin = {0, 0};
SIZE size = {rc.right - rc.left, rc.bottom - rc.top};
UpdateLayeredWindow(hwnd_, NULL, nullptr, &size, hdc_, &origin, 0, nullptr,
0);
}
}
void DesktopNotificationController::Toast::Dismiss() {
if (!is_non_interactive_) {
// Set a flag to prevent further interaction. We don't disable the HWND
// because we still want to receive mouse move messages in order to keep
// the toast under the cursor and not collapse it while dismissing.
is_non_interactive_ = true;
AutoDismiss();
}
}
void DesktopNotificationController::Toast::AutoDismiss() {
KillTimer(hwnd_, TimerID_AutoDismiss);
StartEaseOut();
}
void DesktopNotificationController::Toast::CancelDismiss() {
KillTimer(hwnd_, TimerID_AutoDismiss);
ease_out_active_ = false;
ease_out_pos_ = 0;
}
void DesktopNotificationController::Toast::ScheduleDismissal() {
ULONG duration;
if (!SystemParametersInfo(SPI_GETMESSAGEDURATION, 0, &duration, 0)) {
duration = 5;
}
SetTimer(hwnd_, TimerID_AutoDismiss, duration * 1000, nullptr);
}
void DesktopNotificationController::Toast::ResetContents() {
if (scaled_image_) {
DeleteBitmap(scaled_image_);
scaled_image_ = NULL;
}
Invalidate();
}
void DesktopNotificationController::Toast::PopUp(int y) {
vertical_pos_target_ = vertical_pos_ = y;
StartEaseIn();
}
void DesktopNotificationController::Toast::SetVerticalPosition(int y) {
// Don't restart animation if current target is the same
if (y == vertical_pos_target_)
return;
// Make sure the new animation's origin is at the current position
vertical_pos_ += static_cast<int>((vertical_pos_target_ - vertical_pos_) *
stack_collapse_pos_);
// Set new target position and start the animation
vertical_pos_target_ = y;
stack_collapse_start_ = GetTickCount();
data_->controller->StartAnimation();
}
HDWP DesktopNotificationController::Toast::Animate(HDWP hdwp,
const POINT& origin) {
UpdateBufferSize();
if (IsRedrawNeeded())
Draw();
POINT src_origin = {0, 0};
UPDATELAYEREDWINDOWINFO ulw;
ulw.cbSize = sizeof(ulw);
ulw.hdcDst = NULL;
ulw.pptDst = nullptr;
ulw.psize = nullptr;
ulw.hdcSrc = hdc_;
ulw.pptSrc = &src_origin;
ulw.crKey = 0;
ulw.pblend = nullptr;
ulw.dwFlags = 0;
ulw.prcDirty = nullptr;
POINT pt = {0, 0};
SIZE size = {0, 0};
BLENDFUNCTION blend;
UINT dwpFlags =
SWP_NOACTIVATE | SWP_SHOWWINDOW | SWP_NOREDRAW | SWP_NOCOPYBITS;
auto ease_in_pos = AnimateEaseIn();
auto ease_out_pos = AnimateEaseOut();
auto stack_collapse_pos = AnimateStackCollapse();
auto y_offset = (vertical_pos_target_ - vertical_pos_) * stack_collapse_pos;
size.cx = static_cast<int>(toast_size_.cx * ease_in_pos);
size.cy = toast_size_.cy;
pt.x = origin.x - size.cx;
pt.y = static_cast<int>(origin.y - vertical_pos_ - y_offset - size.cy);
ulw.pptDst = &pt;
ulw.psize = &size;
if (ease_in_active_ && ease_in_pos == 1.0f) {
ease_in_active_ = false;
ScheduleDismissal();
}
this->ease_in_pos_ = ease_in_pos;
this->stack_collapse_pos_ = stack_collapse_pos;
if (ease_out_pos != this->ease_out_pos_) {
blend.BlendOp = AC_SRC_OVER;
blend.BlendFlags = 0;
blend.SourceConstantAlpha = (BYTE)(255 * (1.0f - ease_out_pos));
blend.AlphaFormat = 0;
ulw.pblend = &blend;
ulw.dwFlags = ULW_ALPHA;
this->ease_out_pos_ = ease_out_pos;
if (ease_out_pos == 1.0f) {
ease_out_active_ = false;
dwpFlags &= ~SWP_SHOWWINDOW;
dwpFlags |= SWP_HIDEWINDOW;
}
}
if (stack_collapse_pos == 1.0f) {
vertical_pos_ = vertical_pos_target_;
}
// `UpdateLayeredWindowIndirect` updates position, size, and transparency.
// `DeferWindowPos` updates z-order, and also position and size in case
// ULWI fails, which can happen when one of the dimensions is zero (e.g.
// at the beginning of ease-in).
UpdateLayeredWindowIndirect(hwnd_, &ulw);
hdwp = DeferWindowPos(hdwp, hwnd_, HWND_TOPMOST, pt.x, pt.y, size.cx, size.cy,
dwpFlags);
return hdwp;
}
void DesktopNotificationController::Toast::StartEaseIn() {
DCHECK(!ease_in_active_);
ease_in_start_ = GetTickCount();
ease_in_active_ = true;
data_->controller->StartAnimation();
}
void DesktopNotificationController::Toast::StartEaseOut() {
DCHECK(!ease_out_active_);
ease_out_start_ = GetTickCount();
ease_out_active_ = true;
data_->controller->StartAnimation();
}
bool DesktopNotificationController::Toast::IsStackCollapseActive() const {
return (vertical_pos_ != vertical_pos_target_);
}
float DesktopNotificationController::Toast::AnimateEaseIn() {
if (!ease_in_active_)
return ease_in_pos_;
constexpr DWORD duration = 500;
auto elapsed = GetTickCount() - ease_in_start_;
float time = std::min(duration, elapsed) / static_cast<float>(duration);
// decelerating exponential ease
const float a = -8.0f;
auto pos = (std::exp(a * time) - 1.0f) / (std::exp(a) - 1.0f);
return pos;
}
float DesktopNotificationController::Toast::AnimateEaseOut() {
if (!ease_out_active_)
return ease_out_pos_;
constexpr DWORD duration = 120;
auto elapsed = GetTickCount() - ease_out_start_;
float time = std::min(duration, elapsed) / static_cast<float>(duration);
// accelerating circle ease
auto pos = 1.0f - std::sqrt(1 - time * time);
return pos;
}
float DesktopNotificationController::Toast::AnimateStackCollapse() {
if (!IsStackCollapseActive())
return stack_collapse_pos_;
constexpr DWORD duration = 500;
auto elapsed = GetTickCount() - stack_collapse_start_;
float time = std::min(duration, elapsed) / static_cast<float>(duration);
// decelerating exponential ease
const float a = -8.0f;
auto pos = (std::exp(a * time) - 1.0f) / (std::exp(a) - 1.0f);
return pos;
}
} // namespace atom

View file

@ -0,0 +1,107 @@
// Copyright (c) 2015 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_NOTIFICATIONS_WIN_WIN32_DESKTOP_NOTIFICATIONS_TOAST_H_
#define ATOM_BROWSER_NOTIFICATIONS_WIN_WIN32_DESKTOP_NOTIFICATIONS_TOAST_H_
#include <memory>
#include "atom/browser/notifications/win/win32_desktop_notifications/desktop_notification_controller.h"
#include "base/logging.h"
namespace atom {
class DesktopNotificationController::Toast {
public:
static void Register(HINSTANCE hinstance);
static HWND Create(HINSTANCE hinstance,
std::shared_ptr<NotificationData> data);
static Toast* Get(HWND hwnd) {
return reinterpret_cast<Toast*>(GetWindowLongPtr(hwnd, 0));
}
static LRESULT CALLBACK WndProc(HWND hwnd,
UINT message,
WPARAM wparam,
LPARAM lparam);
const std::shared_ptr<NotificationData>& GetNotification() const {
return data_;
}
void ResetContents();
void Dismiss();
void PopUp(int y);
void SetVerticalPosition(int y);
int GetVerticalPosition() const { return vertical_pos_target_; }
int GetHeight() const { return toast_size_.cy; }
HDWP Animate(HDWP hdwp, const POINT& origin);
bool IsAnimationActive() const {
return ease_in_active_ || ease_out_active_ || IsStackCollapseActive();
}
bool IsHighlighted() const {
DCHECK(!(is_highlighted_ && !IsWindowVisible(hwnd_)));
return is_highlighted_;
}
private:
enum TimerID { TimerID_AutoDismiss = 1 };
Toast(HWND hwnd, std::shared_ptr<NotificationData>* data);
~Toast();
void UpdateBufferSize();
void UpdateScaledImage(const SIZE& size);
void Draw();
void Invalidate();
bool IsRedrawNeeded() const;
void UpdateContents();
void AutoDismiss();
void CancelDismiss();
void ScheduleDismissal();
void StartEaseIn();
void StartEaseOut();
bool IsStackCollapseActive() const;
float AnimateEaseIn();
float AnimateEaseOut();
float AnimateStackCollapse();
private:
static const TCHAR class_name_[];
const HWND hwnd_;
HDC hdc_;
HBITMAP bitmap_ = NULL;
class UIAutomationInterface;
UIAutomationInterface* uia_ = nullptr;
const std::shared_ptr<NotificationData> data_; // never null
SIZE toast_size_ = {};
SIZE margin_ = {};
RECT close_button_rect_ = {};
HBITMAP scaled_image_ = NULL;
int vertical_pos_ = 0;
int vertical_pos_target_ = 0;
bool is_non_interactive_ = false;
bool ease_in_active_ = false;
bool ease_out_active_ = false;
bool is_content_updated_ = false;
bool is_highlighted_ = false;
bool is_close_hot_ = false;
DWORD ease_in_start_, ease_out_start_, stack_collapse_start_;
float ease_in_pos_ = 0, ease_out_pos_ = 0, stack_collapse_pos_ = 0;
};
} // namespace atom
#endif // ATOM_BROWSER_NOTIFICATIONS_WIN_WIN32_DESKTOP_NOTIFICATIONS_TOAST_H_

View file

@ -0,0 +1,254 @@
// Copyright (c) 2015 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "atom/browser/notifications/win/win32_desktop_notifications/toast_uia.h"
#include <UIAutomation.h>
#include "atom/browser/notifications/win/win32_desktop_notifications/common.h"
#pragma comment(lib, "uiautomationcore.lib")
namespace atom {
DesktopNotificationController::Toast::UIAutomationInterface::
UIAutomationInterface(Toast* toast)
: hwnd_(toast->hwnd_) {
text_ = toast->data_->caption;
if (!toast->data_->body_text.empty()) {
if (!text_.empty())
text_.append(L", ");
text_.append(toast->data_->body_text);
}
}
ULONG DesktopNotificationController::Toast::UIAutomationInterface::AddRef() {
return InterlockedIncrement(&cref_);
}
ULONG DesktopNotificationController::Toast::UIAutomationInterface::Release() {
LONG ret = InterlockedDecrement(&cref_);
if (ret == 0) {
delete this;
return 0;
}
DCHECK_GT(ret, 0);
return ret;
}
STDMETHODIMP
DesktopNotificationController::Toast::UIAutomationInterface::QueryInterface(
REFIID riid,
LPVOID* ppv) {
if (!ppv)
return E_INVALIDARG;
if (riid == IID_IUnknown) {
*ppv =
static_cast<IUnknown*>(static_cast<IRawElementProviderSimple*>(this));
} else if (riid == __uuidof(IRawElementProviderSimple)) {
*ppv = static_cast<IRawElementProviderSimple*>(this);
} else if (riid == __uuidof(IWindowProvider)) {
*ppv = static_cast<IWindowProvider*>(this);
} else if (riid == __uuidof(IInvokeProvider)) {
*ppv = static_cast<IInvokeProvider*>(this);
} else if (riid == __uuidof(ITextProvider)) {
*ppv = static_cast<ITextProvider*>(this);
} else {
*ppv = nullptr;
return E_NOINTERFACE;
}
this->AddRef();
return S_OK;
}
HRESULT DesktopNotificationController::Toast::UIAutomationInterface::
get_ProviderOptions(ProviderOptions* retval) {
*retval = ProviderOptions_ServerSideProvider;
return S_OK;
}
HRESULT
DesktopNotificationController::Toast::UIAutomationInterface::GetPatternProvider(
PATTERNID pattern_id,
IUnknown** retval) {
switch (pattern_id) {
case UIA_WindowPatternId:
*retval = static_cast<IWindowProvider*>(this);
break;
case UIA_InvokePatternId:
*retval = static_cast<IInvokeProvider*>(this);
break;
case UIA_TextPatternId:
*retval = static_cast<ITextProvider*>(this);
break;
default:
*retval = nullptr;
return S_OK;
}
this->AddRef();
return S_OK;
}
HRESULT
DesktopNotificationController::Toast::UIAutomationInterface::GetPropertyValue(
PROPERTYID property_id,
VARIANT* retval) {
// Note: In order to have the toast read by the NVDA screen reader, we
// pretend that we're a Windows 8 native toast notification by reporting
// these property values:
// ClassName: ToastContentHost
// ControlType: UIA_ToolTipControlTypeId
retval->vt = VT_EMPTY;
switch (property_id) {
case UIA_NamePropertyId:
retval->vt = VT_BSTR;
retval->bstrVal = SysAllocString(text_.c_str());
break;
case UIA_ClassNamePropertyId:
retval->vt = VT_BSTR;
retval->bstrVal = SysAllocString(L"ToastContentHost");
break;
case UIA_ControlTypePropertyId:
retval->vt = VT_I4;
retval->lVal = UIA_ToolTipControlTypeId;
break;
case UIA_LiveSettingPropertyId:
retval->vt = VT_I4;
retval->lVal = Assertive;
break;
case UIA_IsContentElementPropertyId:
case UIA_IsControlElementPropertyId:
case UIA_IsPeripheralPropertyId:
retval->vt = VT_BOOL;
retval->lVal = VARIANT_TRUE;
break;
case UIA_HasKeyboardFocusPropertyId:
case UIA_IsKeyboardFocusablePropertyId:
case UIA_IsOffscreenPropertyId:
retval->vt = VT_BOOL;
retval->lVal = VARIANT_FALSE;
break;
}
return S_OK;
}
HRESULT
DesktopNotificationController::Toast::UIAutomationInterface::
get_HostRawElementProvider(IRawElementProviderSimple** retval) {
if (!hwnd_)
return E_FAIL;
return UiaHostProviderFromHwnd(hwnd_, retval);
}
HRESULT DesktopNotificationController::Toast::UIAutomationInterface::Invoke() {
return E_NOTIMPL;
}
HRESULT
DesktopNotificationController::Toast::UIAutomationInterface::SetVisualState(
WindowVisualState state) {
// setting the visual state is not supported
return E_FAIL;
}
HRESULT DesktopNotificationController::Toast::UIAutomationInterface::Close() {
return E_NOTIMPL;
}
HRESULT
DesktopNotificationController::Toast::UIAutomationInterface::WaitForInputIdle(
int milliseconds,
BOOL* retval) {
return E_NOTIMPL;
}
HRESULT
DesktopNotificationController::Toast::UIAutomationInterface::get_CanMaximize(
BOOL* retval) {
*retval = FALSE;
return S_OK;
}
HRESULT
DesktopNotificationController::Toast::UIAutomationInterface::get_CanMinimize(
BOOL* retval) {
*retval = FALSE;
return S_OK;
}
HRESULT
DesktopNotificationController::Toast::UIAutomationInterface::get_IsModal(
BOOL* retval) {
*retval = FALSE;
return S_OK;
}
HRESULT DesktopNotificationController::Toast::UIAutomationInterface::
get_WindowVisualState(WindowVisualState* retval) {
*retval = WindowVisualState_Normal;
return S_OK;
}
HRESULT DesktopNotificationController::Toast::UIAutomationInterface::
get_WindowInteractionState(WindowInteractionState* retval) {
if (!hwnd_)
*retval = WindowInteractionState_Closing;
else
*retval = WindowInteractionState_ReadyForUserInteraction;
return S_OK;
}
HRESULT
DesktopNotificationController::Toast::UIAutomationInterface::get_IsTopmost(
BOOL* retval) {
*retval = TRUE;
return S_OK;
}
HRESULT
DesktopNotificationController::Toast::UIAutomationInterface::GetSelection(
SAFEARRAY** retval) {
return E_NOTIMPL;
}
HRESULT
DesktopNotificationController::Toast::UIAutomationInterface::GetVisibleRanges(
SAFEARRAY** retval) {
return E_NOTIMPL;
}
HRESULT
DesktopNotificationController::Toast::UIAutomationInterface::RangeFromChild(
IRawElementProviderSimple* child_element,
ITextRangeProvider** retval) {
return E_NOTIMPL;
}
HRESULT
DesktopNotificationController::Toast::UIAutomationInterface::RangeFromPoint(
UiaPoint point,
ITextRangeProvider** retval) {
return E_NOTIMPL;
}
HRESULT
DesktopNotificationController::Toast::UIAutomationInterface::get_DocumentRange(
ITextRangeProvider** retval) {
return E_NOTIMPL;
}
HRESULT DesktopNotificationController::Toast::UIAutomationInterface::
get_SupportedTextSelection(SupportedTextSelection* retval) {
*retval = SupportedTextSelection_None;
return S_OK;
}
} // namespace atom

View file

@ -0,0 +1,84 @@
// Copyright (c) 2015 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_NOTIFICATIONS_WIN_WIN32_DESKTOP_NOTIFICATIONS_TOAST_UIA_H_
#define ATOM_BROWSER_NOTIFICATIONS_WIN_WIN32_DESKTOP_NOTIFICATIONS_TOAST_UIA_H_
#include "atom/browser/notifications/win/win32_desktop_notifications/toast.h"
#include <combaseapi.h>
#include <UIAutomationCore.h>
namespace atom {
class DesktopNotificationController::Toast::UIAutomationInterface
: public IRawElementProviderSimple,
public IWindowProvider,
public IInvokeProvider,
public ITextProvider {
public:
explicit UIAutomationInterface(Toast* toast);
void DetachToast() { hwnd_ = NULL; }
bool IsDetached() const { return !hwnd_; }
private:
virtual ~UIAutomationInterface() = default;
// IUnknown
public:
ULONG STDMETHODCALLTYPE AddRef() override;
ULONG STDMETHODCALLTYPE Release() override;
STDMETHODIMP QueryInterface(REFIID riid, LPVOID* ppv) override;
// IRawElementProviderSimple
public:
STDMETHODIMP get_ProviderOptions(ProviderOptions* retval) override;
STDMETHODIMP GetPatternProvider(PATTERNID pattern_id,
IUnknown** retval) override;
STDMETHODIMP GetPropertyValue(PROPERTYID property_id,
VARIANT* retval) override;
STDMETHODIMP get_HostRawElementProvider(
IRawElementProviderSimple** retval) override;
// IWindowProvider
public:
STDMETHODIMP SetVisualState(WindowVisualState state) override;
STDMETHODIMP Close() override;
STDMETHODIMP WaitForInputIdle(int milliseconds, BOOL* retval) override;
STDMETHODIMP get_CanMaximize(BOOL* retval) override;
STDMETHODIMP get_CanMinimize(BOOL* retval) override;
STDMETHODIMP get_IsModal(BOOL* retval) override;
STDMETHODIMP get_WindowVisualState(WindowVisualState* retval) override;
STDMETHODIMP get_WindowInteractionState(
WindowInteractionState* retval) override;
STDMETHODIMP get_IsTopmost(BOOL* retval) override;
// IInvokeProvider
public:
STDMETHODIMP Invoke() override;
// ITextProvider
public:
STDMETHODIMP GetSelection(SAFEARRAY** retval) override;
STDMETHODIMP GetVisibleRanges(SAFEARRAY** retval) override;
STDMETHODIMP RangeFromChild(IRawElementProviderSimple* child_element,
ITextRangeProvider** retval) override;
STDMETHODIMP RangeFromPoint(UiaPoint point,
ITextRangeProvider** retval) override;
STDMETHODIMP get_DocumentRange(ITextRangeProvider** retval) override;
STDMETHODIMP get_SupportedTextSelection(
SupportedTextSelection* retval) override;
private:
volatile LONG cref_ = 0;
HWND hwnd_;
std::wstring text_;
};
} // namespace atom
#endif // ATOM_BROWSER_NOTIFICATIONS_WIN_WIN32_DESKTOP_NOTIFICATIONS_TOAST_UIA_H_

View file

@ -0,0 +1,71 @@
// Copyright (c) 2015 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include "atom/browser/notifications/win/win32_notification.h"
#include <windows.h>
#include <string>
#include <utility>
#include <vector>
#include "third_party/skia/include/core/SkBitmap.h"
namespace atom {
void Win32Notification::Show(const NotificationOptions& options) {
auto* presenter = static_cast<NotificationPresenterWin7*>(this->presenter());
if (!presenter)
return;
HBITMAP image = NULL;
if (!options.icon.drawsNothing()) {
if (options.icon.colorType() == kBGRA_8888_SkColorType) {
BITMAPINFOHEADER bmi = {sizeof(BITMAPINFOHEADER)};
bmi.biWidth = options.icon.width();
bmi.biHeight = -options.icon.height();
bmi.biPlanes = 1;
bmi.biBitCount = 32;
bmi.biCompression = BI_RGB;
HDC hdcScreen = GetDC(NULL);
image =
CreateDIBitmap(hdcScreen, &bmi, CBM_INIT, options.icon.getPixels(),
reinterpret_cast<BITMAPINFO*>(&bmi), DIB_RGB_COLORS);
ReleaseDC(NULL, hdcScreen);
}
}
Win32Notification* existing = nullptr;
if (!options.tag.empty())
existing = presenter->GetNotificationObjectByTag(options.tag);
if (existing) {
existing->tag_.clear();
this->notification_ref_ = std::move(existing->notification_ref_);
this->notification_ref_.Set(options.title, options.msg, image);
// Need to remove the entry in the notifications set that
// NotificationPresenter is holding
existing->Destroy();
} else {
this->notification_ref_ =
presenter->AddNotification(options.title, options.msg, image);
}
this->tag_ = options.tag;
if (image)
DeleteObject(image);
}
void Win32Notification::Dismiss() {
notification_ref_.Close();
}
} // namespace atom

View file

@ -0,0 +1,38 @@
// Copyright (c) 2015 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ATOM_BROWSER_NOTIFICATIONS_WIN_WIN32_NOTIFICATION_H_
#define ATOM_BROWSER_NOTIFICATIONS_WIN_WIN32_NOTIFICATION_H_
#include <string>
#include "atom/browser/notifications/notification.h"
#include "atom/browser/notifications/win/notification_presenter_win7.h"
namespace atom {
class Win32Notification : public atom::Notification {
public:
Win32Notification(NotificationDelegate* delegate,
NotificationPresenterWin7* presenter)
: Notification(delegate, presenter) {}
void Show(const NotificationOptions& options) override;
void Dismiss() override;
const DesktopNotificationController::Notification& GetRef() const {
return notification_ref_;
}
const std::string& GetTag() const { return tag_; }
private:
DesktopNotificationController::Notification notification_ref_;
std::string tag_;
DISALLOW_COPY_AND_ASSIGN(Win32Notification);
};
} // namespace atom
#endif // ATOM_BROWSER_NOTIFICATIONS_WIN_WIN32_NOTIFICATION_H_

View file

@ -0,0 +1,449 @@
// 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 "atom/browser/notifications/win/windows_toast_notification.h"
#include <shlobj.h>
#include <vector>
#include "atom/browser/notifications/notification_delegate.h"
#include "atom/browser/notifications/win/notification_presenter_win.h"
#include "atom/browser/win/scoped_hstring.h"
#include "atom/common/application_info.h"
#include "base/environment.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/post_task.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
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;
namespace atom {
namespace {
bool IsDebuggingNotifications() {
return base::Environment::Create()->HasVar("ELECTRON_DEBUG_NOTIFICATIONS");
}
} // 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);
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;
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_));
}
}
WindowsToastNotification::WindowsToastNotification(
NotificationDelegate* delegate,
NotificationPresenter* presenter)
: Notification(delegate, presenter) {}
WindowsToastNotification::~WindowsToastNotification() {
// Remove the notification on exit.
if (toast_notification_) {
RemoveCallbacks(toast_notification_.Get());
Dismiss();
}
}
void WindowsToastNotification::Show(const NotificationOptions& options) {
auto* presenter_win = static_cast<NotificationPresenterWin*>(presenter());
std::wstring icon_path =
presenter_win->SaveIconToFilesystem(options.icon, options.icon_url);
ComPtr<IXmlDocument> toast_xml;
if (FAILED(GetToastXml(toast_manager_.Get(), options.title, options.msg,
icon_path, options.silent, &toast_xml))) {
NotificationFailed();
return;
}
ScopedHString toast_str(
RuntimeClass_Windows_UI_Notifications_ToastNotification);
if (!toast_str.success()) {
NotificationFailed();
return;
}
ComPtr<ABI::Windows::UI::Notifications::IToastNotificationFactory>
toast_factory;
if (FAILED(Windows::Foundation::GetActivationFactory(toast_str,
&toast_factory))) {
NotificationFailed();
return;
}
if (FAILED(toast_factory->CreateToastNotification(toast_xml.Get(),
&toast_notification_))) {
NotificationFailed();
return;
}
if (!SetupCallbacks(toast_notification_.Get())) {
NotificationFailed();
return;
}
if (FAILED(toast_notifier_->Show(toast_notification_.Get()))) {
NotificationFailed();
return;
}
if (IsDebuggingNotifications())
LOG(INFO) << "Notification created";
if (delegate())
delegate()->NotificationDisplayed();
}
void WindowsToastNotification::Dismiss() {
if (IsDebuggingNotifications())
LOG(INFO) << "Hiding notification";
toast_notifier_->Hide(toast_notification_.Get());
}
bool WindowsToastNotification::GetToastXml(
ABI::Windows::UI::Notifications::IToastNotificationManagerStatics*
toastManager,
const std::wstring& title,
const std::wstring& msg,
const std::wstring& icon_path,
bool silent,
IXmlDocument** toast_xml) {
ABI::Windows::UI::Notifications::ToastTemplateType template_type;
if (title.empty() || msg.empty()) {
// Single line toast.
template_type =
icon_path.empty()
? ABI::Windows::UI::Notifications::ToastTemplateType_ToastText01
: ABI::Windows::UI::Notifications::
ToastTemplateType_ToastImageAndText01;
if (FAILED(toast_manager_->GetTemplateContent(template_type, toast_xml)))
return false;
if (!SetXmlText(*toast_xml, title.empty() ? msg : title))
return false;
} else {
// Title and body toast.
template_type =
icon_path.empty()
? ABI::Windows::UI::Notifications::ToastTemplateType_ToastText02
: ABI::Windows::UI::Notifications::
ToastTemplateType_ToastImageAndText02;
if (FAILED(toastManager->GetTemplateContent(template_type, toast_xml))) {
if (IsDebuggingNotifications())
LOG(INFO) << "Fetching XML template failed";
return false;
}
if (!SetXmlText(*toast_xml, title, msg)) {
if (IsDebuggingNotifications())
LOG(INFO) << "Setting text fields on template failed";
return false;
}
}
// Configure the toast's notification sound
if (silent) {
if (FAILED(SetXmlAudioSilent(*toast_xml))) {
if (IsDebuggingNotifications()) {
LOG(INFO) << "Setting \"silent\" option on notification failed";
}
return false;
}
}
// Configure the toast's image
if (!icon_path.empty())
return SetXmlImage(*toast_xml, icon_path);
return true;
}
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;
ComPtr<IXmlElement> audio_element;
ScopedHString audio_str(L"audio");
if (FAILED(doc->CreateElement(audio_str, &audio_element)))
return false;
ComPtr<IXmlNode> audio_node_tmp;
if (FAILED(audio_element.As(&audio_node_tmp)))
return false;
// Append audio node to toast xml
ComPtr<IXmlNode> audio_node;
if (FAILED(root->AppendChild(audio_node_tmp.Get(), &audio_node)))
return false;
// Create silent attribute
ComPtr<IXmlNamedNodeMap> attributes;
if (FAILED(audio_node->get_Attributes(&attributes)))
return false;
ComPtr<IXmlAttribute> silent_attribute;
ScopedHString silent_str(L"silent");
if (FAILED(doc->CreateAttribute(silent_str, &silent_attribute)))
return false;
ComPtr<IXmlNode> silent_attribute_node;
if (FAILED(silent_attribute.As(&silent_attribute_node)))
return false;
// Set silent attribute to true
ScopedHString silent_value(L"true");
if (!silent_value.success())
return false;
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;
ComPtr<IXmlNode> child_node;
if (FAILED(
silent_attribute_node->AppendChild(silent_node.Get(), &child_node)))
return false;
ComPtr<IXmlNode> silent_attribute_pnode;
return SUCCEEDED(attributes.Get()->SetNamedItem(silent_attribute_node.Get(),
&silent_attribute_pnode));
}
bool WindowsToastNotification::SetXmlText(IXmlDocument* doc,
const std::wstring& text) {
ScopedHString tag;
ComPtr<IXmlNodeList> node_list;
if (!GetTextNodeList(&tag, doc, &node_list, 1))
return false;
ComPtr<IXmlNode> node;
if (FAILED(node_list->Item(0, &node)))
return false;
return AppendTextToXml(doc, node.Get(), text);
}
bool WindowsToastNotification::SetXmlText(IXmlDocument* doc,
const std::wstring& title,
const std::wstring& body) {
ScopedHString tag;
ComPtr<IXmlNodeList> node_list;
if (!GetTextNodeList(&tag, doc, &node_list, 2))
return false;
ComPtr<IXmlNode> node;
if (FAILED(node_list->Item(0, &node)))
return false;
if (!AppendTextToXml(doc, node.Get(), title))
return false;
if (FAILED(node_list->Item(1, &node)))
return false;
return AppendTextToXml(doc, node.Get(), body);
}
bool WindowsToastNotification::SetXmlImage(IXmlDocument* doc,
const std::wstring& icon_path) {
ScopedHString tag(L"image");
if (!tag.success())
return false;
ComPtr<IXmlNodeList> node_list;
if (FAILED(doc->GetElementsByTagName(tag, &node_list)))
return false;
ComPtr<IXmlNode> image_node;
if (FAILED(node_list->Item(0, &image_node)))
return false;
ComPtr<IXmlNamedNodeMap> attrs;
if (FAILED(image_node->get_Attributes(&attrs)))
return false;
ScopedHString src(L"src");
if (!src.success())
return false;
ComPtr<IXmlNode> src_attr;
if (FAILED(attrs->GetNamedItem(src, &src_attr)))
return false;
ScopedHString img_path(icon_path.c_str());
if (!img_path.success())
return false;
ComPtr<IXmlText> src_text;
if (FAILED(doc->CreateTextNode(img_path, &src_text)))
return false;
ComPtr<IXmlNode> src_node;
if (FAILED(src_text.As(&src_node)))
return false;
ComPtr<IXmlNode> child_node;
return SUCCEEDED(src_attr->AppendChild(src_node.Get(), &child_node));
}
bool WindowsToastNotification::GetTextNodeList(ScopedHString* tag,
IXmlDocument* doc,
IXmlNodeList** node_list,
uint32_t req_length) {
tag->Reset(L"text");
if (!tag->success())
return false;
if (FAILED(doc->GetElementsByTagName(*tag, node_list)))
return false;
uint32_t node_length;
if (FAILED((*node_list)->get_Length(&node_length)))
return false;
return node_length >= req_length;
}
bool WindowsToastNotification::AppendTextToXml(IXmlDocument* doc,
IXmlNode* node,
const std::wstring& text) {
ScopedHString str(text);
if (!str.success())
return false;
ComPtr<IXmlText> xml_text;
if (FAILED(doc->CreateTextNode(str, &xml_text)))
return false;
ComPtr<IXmlNode> text_node;
if (FAILED(xml_text.As(&text_node)))
return false;
ComPtr<IXmlNode> append_node;
return SUCCEEDED(node->AppendChild(text_node.Get(), &append_node));
}
bool WindowsToastNotification::SetupCallbacks(
ABI::Windows::UI::Notifications::IToastNotification* toast) {
event_handler_ = Make<ToastEventHandler>(this);
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_)))
return false;
if (FAILED(toast->remove_Dismissed(dismissed_token_)))
return false;
return SUCCEEDED(toast->remove_Failed(failed_token_));
}
/*
/ Toast Event Handler
*/
ToastEventHandler::ToastEventHandler(Notification* notification)
: notification_(notification->GetWeakPtr()) {}
ToastEventHandler::~ToastEventHandler() {}
IFACEMETHODIMP ToastEventHandler::Invoke(
ABI::Windows::UI::Notifications::IToastNotification* sender,
IInspectable* args) {
base::PostTaskWithTraits(
FROM_HERE, {content::BrowserThread::UI},
base::BindOnce(&Notification::NotificationClicked, notification_));
if (IsDebuggingNotifications())
LOG(INFO) << "Notification clicked";
return S_OK;
}
IFACEMETHODIMP ToastEventHandler::Invoke(
ABI::Windows::UI::Notifications::IToastNotification* sender,
ABI::Windows::UI::Notifications::IToastDismissedEventArgs* e) {
base::PostTaskWithTraits(
FROM_HERE, {content::BrowserThread::UI},
base::BindOnce(&Notification::NotificationDismissed, notification_));
if (IsDebuggingNotifications())
LOG(INFO) << "Notification dismissed";
return S_OK;
}
IFACEMETHODIMP ToastEventHandler::Invoke(
ABI::Windows::UI::Notifications::IToastNotification* sender,
ABI::Windows::UI::Notifications::IToastFailedEventArgs* e) {
base::PostTaskWithTraits(
FROM_HERE, {content::BrowserThread::UI},
base::BindOnce(&Notification::NotificationFailed, notification_));
if (IsDebuggingNotifications())
LOG(INFO) << "Notification failed";
return S_OK;
}
} // namespace atom

View file

@ -0,0 +1,131 @@
// 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.
#ifndef ATOM_BROWSER_NOTIFICATIONS_WIN_WINDOWS_TOAST_NOTIFICATION_H_
#define ATOM_BROWSER_NOTIFICATIONS_WIN_WINDOWS_TOAST_NOTIFICATION_H_
#include <windows.h>
#include <windows.ui.notifications.h>
#include <wrl/implements.h>
#include <string>
#include <vector>
#include "atom/browser/notifications/notification.h"
using Microsoft::WRL::ClassicCom;
using Microsoft::WRL::ComPtr;
using Microsoft::WRL::Make;
using Microsoft::WRL::RuntimeClass;
using Microsoft::WRL::RuntimeClassFlags;
namespace atom {
class ScopedHString;
using DesktopToastActivatedEventHandler =
ABI::Windows::Foundation::ITypedEventHandler<
ABI::Windows::UI::Notifications::ToastNotification*,
IInspectable*>;
using DesktopToastDismissedEventHandler =
ABI::Windows::Foundation::ITypedEventHandler<
ABI::Windows::UI::Notifications::ToastNotification*,
ABI::Windows::UI::Notifications::ToastDismissedEventArgs*>;
using DesktopToastFailedEventHandler =
ABI::Windows::Foundation::ITypedEventHandler<
ABI::Windows::UI::Notifications::ToastNotification*,
ABI::Windows::UI::Notifications::ToastFailedEventArgs*>;
class WindowsToastNotification : public Notification {
public:
// Should only be called by NotificationPresenterWin.
static bool Initialize();
WindowsToastNotification(NotificationDelegate* delegate,
NotificationPresenter* presenter);
~WindowsToastNotification() override;
protected:
// Notification:
void Show(const NotificationOptions& options) override;
void Dismiss() override;
private:
friend class ToastEventHandler;
bool GetToastXml(
ABI::Windows::UI::Notifications::IToastNotificationManagerStatics*
toastManager,
const std::wstring& title,
const std::wstring& msg,
const std::wstring& icon_path,
const bool silent,
ABI::Windows::Data::Xml::Dom::IXmlDocument** toastXml);
bool SetXmlAudioSilent(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,
const std::wstring& title,
const std::wstring& body);
bool SetXmlImage(ABI::Windows::Data::Xml::Dom::IXmlDocument* doc,
const std::wstring& icon_path);
bool GetTextNodeList(ScopedHString* tag,
ABI::Windows::Data::Xml::Dom::IXmlDocument* doc,
ABI::Windows::Data::Xml::Dom::IXmlNodeList** nodeList,
uint32_t reqLength);
bool AppendTextToXml(ABI::Windows::Data::Xml::Dom::IXmlDocument* doc,
ABI::Windows::Data::Xml::Dom::IXmlNode* node,
const std::wstring& text);
bool SetupCallbacks(
ABI::Windows::UI::Notifications::IToastNotification* toast);
bool RemoveCallbacks(
ABI::Windows::UI::Notifications::IToastNotification* toast);
static ComPtr<
ABI::Windows::UI::Notifications::IToastNotificationManagerStatics>
toast_manager_;
static ComPtr<ABI::Windows::UI::Notifications::IToastNotifier>
toast_notifier_;
EventRegistrationToken activated_token_;
EventRegistrationToken dismissed_token_;
EventRegistrationToken failed_token_;
ComPtr<ToastEventHandler> event_handler_;
ComPtr<ABI::Windows::UI::Notifications::IToastNotification>
toast_notification_;
DISALLOW_COPY_AND_ASSIGN(WindowsToastNotification);
};
class ToastEventHandler : public RuntimeClass<RuntimeClassFlags<ClassicCom>,
DesktopToastActivatedEventHandler,
DesktopToastDismissedEventHandler,
DesktopToastFailedEventHandler> {
public:
explicit ToastEventHandler(Notification* notification);
~ToastEventHandler() override;
IFACEMETHODIMP Invoke(
ABI::Windows::UI::Notifications::IToastNotification* sender,
IInspectable* args) override;
IFACEMETHODIMP Invoke(
ABI::Windows::UI::Notifications::IToastNotification* sender,
ABI::Windows::UI::Notifications::IToastDismissedEventArgs* e) override;
IFACEMETHODIMP Invoke(
ABI::Windows::UI::Notifications::IToastNotification* sender,
ABI::Windows::UI::Notifications::IToastFailedEventArgs* e) override;
private:
base::WeakPtr<Notification> notification_; // weak ref.
DISALLOW_COPY_AND_ASSIGN(ToastEventHandler);
};
} // namespace atom
#endif // ATOM_BROWSER_NOTIFICATIONS_WIN_WINDOWS_TOAST_NOTIFICATION_H_