From 1cb135f7f2a262e385881dd1cd9a695db1303517 Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Tue, 3 Jun 2014 11:25:09 +0800 Subject: [PATCH] win: Implement tray icon API. --- atom.gyp | 7 +- atom/browser/ui/tray_icon_win.cc | 24 +-- atom/browser/ui/win/notify_icon.cc | 138 +++++++++++++++ atom/browser/ui/win/notify_icon.h | 74 ++++++++ atom/browser/ui/win/notify_icon_host.cc | 226 ++++++++++++++++++++++++ atom/browser/ui/win/notify_icon_host.h | 79 +++++++++ 6 files changed, 527 insertions(+), 21 deletions(-) create mode 100644 atom/browser/ui/win/notify_icon.cc create mode 100644 atom/browser/ui/win/notify_icon.h create mode 100644 atom/browser/ui/win/notify_icon_host.cc create mode 100644 atom/browser/ui/win/notify_icon_host.h diff --git a/atom.gyp b/atom.gyp index d0503227386a..e3075b4d985f 100644 --- a/atom.gyp +++ b/atom.gyp @@ -145,11 +145,14 @@ 'atom/browser/ui/tray_icon_cocoa.mm', 'atom/browser/ui/tray_icon_observer.h', 'atom/browser/ui/tray_icon_win.cc', - 'atom/browser/ui/tray_icon_win.h', 'atom/browser/ui/win/menu_2.cc', 'atom/browser/ui/win/menu_2.h', 'atom/browser/ui/win/native_menu_win.cc', 'atom/browser/ui/win/native_menu_win.h', + 'atom/browser/ui/win/notify_icon_host.cc', + 'atom/browser/ui/win/notify_icon_host.h', + 'atom/browser/ui/win/notify_icon.cc', + 'atom/browser/ui/win/notify_icon.h', 'atom/browser/window_list.cc', 'atom/browser/window_list.h', 'atom/browser/window_list_observer.h', @@ -232,6 +235,8 @@ 'chrome/browser/ui/gtk/gtk_window_util.h', 'chrome/browser/ui/gtk/menu_gtk.cc', 'chrome/browser/ui/gtk/menu_gtk.h', + 'chrome/browser/ui/views/status_icons/status_tray_state_changer_win.cc', + 'chrome/browser/ui/views/status_icons/status_tray_state_changer_win.h', '<@(native_mate_files)', ], 'framework_sources': [ diff --git a/atom/browser/ui/tray_icon_win.cc b/atom/browser/ui/tray_icon_win.cc index 3c8e36ebc910..f3b7581c2db6 100644 --- a/atom/browser/ui/tray_icon_win.cc +++ b/atom/browser/ui/tray_icon_win.cc @@ -2,31 +2,15 @@ // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. -#include "atom/browser/ui/tray_icon_win.h" +#include "atom/browser/ui/win/notify_icon.h" +#include "atom/browser/ui/win/notify_icon_host.h" namespace atom { -TrayIconWin::TrayIconWin() { -} - -TrayIconWin::~TrayIconWin() { -} - -void TrayIconWin::SetImage(const gfx::ImageSkia& image) { -} - -void TrayIconWin::SetPressedImage(const gfx::ImageSkia& image) { -} - -void TrayIconWin::SetToolTip(const std::string& tool_tip) { -} - -void TrayIconWin::SetContextMenu(ui::SimpleMenuModel* menu_model) { -} - // static TrayIcon* TrayIcon::Create() { - return new TrayIconWin; + static NotifyIconHost host; + return host.CreateNotifyIcon(); } } // namespace atom diff --git a/atom/browser/ui/win/notify_icon.cc b/atom/browser/ui/win/notify_icon.cc new file mode 100644 index 000000000000..b44791fe90a6 --- /dev/null +++ b/atom/browser/ui/win/notify_icon.cc @@ -0,0 +1,138 @@ +// Copyright (c) 2014 GitHub, Inc. All rights reserved. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/ui/win/notify_icon.h" + +#include "atom/browser/ui/win/notify_icon_host.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/utf_string_conversions.h" +#include "base/win/windows_version.h" +#include "third_party/skia/include/core/SkBitmap.h" +#include "ui/gfx/icon_util.h" +#include "ui/gfx/point.h" +#include "ui/gfx/rect.h" + +namespace atom { + +NotifyIcon::NotifyIcon(NotifyIconHost* host, + UINT id, + HWND window, + UINT message) + : host_(host), + icon_id_(id), + window_(window), + message_id_(message) { + NOTIFYICONDATA icon_data; + InitIconData(&icon_data); + icon_data.uFlags = NIF_MESSAGE; + icon_data.uCallbackMessage = message_id_; + BOOL result = Shell_NotifyIcon(NIM_ADD, &icon_data); + // This can happen if the explorer process isn't running when we try to + // create the icon for some reason (for example, at startup). + if (!result) + LOG(WARNING) << "Unable to create status tray icon."; +} + +NotifyIcon::~NotifyIcon() { + // Remove our icon. + host_->Remove(this); + NOTIFYICONDATA icon_data; + InitIconData(&icon_data); + Shell_NotifyIcon(NIM_DELETE, &icon_data); +} + +void NotifyIcon::HandleClickEvent(const gfx::Point& cursor_pos, + bool left_mouse_click) { + // Pass to the observer if appropriate. + if (left_mouse_click) { + NotifyClicked(); + return; + } + + /* + if (!menu_model_) + return; + + // Set our window as the foreground window, so the context menu closes when + // we click away from it. + if (!SetForegroundWindow(window_)) + return; + + menu_runner_.reset(new views::MenuRunner(menu_model_)); + + ignore_result(menu_runner_->RunMenuAt(NULL, + NULL, + gfx::Rect(cursor_pos, gfx::Size()), + views::MENU_ANCHOR_TOPLEFT, + ui::MENU_SOURCE_MOUSE, + views::MenuRunner::HAS_MNEMONICS)); + */ +} + +void NotifyIcon::ResetIcon() { + NOTIFYICONDATA icon_data; + InitIconData(&icon_data); + // Delete any previously existing icon. + Shell_NotifyIcon(NIM_DELETE, &icon_data); + InitIconData(&icon_data); + icon_data.uFlags = NIF_MESSAGE; + icon_data.uCallbackMessage = message_id_; + icon_data.hIcon = icon_.Get(); + // If we have an image, then set the NIF_ICON flag, which tells + // Shell_NotifyIcon() to set the image for the status icon it creates. + if (icon_data.hIcon) + icon_data.uFlags |= NIF_ICON; + // Re-add our icon. + BOOL result = Shell_NotifyIcon(NIM_ADD, &icon_data); + if (!result) + LOG(WARNING) << "Unable to re-create status tray icon."; +} + +void NotifyIcon::SetImage(const gfx::ImageSkia& image) { + // Create the icon. + NOTIFYICONDATA icon_data; + InitIconData(&icon_data); + icon_data.uFlags = NIF_ICON; + icon_.Set(IconUtil::CreateHICONFromSkBitmap(*image.bitmap())); + icon_data.hIcon = icon_.Get(); + BOOL result = Shell_NotifyIcon(NIM_MODIFY, &icon_data); + if (!result) + LOG(WARNING) << "Error setting status tray icon image"; + else + host_->UpdateIconVisibilityInBackground(this); +} + +void NotifyIcon::SetPressedImage(const gfx::ImageSkia& image) { + // Ignore pressed images, since the standard on Windows is to not highlight + // pressed status icons. +} + +void NotifyIcon::SetToolTip(const std::string& tool_tip) { + // Create the icon. + NOTIFYICONDATA icon_data; + InitIconData(&icon_data); + icon_data.uFlags = NIF_TIP; + wcscpy_s(icon_data.szTip, UTF8ToUTF16(tool_tip).c_str()); + BOOL result = Shell_NotifyIcon(NIM_MODIFY, &icon_data); + if (!result) + LOG(WARNING) << "Unable to set tooltip for status tray icon"; +} + +void NotifyIcon::SetContextMenu(ui::SimpleMenuModel* menu_model) { +} + +void NotifyIcon::InitIconData(NOTIFYICONDATA* icon_data) { + if (base::win::GetVersion() >= base::win::VERSION_VISTA) { + memset(icon_data, 0, sizeof(NOTIFYICONDATA)); + icon_data->cbSize = sizeof(NOTIFYICONDATA); + } else { + memset(icon_data, 0, NOTIFYICONDATA_V3_SIZE); + icon_data->cbSize = NOTIFYICONDATA_V3_SIZE; + } + + icon_data->hWnd = window_; + icon_data->uID = icon_id_; +} + +} // namespace atom diff --git a/atom/browser/ui/win/notify_icon.h b/atom/browser/ui/win/notify_icon.h new file mode 100644 index 000000000000..850b3961b47d --- /dev/null +++ b/atom/browser/ui/win/notify_icon.h @@ -0,0 +1,74 @@ +// Copyright (c) 2014 GitHub, Inc. All rights reserved. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_UI_WIN_NOTIFY_ICON_H_ +#define ATOM_BROWSER_UI_WIN_NOTIFY_ICON_H_ + +#include +#include + +#include + +#include "atom/browser/ui/tray_icon.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/win/scoped_gdi_object.h" + +namespace gfx { +class Point; +} + +namespace atom { + +class NotifyIconHost; + +class NotifyIcon : public TrayIcon { + public: + // Constructor which provides this icon's unique ID and messaging window. + NotifyIcon(NotifyIconHost* host, UINT id, HWND window, UINT message); + virtual ~NotifyIcon(); + + // Handles a click event from the user - if |left_button_click| is true and + // there is a registered observer, passes the click event to the observer, + // otherwise displays the context menu if there is one. + void HandleClickEvent(const gfx::Point& cursor_pos, bool left_button_click); + + // Re-creates the status tray icon now after the taskbar has been created. + void ResetIcon(); + + UINT icon_id() const { return icon_id_; } + HWND window() const { return window_; } + UINT message_id() const { return message_id_; } + + // Overridden from TrayIcon: + virtual void SetImage(const gfx::ImageSkia& image) OVERRIDE; + virtual void SetPressedImage(const gfx::ImageSkia& image) OVERRIDE; + virtual void SetToolTip(const std::string& tool_tip) OVERRIDE; + virtual void SetContextMenu(ui::SimpleMenuModel* menu_model) OVERRIDE; + + private: + void InitIconData(NOTIFYICONDATA* icon_data); + + // The tray that owns us. Weak. + NotifyIconHost* host_; + + // The unique ID corresponding to this icon. + UINT icon_id_; + + // Window used for processing messages from this icon. + HWND window_; + + // The message identifier used for status icon messages. + UINT message_id_; + + // The currently-displayed icon for the window. + base::win::ScopedHICON icon_; + + DISALLOW_COPY_AND_ASSIGN(NotifyIcon); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_WIN_NOTIFY_ICON_H_ diff --git a/atom/browser/ui/win/notify_icon_host.cc b/atom/browser/ui/win/notify_icon_host.cc new file mode 100644 index 000000000000..7f2b54113b47 --- /dev/null +++ b/atom/browser/ui/win/notify_icon_host.cc @@ -0,0 +1,226 @@ +// Copyright (c) 2014 GitHub, Inc. All rights reserved. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "atom/browser/ui/win/notify_icon_host.h" + +#include + +#include "atom/browser/ui/win/notify_icon.h" +#include "base/bind.h" +#include "base/stl_util.h" +#include "base/threading/non_thread_safe.h" +#include "base/threading/thread.h" +#include "base/win/wrapped_window_proc.h" +#include "chrome/browser/ui/views/status_icons/status_tray_state_changer_win.h" +#include "ui/gfx/screen.h" +#include "ui/gfx/win/hwnd_util.h" + +namespace atom { + +namespace { + +const UINT kNotifyIconMessage = WM_APP + 1; + +// |kBaseIconId| is 2 to avoid conflicts with plugins that hard-code id 1. +const UINT kBaseIconId = 2; + +const wchar_t kNotifyIconHostWindowClass[] = L"AtomShell_NotifyIconHostWindow"; + +} // namespace + +// Default implementation for NotifyIconHostStateChangerProxy that communicates +// to Exporer.exe via COM. It spawns a background thread with a fresh COM +// apartment and requests that the visibility be increased unless the user +// has explicitly set the icon to be hidden. +class NotifyIconHostStateChangerProxyImpl + : public NotifyIconHostStateChangerProxy, + public base::NonThreadSafe { + public: + NotifyIconHostStateChangerProxyImpl() + : pending_requests_(0), + worker_thread_("NotifyIconCOMWorkerThread"), + weak_factory_(this) { + worker_thread_.init_com_with_mta(false); + } + + virtual void EnqueueChange(UINT icon_id, HWND window) OVERRIDE { + DCHECK(CalledOnValidThread()); + if (pending_requests_ == 0) + worker_thread_.Start(); + + ++pending_requests_; + worker_thread_.message_loop_proxy()->PostTaskAndReply( + FROM_HERE, + base::Bind( + &NotifyIconHostStateChangerProxyImpl::EnqueueChangeOnWorkerThread, + icon_id, + window), + base::Bind(&NotifyIconHostStateChangerProxyImpl::ChangeDone, + weak_factory_.GetWeakPtr())); + } + + private: + // Must be called only on |worker_thread_|, to ensure the correct COM + // apartment. + static void EnqueueChangeOnWorkerThread(UINT icon_id, HWND window) { + // It appears that IUnknowns are coincidentally compatible with + // scoped_refptr. Normally I wouldn't depend on that but it seems that + // base::win::IUnknownImpl itself depends on that coincidence so it's + // already being assumed elsewhere. + scoped_refptr status_tray_state_changer( + new StatusTrayStateChangerWin(icon_id, window)); + status_tray_state_changer->EnsureTrayIconVisible(); + } + + // Called on UI thread. + void ChangeDone() { + DCHECK(CalledOnValidThread()); + DCHECK_GT(pending_requests_, 0); + + if (--pending_requests_ == 0) + worker_thread_.Stop(); + } + + private: + int pending_requests_; + base::Thread worker_thread_; + base::WeakPtrFactory weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(NotifyIconHostStateChangerProxyImpl); +}; + + +NotifyIconHost::NotifyIconHost() + : next_icon_id_(1), + atom_(0), + instance_(NULL), + window_(NULL) { + // Register our window class + WNDCLASSEX window_class; + base::win::InitializeWindowClass( + kNotifyIconHostWindowClass, + &base::win::WrappedWindowProc, + 0, 0, 0, NULL, NULL, NULL, NULL, NULL, + &window_class); + instance_ = window_class.hInstance; + atom_ = RegisterClassEx(&window_class); + CHECK(atom_); + + // If the taskbar is re-created after we start up, we have to rebuild all of + // our icons. + taskbar_created_message_ = RegisterWindowMessage(TEXT("TaskbarCreated")); + + // Create an offscreen window for handling messages for the status icons. We + // create a hidden WS_POPUP window instead of an HWND_MESSAGE window, because + // only top-level windows such as popups can receive broadcast messages like + // "TaskbarCreated". + window_ = CreateWindow(MAKEINTATOM(atom_), + 0, WS_POPUP, 0, 0, 0, 0, 0, 0, instance_, 0); + gfx::CheckWindowCreated(window_); + gfx::SetWindowUserData(window_, this); +} + +NotifyIconHost::~NotifyIconHost() { + if (window_) + DestroyWindow(window_); + + if (atom_) + UnregisterClass(MAKEINTATOM(atom_), instance_); + + NotifyIcons copied_container(notify_icons_); + STLDeleteContainerPointers(copied_container.begin(), copied_container.end()); +} + +NotifyIcon* NotifyIconHost::CreateNotifyIcon() { + NotifyIcon* notify_icon = + new NotifyIcon(this, NextIconId(), window_, kNotifyIconMessage); + notify_icons_.push_back(notify_icon); + return notify_icon; +} + +void NotifyIconHost::Remove(NotifyIcon* icon) { + NotifyIcons::iterator i( + std::find(notify_icons_.begin(), notify_icons_.end(), icon)); + + if (i == notify_icons_.end()) { + NOTREACHED(); + return; + } + + notify_icons_.erase(i); +} + +void NotifyIconHost::UpdateIconVisibilityInBackground( + NotifyIcon* notify_icon) { + if (!state_changer_proxy_.get()) + state_changer_proxy_.reset(new NotifyIconHostStateChangerProxyImpl); + + state_changer_proxy_->EnqueueChange(notify_icon->icon_id(), + notify_icon->window()); +} + +LRESULT CALLBACK NotifyIconHost::WndProcStatic(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam) { + NotifyIconHost* msg_wnd = reinterpret_cast( + GetWindowLongPtr(hwnd, GWLP_USERDATA)); + if (msg_wnd) + return msg_wnd->WndProc(hwnd, message, wparam, lparam); + else + return ::DefWindowProc(hwnd, message, wparam, lparam); +} + +LRESULT CALLBACK NotifyIconHost::WndProc(HWND hwnd, + UINT message, + WPARAM wparam, + LPARAM lparam) { + if (message == taskbar_created_message_) { + // We need to reset all of our icons because the taskbar went away. + for (NotifyIcons::const_iterator i(notify_icons_.begin()); + i != notify_icons_.end(); ++i) { + NotifyIcon* win_icon = static_cast(*i); + win_icon->ResetIcon(); + } + return TRUE; + } else if (message == kNotifyIconMessage) { + NotifyIcon* win_icon = NULL; + + // Find the selected status icon. + for (NotifyIcons::const_iterator i(notify_icons_.begin()); + i != notify_icons_.end(); ++i) { + NotifyIcon* current_win_icon = static_cast(*i); + if (current_win_icon->icon_id() == wparam) { + win_icon = current_win_icon; + break; + } + } + + // It is possible for this procedure to be called with an obsolete icon + // id. In that case we should just return early before handling any + // actions. + if (!win_icon) + return TRUE; + + switch (lparam) { + case WM_LBUTTONDOWN: + case WM_RBUTTONDOWN: + case WM_CONTEXTMENU: + // Walk our icons, find which one was clicked on, and invoke its + // HandleClickEvent() method. + gfx::Point cursor_pos( + gfx::Screen::GetNativeScreen()->GetCursorScreenPoint()); + win_icon->HandleClickEvent(cursor_pos, lparam == WM_LBUTTONDOWN); + return TRUE; + } + } + return ::DefWindowProc(hwnd, message, wparam, lparam); +} + +UINT NotifyIconHost::NextIconId() { + UINT icon_id = next_icon_id_++; + return kBaseIconId + icon_id; +} + +} // namespace atom diff --git a/atom/browser/ui/win/notify_icon_host.h b/atom/browser/ui/win/notify_icon_host.h new file mode 100644 index 000000000000..c4757ffd9716 --- /dev/null +++ b/atom/browser/ui/win/notify_icon_host.h @@ -0,0 +1,79 @@ +// Copyright (c) 2014 GitHub, Inc. All rights reserved. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ATOM_BROWSER_UI_WIN_NOTIFY_ICON_HOST_H_ +#define ATOM_BROWSER_UI_WIN_NOTIFY_ICON_HOST_H_ + +#include + +#include + +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" + +namespace atom { + +class NotifyIcon; + +// A class that's responsible for increasing, if possible, the visibility +// of a status tray icon on the taskbar. The default implementation sends +// a task to a worker thread each time EnqueueChange is called. +class NotifyIconHostStateChangerProxy { + public: + // Called by NotifyIconHost to request upgraded visibility on the icon + // represented by the |icon_id|, |window| pair. + virtual void EnqueueChange(UINT icon_id, HWND window) = 0; +}; + +class NotifyIconHost { + public: + NotifyIconHost(); + ~NotifyIconHost(); + + NotifyIcon* CreateNotifyIcon(); + void Remove(NotifyIcon* notify_icon); + + void UpdateIconVisibilityInBackground(NotifyIcon* notify_icon); + + private: + typedef std::vector NotifyIcons; + + // Static callback invoked when a message comes in to our messaging window. + static LRESULT CALLBACK + WndProcStatic(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam); + + LRESULT CALLBACK + WndProc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam); + + UINT NextIconId(); + + // The unique icon ID we will assign to the next icon. + UINT next_icon_id_; + + // List containing all active NotifyIcons. + NotifyIcons notify_icons_; + + // The window class of |window_|. + ATOM atom_; + + // The handle of the module that contains the window procedure of |window_|. + HMODULE instance_; + + // The window used for processing events. + HWND window_; + + // The message ID of the "TaskbarCreated" message, sent to us when we need to + // reset our status icons. + UINT taskbar_created_message_; + + // Manages changes performed on a background thread to manipulate visibility + // of notification icons. + scoped_ptr state_changer_proxy_; + + DISALLOW_COPY_AND_ASSIGN(NotifyIconHost); +}; + +} // namespace atom + +#endif // ATOM_BROWSER_UI_WIN_NOTIFY_ICON_HOST_H_