--- /dev/null
+// Copyright 2014 The Chromium Authors. All rights reserved.\r
+// Use of this source code is governed by a BSD-style license that can be\r
+// found in the LICENSE file.\r
+\r
+#include "chrome/browser/ui/views/status_icons/status_tray_state_changer_win.h"\r
+\r
+namespace {\r
+\r
+////////////////////////////////////////////////////////////////////////////////\r
+// Status Tray API\r
+\r
+// The folowing describes the interface to the undocumented Windows Exporer APIs\r
+// for manipulating with the status tray area. This code should be used with\r
+// care as it can change with versions (even minor versions) of Windows.\r
+\r
+// ITrayNotify is an interface describing the API for manipulating the state of\r
+// the Windows notification area, as well as for registering for change\r
+// notifications.\r
+class __declspec(uuid("FB852B2C-6BAD-4605-9551-F15F87830935")) ITrayNotify\r
+ : public IUnknown {\r
+ public:\r
+ virtual HRESULT STDMETHODCALLTYPE\r
+ RegisterCallback(INotificationCB* callback) = 0;\r
+ virtual HRESULT STDMETHODCALLTYPE\r
+ SetPreference(const NOTIFYITEM* notify_item) = 0;\r
+ virtual HRESULT STDMETHODCALLTYPE EnableAutoTray(BOOL enabled) = 0;\r
+};\r
+\r
+// ITrayNotifyWin8 is the interface that replaces ITrayNotify for newer versions\r
+// of Windows.\r
+class __declspec(uuid("D133CE13-3537-48BA-93A7-AFCD5D2053B4")) ITrayNotifyWin8\r
+ : public IUnknown {\r
+ public:\r
+ virtual HRESULT STDMETHODCALLTYPE\r
+ RegisterCallback(INotificationCB* callback, unsigned long*) = 0;\r
+ virtual HRESULT STDMETHODCALLTYPE UnregisterCallback(unsigned long*) = 0;\r
+ virtual HRESULT STDMETHODCALLTYPE SetPreference(NOTIFYITEM const*) = 0;\r
+ virtual HRESULT STDMETHODCALLTYPE EnableAutoTray(BOOL) = 0;\r
+ virtual HRESULT STDMETHODCALLTYPE DoAction(BOOL) = 0;\r
+};\r
+\r
+const CLSID CLSID_TrayNotify = {\r
+ 0x25DEAD04,\r
+ 0x1EAC,\r
+ 0x4911,\r
+ {0x9E, 0x3A, 0xAD, 0x0A, 0x4A, 0xB5, 0x60, 0xFD}};\r
+\r
+} // namespace\r
+\r
+StatusTrayStateChangerWin::StatusTrayStateChangerWin(UINT icon_id, HWND window)\r
+ : interface_version_(INTERFACE_VERSION_UNKNOWN),\r
+ icon_id_(icon_id),\r
+ window_(window) {\r
+ wchar_t module_name[MAX_PATH];\r
+ ::GetModuleFileName(NULL, module_name, MAX_PATH);\r
+\r
+ file_name_ = module_name;\r
+}\r
+\r
+void StatusTrayStateChangerWin::EnsureTrayIconVisible() {\r
+ DCHECK(CalledOnValidThread());\r
+\r
+ if (!CreateTrayNotify()) {\r
+ VLOG(1) << "Unable to create COM object for ITrayNotify.";\r
+ return;\r
+ }\r
+\r
+ scoped_ptr<NOTIFYITEM> notify_item = RegisterCallback();\r
+\r
+ // If the user has already hidden us explicitly, try to honor their choice by\r
+ // not changing anything.\r
+ if (notify_item->preference == PREFERENCE_SHOW_NEVER)\r
+ return;\r
+\r
+ // If we are already on the taskbar, return since nothing needs to be done.\r
+ if (notify_item->preference == PREFERENCE_SHOW_ALWAYS)\r
+ return;\r
+\r
+ notify_item->preference = PREFERENCE_SHOW_ALWAYS;\r
+\r
+ SendNotifyItemUpdate(notify_item.Pass());\r
+}\r
+\r
+STDMETHODIMP_(ULONG) StatusTrayStateChangerWin::AddRef() {\r
+ DCHECK(CalledOnValidThread());\r
+ return base::win::IUnknownImpl::AddRef();\r
+}\r
+\r
+STDMETHODIMP_(ULONG) StatusTrayStateChangerWin::Release() {\r
+ DCHECK(CalledOnValidThread());\r
+ return base::win::IUnknownImpl::Release();\r
+}\r
+\r
+STDMETHODIMP StatusTrayStateChangerWin::QueryInterface(REFIID riid,\r
+ PVOID* ptr_void) {\r
+ DCHECK(CalledOnValidThread());\r
+ if (riid == __uuidof(INotificationCB)) {\r
+ *ptr_void = static_cast<INotificationCB*>(this);\r
+ AddRef();\r
+ return S_OK;\r
+ }\r
+\r
+ return base::win::IUnknownImpl::QueryInterface(riid, ptr_void);\r
+}\r
+\r
+STDMETHODIMP StatusTrayStateChangerWin::Notify(ULONG event,\r
+ NOTIFYITEM* notify_item) {\r
+ DCHECK(CalledOnValidThread());\r
+ DCHECK(notify_item);\r
+ if (notify_item->hwnd != window_ || notify_item->id != icon_id_ ||\r
+ base::string16(notify_item->exe_name) != file_name_) {\r
+ return S_OK;\r
+ }\r
+\r
+ notify_item_.reset(new NOTIFYITEM(*notify_item));\r
+ return S_OK;\r
+}\r
+\r
+StatusTrayStateChangerWin::~StatusTrayStateChangerWin() {\r
+ DCHECK(CalledOnValidThread());\r
+}\r
+\r
+bool StatusTrayStateChangerWin::CreateTrayNotify() {\r
+ DCHECK(CalledOnValidThread());\r
+\r
+ tray_notify_.Release(); // Release so this method can be called more than\r
+ // once.\r
+\r
+ HRESULT hr = tray_notify_.CreateInstance(CLSID_TrayNotify);\r
+ if (FAILED(hr))\r
+ return false;\r
+\r
+ base::win::ScopedComPtr<ITrayNotifyWin8> tray_notify_win8;\r
+ hr = tray_notify_win8.QueryFrom(tray_notify_);\r
+ if (SUCCEEDED(hr)) {\r
+ interface_version_ = INTERFACE_VERSION_WIN8;\r
+ return true;\r
+ }\r
+\r
+ base::win::ScopedComPtr<ITrayNotify> tray_notify_legacy;\r
+ hr = tray_notify_legacy.QueryFrom(tray_notify_);\r
+ if (SUCCEEDED(hr)) {\r
+ interface_version_ = INTERFACE_VERSION_LEGACY;\r
+ return true;\r
+ }\r
+\r
+ return false;\r
+}\r
+\r
+scoped_ptr<NOTIFYITEM> StatusTrayStateChangerWin::RegisterCallback() {\r
+ // |notify_item_| is used to store the result of the callback from\r
+ // Explorer.exe, which happens synchronously during\r
+ // RegisterCallbackWin8 or RegisterCallbackLegacy.\r
+ DCHECK(notify_item_.get() == NULL);\r
+\r
+ // TODO(dewittj): Add UMA logging here to report if either of our strategies\r
+ // has a tendency to fail on particular versions of Windows.\r
+ switch (interface_version_) {\r
+ case INTERFACE_VERSION_WIN8:\r
+ if (!RegisterCallbackWin8())\r
+ VLOG(1) << "Unable to successfully run RegisterCallbackWin8.";\r
+ break;\r
+ case INTERFACE_VERSION_LEGACY:\r
+ if (!RegisterCallbackLegacy())\r
+ VLOG(1) << "Unable to successfully run RegisterCallbackLegacy.";\r
+ break;\r
+ default:\r
+ NOTREACHED();\r
+ }\r
+\r
+ // Adding an intermediate scoped pointer here so that |notify_item_| is reset\r
+ // to NULL.\r
+ scoped_ptr<NOTIFYITEM> rv(notify_item_.release());\r
+ return rv.Pass();\r
+}\r
+\r
+bool StatusTrayStateChangerWin::RegisterCallbackWin8() {\r
+ base::win::ScopedComPtr<ITrayNotifyWin8> tray_notify_win8;\r
+ HRESULT hr = tray_notify_win8.QueryFrom(tray_notify_);\r
+ if (FAILED(hr))\r
+ return false;\r
+\r
+ // The following two lines cause Windows Explorer to call us back with all the\r
+ // existing tray icons and their preference. It would also presumably notify\r
+ // us if changes were made in realtime while we registered as a callback, but\r
+ // we just want to modify our own entry so we immediately unregister.\r
+ unsigned long callback_id = 0;\r
+ hr = tray_notify_win8->RegisterCallback(this, &callback_id);\r
+ tray_notify_win8->UnregisterCallback(&callback_id);\r
+ if (FAILED(hr)) {\r
+ return false;\r
+ }\r
+\r
+ return true;\r
+}\r
+\r
+bool StatusTrayStateChangerWin::RegisterCallbackLegacy() {\r
+ base::win::ScopedComPtr<ITrayNotify> tray_notify;\r
+ HRESULT hr = tray_notify.QueryFrom(tray_notify_);\r
+ if (FAILED(hr)) {\r
+ return false;\r
+ }\r
+\r
+ // The following two lines cause Windows Explorer to call us back with all the\r
+ // existing tray icons and their preference. It would also presumably notify\r
+ // us if changes were made in realtime while we registered as a callback. In\r
+ // this version of the API, there can be only one registered callback so it is\r
+ // better to unregister as soon as possible.\r
+ // TODO(dewittj): Try to notice if the notification area icon customization\r
+ // window is open and postpone this call until the user closes it;\r
+ // registering the callback while the window is open can cause stale data to\r
+ // be displayed to the user.\r
+ hr = tray_notify->RegisterCallback(this);\r
+ tray_notify->RegisterCallback(NULL);\r
+ if (FAILED(hr)) {\r
+ return false;\r
+ }\r
+\r
+ return true;\r
+}\r
+\r
+void StatusTrayStateChangerWin::SendNotifyItemUpdate(\r
+ scoped_ptr<NOTIFYITEM> notify_item) {\r
+ if (interface_version_ == INTERFACE_VERSION_LEGACY) {\r
+ base::win::ScopedComPtr<ITrayNotify> tray_notify;\r
+ HRESULT hr = tray_notify.QueryFrom(tray_notify_);\r
+ if (SUCCEEDED(hr))\r
+ tray_notify->SetPreference(notify_item.get());\r
+ } else if (interface_version_ == INTERFACE_VERSION_WIN8) {\r
+ base::win::ScopedComPtr<ITrayNotifyWin8> tray_notify;\r
+ HRESULT hr = tray_notify.QueryFrom(tray_notify_);\r
+ if (SUCCEEDED(hr))\r
+ tray_notify->SetPreference(notify_item.get());\r
+ }\r
+}\r
+\r
--- /dev/null
+// Copyright 2014 The Chromium Authors. All rights reserved.\r
+// Use of this source code is governed by a BSD-style license that can be\r
+// found in the LICENSE file.\r
+\r
+#ifndef CHROME_BROWSER_UI_VIEWS_STATUS_ICONS_STATUS_TRAY_STATE_CHANGER_WIN_H_\r
+#define CHROME_BROWSER_UI_VIEWS_STATUS_ICONS_STATUS_TRAY_STATE_CHANGER_WIN_H_\r
+\r
+#include "base/memory/scoped_ptr.h"\r
+#include "base/strings/string16.h"\r
+#include "base/threading/non_thread_safe.h"\r
+#include "base/win/iunknown_impl.h"\r
+#include "base/win/scoped_comptr.h"\r
+\r
+// The known values for NOTIFYITEM's dwPreference member.\r
+enum NOTIFYITEM_PREFERENCE {\r
+ // In Windows UI: "Only show notifications."\r
+ PREFERENCE_SHOW_WHEN_ACTIVE = 0,\r
+ // In Windows UI: "Hide icon and notifications."\r
+ PREFERENCE_SHOW_NEVER = 1,\r
+ // In Windows UI: "Show icon and notifications."\r
+ PREFERENCE_SHOW_ALWAYS = 2\r
+};\r
+\r
+// NOTIFYITEM describes an entry in Explorer's registry of status icons.\r
+// Explorer keeps entries around for a process even after it exits.\r
+struct NOTIFYITEM {\r
+ PWSTR exe_name; // The file name of the creating executable.\r
+ PWSTR tip; // The last hover-text value associated with this status\r
+ // item.\r
+ HICON icon; // The icon associated with this status item.\r
+ HWND hwnd; // The HWND associated with the status item.\r
+ DWORD preference; // Determines the behavior of the icon with respect to\r
+ // the taskbar. Values taken from NOTIFYITEM_PREFERENCE.\r
+ UINT id; // The ID specified by the application. (hWnd, uID) is\r
+ // unique.\r
+ GUID guid; // The GUID specified by the application, alternative to\r
+ // uID.\r
+};\r
+\r
+// INotificationCB is an interface that applications can implement in order to\r
+// receive notifications about the state of the notification area manager.\r
+class __declspec(uuid("D782CCBA-AFB0-43F1-94DB-FDA3779EACCB")) INotificationCB\r
+ : public IUnknown {\r
+ public:\r
+ virtual HRESULT STDMETHODCALLTYPE\r
+ Notify(ULONG event, NOTIFYITEM* notify_item) = 0;\r
+};\r
+\r
+// A class that is capable of reading and writing the state of the notification\r
+// area in the Windows taskbar. It is used to promote a tray icon from the\r
+// overflow area to the taskbar, and refuses to do anything if the user has\r
+// explicitly marked an icon to be always hidden.\r
+class StatusTrayStateChangerWin : public INotificationCB,\r
+ public base::win::IUnknownImpl,\r
+ public base::NonThreadSafe {\r
+ public:\r
+ StatusTrayStateChangerWin(UINT icon_id, HWND window);\r
+\r
+ // Call this method to move the icon matching |icon_id| and |window| to the\r
+ // taskbar from the overflow area. This will not make any changes if the\r
+ // icon has been set to |PREFERENCE_SHOW_NEVER|, in order to comply with\r
+ // the explicit wishes/configuration of the user.\r
+ void EnsureTrayIconVisible();\r
+\r
+ // IUnknown.\r
+ virtual ULONG STDMETHODCALLTYPE AddRef() OVERRIDE;\r
+ virtual ULONG STDMETHODCALLTYPE Release() OVERRIDE;\r
+ virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, PVOID*) OVERRIDE;\r
+\r
+ // INotificationCB.\r
+ // Notify is called in response to RegisterCallback for each current\r
+ // entry in Explorer's list of notification area icons, and ever time\r
+ // one of them changes, until UnregisterCallback is called or |this|\r
+ // is destroyed.\r
+ virtual HRESULT STDMETHODCALLTYPE Notify(ULONG, NOTIFYITEM*);\r
+\r
+ protected:\r
+ virtual ~StatusTrayStateChangerWin();\r
+\r
+ private:\r
+ friend class StatusTrayStateChangerWinTest;\r
+\r
+ enum InterfaceVersion {\r
+ INTERFACE_VERSION_LEGACY = 0,\r
+ INTERFACE_VERSION_WIN8,\r
+ INTERFACE_VERSION_UNKNOWN\r
+ };\r
+\r
+ // Creates an instance of TrayNotify, and ensures that it supports either\r
+ // ITrayNotify or ITrayNotifyWin8. Returns true on success.\r
+ bool CreateTrayNotify();\r
+\r
+ // Returns the NOTIFYITEM that corresponds to this executable and the\r
+ // HWND/ID pair that were used to create the StatusTrayStateChangerWin.\r
+ // Internally it calls the appropriate RegisterCallback{Win8,Legacy}.\r
+ scoped_ptr<NOTIFYITEM> RegisterCallback();\r
+\r
+ // Calls RegisterCallback with the appropriate interface required by\r
+ // different versions of Windows. This will result in |notify_item_| being\r
+ // updated when a matching item is passed into\r
+ // StatusTrayStateChangerWin::Notify.\r
+ bool RegisterCallbackWin8();\r
+ bool RegisterCallbackLegacy();\r
+\r
+ // Sends an update to Explorer with the passed NOTIFYITEM.\r
+ void SendNotifyItemUpdate(scoped_ptr<NOTIFYITEM> notify_item);\r
+\r
+ // Storing IUnknown since we will need to use different interfaces\r
+ // for different versions of Windows.\r
+ base::win::ScopedComPtr<IUnknown> tray_notify_;\r
+ InterfaceVersion interface_version_;\r
+\r
+ // The ID assigned to the notification area icon that we want to manipulate.\r
+ const UINT icon_id_;\r
+ // The HWND associated with the notification area icon that we want to\r
+ // manipulate. This is an unretained pointer, do not dereference.\r
+ const HWND window_;\r
+ // Executable name of the current program. Along with |icon_id_| and\r
+ // |window_|, this uniquely identifies a notification area entry to Explorer.\r
+ base::string16 file_name_;\r
+\r
+ // Temporary storage for the matched NOTIFYITEM. This is necessary because\r
+ // Notify doesn't return anything. The call flow looks like this:\r
+ // TrayNotify->RegisterCallback()\r
+ // ... other COM stack frames ..\r
+ // StatusTrayStateChangerWin->Notify(NOTIFYITEM);\r
+ // so we can't just return the notifyitem we're looking for.\r
+ scoped_ptr<NOTIFYITEM> notify_item_;\r
+\r
+ DISALLOW_COPY_AND_ASSIGN(StatusTrayStateChangerWin);\r
+};\r
+\r
+#endif // CHROME_BROWSER_UI_VIEWS_STATUS_ICONS_STATUS_TRAY_STATE_CHANGER_WIN_H_
\ No newline at end of file