Chromium Code Reviews| Index: chrome/browser/ui/views/message_center/tray_watcher_win.cc |
| diff --git a/chrome/browser/ui/views/message_center/tray_watcher_win.cc b/chrome/browser/ui/views/message_center/tray_watcher_win.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..3da349732f8b208225f71230f3ed2c87c975cfb5 |
| --- /dev/null |
| +++ b/chrome/browser/ui/views/message_center/tray_watcher_win.cc |
| @@ -0,0 +1,209 @@ |
| +// Copyright 2014 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 file. |
| + |
| +#include "chrome/browser/ui/views/message_center/tray_watcher_win.h" |
| + |
| +namespace message_center { |
| +namespace { |
| + |
| +//////////////////////////////////////////////////////////////////////////////// |
| +// Status Tray API |
| + |
| +// The folowing describes the interface to the undocumented Windows Exporer APIs |
| +// for manipulating with the status tray area. This code should be used with |
| +// care as it can change with versions (even minor versions) of Windows. |
| + |
| +// ItrayNotify GUID |
| +[uuid("FB852B2C-6BAD-4605-9551-F15F87830935")] interface ITrayNotify |
| + : public IUnknown { |
| + STDMETHOD(RegisterCallback)(INotificationCB* callback) = 0; |
| + STDMETHOD(SetPreference)(const NOTIFYITEM* notify_item) = 0; |
| + STDMETHOD(EnableAutoTray)(BOOL enabled) = 0; |
| +}; |
| + |
| +[uuid("D133CE13-3537-48BA-93A7-AFCD5D2053B4")] interface ITrayNotifyWin8 |
| + : public IUnknown { |
| + STDMETHOD(RegisterCallback)(INotificationCB * callback, unsigned long*) = 0; |
| + STDMETHOD(UnregisterCallback)(unsigned long*) = 0; |
| + STDMETHOD(SetPreference)(NOTIFYITEM const*) = 0; |
| + STDMETHOD(EnableAutoTray)(BOOL) = 0; |
| + STDMETHOD(DoAction)(BOOL) = 0; |
| +}; |
| + |
| +const CLSID CLSID_TrayNotify = { |
| + 0x25DEAD04, |
| + 0x1EAC, |
| + 0x4911, |
| + {0x9E, 0x3A, 0xAD, 0x0A, 0x4A, 0xB5, 0x60, 0xFD}}; |
| + |
| +} // namespace |
| + |
| +TrayWatcherWin::TrayWatcherWin(UINT icon_id, HWND window) |
| + : icon_id_(icon_id), |
| + window_(window), |
| + interface_version_(INTERFACE_VERSION_UNKNOWN) { |
| + wchar_t module_name[MAX_PATH]; |
| + ::GetModuleFileName(NULL, module_name, MAX_PATH); |
| + |
| + file_name_ = module_name; |
| +} |
| + |
| +bool TrayWatcherWin::CreateTrayNotify() { |
| + DCHECK(CalledOnValidThread()); |
| + HRESULT hr = tray_notify_.CreateInstance(CLSID_TrayNotify); |
| + if (FAILED(hr)) |
| + return false; |
| + |
| + base::win::ScopedComPtr<ITrayNotifyWin8> tray_notify_win8; |
| + hr = tray_notify_win8.QueryFrom(tray_notify_); |
| + if (SUCCEEDED(hr)) { |
| + interface_version_ = INTERFACE_VERSION_WIN8; |
| + return true; |
| + } |
| + |
| + base::win::ScopedComPtr<ITrayNotify> tray_notify_legacy; |
| + hr = tray_notify_legacy.QueryFrom(tray_notify_); |
| + if (SUCCEEDED(hr)) { |
| + interface_version_ = INTERFACE_VERSION_LEGACY; |
| + return true; |
| + } |
| + |
| + return false; |
| +} |
| + |
| +void TrayWatcherWin::EnsureTrayIconVisible() { |
| + DCHECK(CalledOnValidThread()); |
| + |
| + if (!CreateTrayNotify()) { |
| + VLOG(1) << "Unable to create COM object for ITrayNotify."; |
| + return; |
| + } |
| + |
| + scoped_ptr<NOTIFYITEM> notify_item = RegisterCallback(); |
| + |
| + // If the user has already hidden us explicitly, try to honor their choice by |
| + // not changing anything. |
| + if (notify_item->dwPreference == PREFERENCE_SHOW_NEVER) |
| + return; |
| + |
| + // If we are already on the taskbar, return since nothing needs to be done. |
| + if (notify_item->dwPreference == PREFERENCE_SHOW_ALWAYS) |
| + return; |
| + |
| + notify_item->dwPreference = PREFERENCE_SHOW_ALWAYS; |
| + |
| + SendNotifyItemUpdate(notify_item.Pass()); |
| +} |
| + |
| +scoped_ptr<NOTIFYITEM> TrayWatcherWin::RegisterCallback() { |
| + // |notify_item_| is used to store the result of the callback from |
| + // Explorer.exe |
|
Dmitry Titov
2014/03/18 22:34:13
maybe add "which happens synchronously once callba
dewittj
2014/03/19 18:36:15
Done.
|
| + DCHECK(notify_item_.get() == NULL); |
| + |
| + // TODO(dewittj): Add UMA logging here to report if either of our strategies |
| + // has a tendency to fail on particular versions of Windows. |
| + switch (interface_version_) { |
| + case INTERFACE_VERSION_WIN8: |
| + if (!RegisterCallbackWin8()) |
| + VLOG(1) << "Unable to successfully run RegisterCallbackWin8."; |
| + break; |
| + case INTERFACE_VERSION_LEGACY: |
| + if (!RegisterCallbackLegacy()) |
| + VLOG(1) << "Unable to successfully run RegisterCallbackLegacy."; |
| + break; |
| + default: |
| + NOTREACHED(); |
| + } |
| + |
| + return notify_item_.Pass(); |
| +} |
| + |
| +void TrayWatcherWin::SendNotifyItemUpdate(scoped_ptr<NOTIFYITEM> notify_item) { |
| + if (interface_version_ == INTERFACE_VERSION_LEGACY) { |
| + base::win::ScopedComPtr<ITrayNotify> tray_notify; |
| + HRESULT hr = tray_notify.QueryFrom(tray_notify_); |
| + if (SUCCEEDED(hr)) |
| + tray_notify->SetPreference(notify_item.get()); |
| + } else if (interface_version_ == INTERFACE_VERSION_WIN8) { |
| + base::win::ScopedComPtr<ITrayNotifyWin8> tray_notify; |
| + HRESULT hr = tray_notify.QueryFrom(tray_notify_); |
| + if (SUCCEEDED(hr)) |
| + tray_notify->SetPreference(notify_item.get()); |
| + } |
| +} |
| + |
| +STDMETHODIMP_(ULONG) TrayWatcherWin::AddRef() { |
| + DCHECK(CalledOnValidThread()); |
| + return base::win::IUnknownImpl::AddRef(); |
| +} |
| + |
| +STDMETHODIMP_(ULONG) TrayWatcherWin::Release() { |
| + DCHECK(CalledOnValidThread()); |
| + return base::win::IUnknownImpl::Release(); |
| +} |
| + |
| +STDMETHODIMP TrayWatcherWin::QueryInterface(REFIID riid, PVOID* ptr_void) { |
| + DCHECK(CalledOnValidThread()); |
| + if (riid == __uuidof(INotificationCB)) { |
| + *ptr_void = static_cast<INotificationCB*>(this); |
| + AddRef(); |
| + return S_OK; |
| + } |
| + |
| + return base::win::IUnknownImpl::QueryInterface(riid, ptr_void); |
| +} |
| + |
| +STDMETHODIMP TrayWatcherWin::Notify(ULONG event, NOTIFYITEM* notify_item) { |
| + DCHECK(CalledOnValidThread()); |
| + DCHECK(notify_item); |
| + if (notify_item->hWnd != window_ || notify_item->uID != icon_id_ || |
| + base::string16(notify_item->pszExeName) != file_name_) { |
| + return S_OK; |
| + } |
| + |
| + notify_item_.reset(new NOTIFYITEM(*notify_item)); |
| + return S_OK; |
| +} |
| + |
| +TrayWatcherWin::~TrayWatcherWin() { DCHECK(CalledOnValidThread()); } |
| + |
| +bool TrayWatcherWin::RegisterCallbackWin8() { |
| + base::win::ScopedComPtr<ITrayNotifyWin8> tray_notify_win8; |
| + HRESULT hr = tray_notify_win8.QueryFrom(tray_notify_); |
| + if (FAILED(hr)) |
| + return false; |
| + |
| + // The following two lines cause Windows Explorer to call us back with all the |
| + // existing tray icons and their preference. It would also presumably notify |
| + // us if changes were made in realtime while we registered as a callback, but |
| + // we just want to modify our own entry so we immediately unregister. |
| + unsigned long callback_id = 0; |
| + tray_notify_win8->RegisterCallback(this, &callback_id); |
| + tray_notify_win8->UnregisterCallback(&callback_id); |
| + |
| + return true; |
| +} |
| + |
| +bool TrayWatcherWin::RegisterCallbackLegacy() { |
| + base::win::ScopedComPtr<ITrayNotify> tray_notify; |
| + HRESULT hr = tray_notify.QueryFrom(tray_notify_); |
| + if (FAILED(hr)) |
| + return false; |
| + |
| + // The following two lines cause Windows Explorer to call us back with all the |
| + // existing tray icons and their preference. It would also presumably notify |
| + // us if changes were made in realtime while we registered as a callback. In |
| + // this version of the API, there can be only one registered callback so it is |
| + // better to unregister as soon as possible. |
| + // TODO(dewittj): Try to notice if the notification area icon customization |
| + // window is open and postpone this call until the user closes it; |
| + // registering the callback while the window is open can cause stale data to |
| + // be displayed to the user. |
| + tray_notify->RegisterCallback(this); |
| + tray_notify->RegisterCallback(NULL); |
| + |
| + return true; |
| +} |
| + |
| +} // namespace message_center |