Add StatusTrayStateChangerWindow from chrome.
authorCheng Zhao <zcbenz@gmail.com>
Tue, 3 Jun 2014 03:24:40 +0000 (11:24 +0800)
committerCheng Zhao <zcbenz@gmail.com>
Tue, 3 Jun 2014 03:24:40 +0000 (11:24 +0800)
chrome/browser/ui/views/status_icons/status_tray_state_changer_win.cc [new file with mode: 0644]
chrome/browser/ui/views/status_icons/status_tray_state_changer_win.h [new file with mode: 0644]

diff --git a/chrome/browser/ui/views/status_icons/status_tray_state_changer_win.cc b/chrome/browser/ui/views/status_icons/status_tray_state_changer_win.cc
new file mode 100644 (file)
index 0000000..410d31d
--- /dev/null
@@ -0,0 +1,236 @@
+// 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
diff --git a/chrome/browser/ui/views/status_icons/status_tray_state_changer_win.h b/chrome/browser/ui/views/status_icons/status_tray_state_changer_win.h
new file mode 100644 (file)
index 0000000..963792b
--- /dev/null
@@ -0,0 +1,133 @@
+// 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