Merge pull request #279 from electron/win7_notifications

Windows 7 notifications
This commit is contained in:
Cheng Zhao 2017-04-11 15:06:26 +09:00 committed by GitHub
commit 8f7bd436f0
23 changed files with 1696 additions and 40 deletions

View file

@ -13,10 +13,6 @@
#include "browser/platform_notification_service.h"
#include "content/public/common/url_constants.h"
#if defined(OS_WIN)
#include "base/win/windows_version.h"
#endif
namespace brightray {
namespace {
@ -39,12 +35,6 @@ BrowserClient::~BrowserClient() {
}
NotificationPresenter* BrowserClient::GetNotificationPresenter() {
#if defined(OS_WIN)
// Bail out if on Windows 7 or even lower, no operating will follow
if (base::win::GetVersion() < base::win::VERSION_WIN8)
return nullptr;
#endif
if (!notification_presenter_) {
// Create a new presenter if on OS X, Linux, or Windows 8+
notification_presenter_.reset(NotificationPresenter::Create());

View file

@ -55,12 +55,6 @@ void log_and_clear_error(GError* error, const char* context) {
} // namespace
// static
Notification* Notification::Create(NotificationDelegate* delegate,
NotificationPresenter* presenter) {
return new LibnotifyNotification(delegate, presenter);
}
// static
bool LibnotifyNotification::Initialize() {
if (!libnotify_loader_.Load("libnotify.so.4") && // most common one

View file

@ -22,4 +22,9 @@ NotificationPresenterLinux::NotificationPresenterLinux() {
NotificationPresenterLinux::~NotificationPresenterLinux() {
}
Notification* NotificationPresenterLinux::CreateNotificationObject(
NotificationDelegate* delegate) {
return new LibnotifyNotification(delegate, this);
}
} // namespace brightray

View file

@ -16,6 +16,9 @@ class NotificationPresenterLinux : public NotificationPresenter {
~NotificationPresenterLinux();
private:
Notification* CreateNotificationObject(
NotificationDelegate* delegate) override;
DISALLOW_COPY_AND_ASSIGN(NotificationPresenterLinux);
};

View file

@ -12,12 +12,6 @@
namespace brightray {
// static
Notification* Notification::Create(NotificationDelegate* delegate,
NotificationPresenter* presenter) {
return new CocoaNotification(delegate, presenter);
}
CocoaNotification::CocoaNotification(NotificationDelegate* delegate,
NotificationPresenter* presenter)
: Notification(delegate, presenter) {

View file

@ -22,6 +22,9 @@ class NotificationPresenterMac : public NotificationPresenter {
~NotificationPresenterMac();
private:
Notification* CreateNotificationObject(
NotificationDelegate* delegate) override;
base::scoped_nsobject<NotificationCenterDelegate>
notification_center_delegate_;

View file

@ -35,4 +35,9 @@ NotificationPresenterMac::~NotificationPresenterMac() {
NSUserNotificationCenter.defaultUserNotificationCenter.delegate = nil;
}
Notification* NotificationPresenterMac::CreateNotificationObject(
NotificationDelegate* delegate) {
return new CocoaNotification(delegate, this);
}
} // namespace brightray

View file

@ -47,16 +47,11 @@ class Notification {
protected:
Notification(NotificationDelegate* delegate,
NotificationPresenter* presenter);
public:
virtual ~Notification();
private:
friend class NotificationPresenter;
// Can only be called by NotificationPresenter, the caller is responsible of
// freeing the returned instance.
static Notification* Create(NotificationDelegate* delegate,
NotificationPresenter* presenter);
NotificationDelegate* delegate_;
NotificationPresenter* presenter_;

View file

@ -18,7 +18,7 @@ NotificationPresenter::~NotificationPresenter() {
base::WeakPtr<Notification> NotificationPresenter::CreateNotification(
NotificationDelegate* delegate) {
Notification* notification = Notification::Create(delegate, this);
Notification* notification = CreateNotificationObject(delegate);
notifications_.insert(notification);
return notification->GetWeakPtr();
}

View file

@ -27,6 +27,8 @@ class NotificationPresenter {
protected:
NotificationPresenter();
virtual Notification* CreateNotificationObject(
NotificationDelegate* delegate) = 0;
private:
friend class Notification;

View file

@ -10,6 +10,7 @@
#include "base/md5.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/windows_version.h"
#include "browser/win/notification_presenter_win7.h"
#include "browser/win/windows_toast_notification.h"
#include "content/public/browser/desktop_notification_delegate.h"
#include "content/public/common/platform_notification_data.h"
@ -36,6 +37,9 @@ bool SaveIconToPath(const SkBitmap& bitmap, const base::FilePath& path) {
// 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(
@ -66,4 +70,9 @@ base::string16 NotificationPresenterWin::SaveIconToFilesystem(
return base::UTF8ToUTF16(origin.spec());
}
Notification* NotificationPresenterWin::CreateNotificationObject(
NotificationDelegate* delegate) {
return new WindowsToastNotification(delegate, this);
}
} // namespace brightray

View file

@ -42,6 +42,9 @@ class NotificationPresenterWin : public NotificationPresenter {
base::string16 SaveIconToFilesystem(const SkBitmap& icon, const GURL& origin);
private:
Notification* CreateNotificationObject(
NotificationDelegate* delegate) override;
base::ScopedTempDir temp_dir_;
DISALLOW_COPY_AND_ASSIGN(NotificationPresenterWin);

View file

@ -0,0 +1,45 @@
#include "browser/win/notification_presenter_win7.h"
#include "browser/win/win32_notification.h"
namespace brightray {
brightray::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(
Notification& notification) {
auto n = GetNotificationObjectByRef(notification);
if (n) n->NotificationClicked();
}
void NotificationPresenterWin7::OnNotificationDismissed(
Notification& notification) {
auto n = GetNotificationObjectByRef(notification);
if (n) n->NotificationDismissed();
}
} // namespace brightray

View file

@ -0,0 +1,30 @@
#pragma once
#include "browser/notification_presenter.h"
#include "browser/win/win32_desktop_notifications/desktop_notification_controller.h"
namespace brightray {
class Win32Notification;
class NotificationPresenterWin7 :
public NotificationPresenter,
public DesktopNotificationController {
public:
NotificationPresenterWin7() = default;
Win32Notification* GetNotificationObjectByRef(
const DesktopNotificationController::Notification& ref);
Win32Notification* GetNotificationObjectByTag(const std::string& tag);
private:
brightray::Notification* CreateNotificationObject(
NotificationDelegate* delegate) override;
void OnNotificationClicked(Notification& notification) override;
void OnNotificationDismissed(Notification& notification) override;
DISALLOW_COPY_AND_ASSIGN(NotificationPresenterWin7);
};
} // namespace brightray

View file

@ -0,0 +1,55 @@
#pragma once
#include <Windows.h>
namespace brightray {
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>
inline T ScaleForDpi(T value, unsigned dpi) {
return value * dpi / 96;
}
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 brightray

View file

@ -0,0 +1,404 @@
#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include "browser/win/win32_desktop_notifications/desktop_notification_controller.h"
#include <windowsx.h>
#include <algorithm>
#include <vector>
#include "browser/win/win32_desktop_notifications/common.h"
#include "browser/win/win32_desktop_notifications/toast.h"
using std::make_shared;
using std::shared_ptr;
namespace brightray {
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;
}
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() {
_ASSERT(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 baseHeight = metrics.lfMessageFont.lfHeight;
HDC hdc = GetDC(NULL);
auto dpi_y = GetDeviceCaps(hdc, LOGPIXELSY);
ReleaseDC(NULL, hdc);
metrics.lfMessageFont.lfHeight =
(LONG)ScaleForDpi(baseHeight * 1.1f, dpi_y);
body_font_ = CreateFontIndirect(&metrics.lfMessageFont);
if (caption_font_) DeleteFont(caption_font_);
metrics.lfMessageFont.lfHeight =
(LONG)ScaleForDpi(baseHeight * 1.4f, 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_<int>) };
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) {
_ASSERT(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>);
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(
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();
_ASSERT(item.hwnd);
ScreenMetrics scr;
auto toast = Toast::Get(item.hwnd);
toast_pos = toast->GetVerticalPosition() +
toast->GetHeight() +
scr.Y(toast_margin_<int>);
}
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) {
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(
const shared_ptr<NotificationData>& data) :
data_(data) {
_ASSERT(data != nullptr);
}
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
_ASSERT(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
_ASSERT(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 brightray

View file

@ -0,0 +1,106 @@
#pragma once
#include <deque>
#include <memory>
#include <string>
#include <vector>
#include <Windows.h>
namespace brightray {
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(Notification& notification);
// Event handlers -- override to receive the events
private:
virtual void OnNotificationClosed(Notification& notification) {}
virtual void OnNotificationClicked(Notification& notification) {}
virtual void OnNotificationDismissed(Notification& notification) {}
private:
static HINSTANCE RegisterWndClasses();
void StartAnimation();
HFONT GetCaptionFont();
HFONT GetBodyFont();
private:
enum TimerID {
TimerID_Animate = 1
};
template<typename T>
static constexpr T 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 constexpr const TCHAR class_name_[] =
TEXT("DesktopNotificationController");
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() = default;
explicit Notification(const std::shared_ptr<NotificationData>& data);
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 brightray

View file

@ -0,0 +1,822 @@
#define NOMINMAX
#include "browser/win/win32_desktop_notifications/toast.h"
#include <uxtheme.h>
#include <windowsx.h>
#include <algorithm>
#include "browser/win/win32_desktop_notifications/common.h"
#pragma comment(lib, "msimg32.lib")
#pragma comment(lib, "uxtheme.lib")
using std::min;
using std::shared_ptr;
namespace brightray {
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;
}
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() {
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_MOUSEACTIVATE:
return MA_NOACTIVATE;
case WM_TIMER:
if (wparam == TimerID_AutoDismiss) {
Get(hwnd)->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;
}
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);
_ASSERT(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() {
SetTimer(hwnd_, TimerID_AutoDismiss, 4000, 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).
auto ulw_result = UpdateLayeredWindowIndirect(hwnd_, &ulw);
hdwp = DeferWindowPos(hdwp, hwnd_, HWND_TOPMOST,
pt.x, pt.y, size.cx, size.cy, dwpFlags);
return hdwp;
}
void DesktopNotificationController::Toast::StartEaseIn() {
_ASSERT(!ease_in_active_);
ease_in_start_ = GetTickCount();
ease_in_active_ = true;
data_->controller->StartAnimation();
}
void DesktopNotificationController::Toast::StartEaseOut() {
_ASSERT(!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 float duration = 500.0f;
float elapsed = GetTickCount() - ease_in_start_;
float time = std::min(duration, elapsed) / 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 float duration = 120.0f;
float elapsed = GetTickCount() - ease_out_start_;
float time = std::min(duration, elapsed) / 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 float duration = 500.0f;
float elapsed = GetTickCount() - stack_collapse_start_;
float time = std::min(duration, elapsed) / 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 brightray

View file

@ -0,0 +1,97 @@
#pragma once
#include "browser/win/win32_desktop_notifications/desktop_notification_controller.h"
namespace brightray {
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 {
_ASSERT(!(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 constexpr const TCHAR class_name_[] =
TEXT("DesktopNotificationToast");
const HWND hwnd_;
HDC hdc_;
HBITMAP bitmap_ = NULL;
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 brightray

View file

@ -0,0 +1,58 @@
#define WIN32_LEAN_AND_MEAN
#include "browser/win/win32_notification.h"
#include <windows.h>
#include "third_party/skia/include/core/SkBitmap.h"
namespace brightray {
void Win32Notification::Show(
const base::string16& title, const base::string16& msg,
const std::string& tag, const GURL& icon_url,
const SkBitmap& icon, const bool silent) {
auto presenter = static_cast<NotificationPresenterWin7*>(this->presenter());
if (!presenter) return;
HBITMAP image = NULL;
if (!icon.drawsNothing()) {
if (icon.colorType() == kBGRA_8888_SkColorType) {
icon.lockPixels();
BITMAPINFOHEADER bmi = { sizeof(BITMAPINFOHEADER) };
bmi.biWidth = icon.width();
bmi.biHeight = -icon.height();
bmi.biPlanes = 1;
bmi.biBitCount = 32;
bmi.biCompression = BI_RGB;
HDC hdcScreen = GetDC(NULL);
image = CreateDIBitmap(hdcScreen, &bmi, CBM_INIT, icon.getPixels(),
reinterpret_cast<BITMAPINFO*>(&bmi),
DIB_RGB_COLORS);
ReleaseDC(NULL, hdcScreen);
icon.unlockPixels();
}
}
Win32Notification* existing = nullptr;
if (!tag.empty()) existing = presenter->GetNotificationObjectByTag(tag);
if (existing) {
existing->tag_.clear();
this->notification_ref_ = std::move(existing->notification_ref_);
this->notification_ref_.Set(title, msg, image);
} else {
this->notification_ref_ = presenter->AddNotification(title, msg, image);
}
this->tag_ = tag;
if (image) DeleteObject(image);
}
void Win32Notification::Dismiss() {
notification_ref_.Close();
}
} // namespace brightray

View file

@ -0,0 +1,33 @@
#pragma once
#include "browser/notification.h"
#include "browser/win/notification_presenter_win7.h"
namespace brightray {
class Win32Notification : public brightray::Notification {
public:
Win32Notification(NotificationDelegate* delegate,
NotificationPresenterWin7* presenter) :
Notification(delegate, presenter) {
}
void Show(const base::string16& title, const base::string16& msg,
const std::string& tag, const GURL& icon_url,
const SkBitmap& icon, const bool silent) override;
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 brightray

View file

@ -42,12 +42,6 @@ bool GetAppUserModelId(ScopedHString* app_id) {
} // namespace
// static
Notification* Notification::Create(NotificationDelegate* delegate,
NotificationPresenter* presenter) {
return new WindowsToastNotification(delegate, presenter);
}
// static
ComPtr<ABI::Windows::UI::Notifications::IToastNotificationManagerStatics>
WindowsToastNotification::toast_manager_;

View file

@ -84,12 +84,21 @@
'browser/linux/libnotify_notification.cc',
'browser/linux/notification_presenter_linux.h',
'browser/linux/notification_presenter_linux.cc',
'browser/win/notification_presenter_win.h',
'browser/win/notification_presenter_win.cc',
'browser/win/windows_toast_notification.h',
'browser/win/windows_toast_notification.cc',
'browser/win/scoped_hstring.h',
'browser/win/notification_presenter_win.h',
'browser/win/notification_presenter_win7.cc',
'browser/win/notification_presenter_win7.h',
'browser/win/scoped_hstring.cc',
'browser/win/scoped_hstring.h',
'browser/win/win32_desktop_notifications/common.h',
'browser/win/win32_desktop_notifications/desktop_notification_controller.cc',
'browser/win/win32_desktop_notifications/desktop_notification_controller.h',
'browser/win/win32_desktop_notifications/toast.cc',
'browser/win/win32_desktop_notifications/toast.h',
'browser/win/win32_notification.cc',
'browser/win/win32_notification.h',
'browser/win/windows_toast_notification.cc',
'browser/win/windows_toast_notification.h',
'browser/special_storage_policy.cc',
'browser/special_storage_policy.h',
'browser/url_request_context_getter.cc',