status_tray_state_changer_win.cc revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
1// Copyright 2014 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "chrome/browser/ui/views/status_icons/status_tray_state_changer_win.h"
6
7namespace {
8
9////////////////////////////////////////////////////////////////////////////////
10// Status Tray API
11
12// The folowing describes the interface to the undocumented Windows Exporer APIs
13// for manipulating with the status tray area.  This code should be used with
14// care as it can change with versions (even minor versions) of Windows.
15
16// ITrayNotify is an interface describing the API for manipulating the state of
17// the Windows notification area, as well as for registering for change
18// notifications.
19class __declspec(uuid("FB852B2C-6BAD-4605-9551-F15F87830935")) ITrayNotify
20    : public IUnknown {
21 public:
22  virtual HRESULT STDMETHODCALLTYPE
23      RegisterCallback(INotificationCB* callback) = 0;
24  virtual HRESULT STDMETHODCALLTYPE
25      SetPreference(const NOTIFYITEM* notify_item) = 0;
26  virtual HRESULT STDMETHODCALLTYPE EnableAutoTray(BOOL enabled) = 0;
27};
28
29// ITrayNotifyWin8 is the interface that replaces ITrayNotify for newer versions
30// of Windows.
31class __declspec(uuid("D133CE13-3537-48BA-93A7-AFCD5D2053B4")) ITrayNotifyWin8
32    : public IUnknown {
33 public:
34  virtual HRESULT STDMETHODCALLTYPE
35      RegisterCallback(INotificationCB* callback, unsigned long*) = 0;
36  virtual HRESULT STDMETHODCALLTYPE UnregisterCallback(unsigned long*) = 0;
37  virtual HRESULT STDMETHODCALLTYPE SetPreference(NOTIFYITEM const*) = 0;
38  virtual HRESULT STDMETHODCALLTYPE EnableAutoTray(BOOL) = 0;
39  virtual HRESULT STDMETHODCALLTYPE DoAction(BOOL) = 0;
40};
41
42const CLSID CLSID_TrayNotify = {
43    0x25DEAD04,
44    0x1EAC,
45    0x4911,
46    {0x9E, 0x3A, 0xAD, 0x0A, 0x4A, 0xB5, 0x60, 0xFD}};
47
48}  // namespace
49
50StatusTrayStateChangerWin::StatusTrayStateChangerWin(UINT icon_id, HWND window)
51    : interface_version_(INTERFACE_VERSION_UNKNOWN),
52      icon_id_(icon_id),
53      window_(window) {
54  wchar_t module_name[MAX_PATH];
55  ::GetModuleFileName(NULL, module_name, MAX_PATH);
56
57  file_name_ = module_name;
58}
59
60void StatusTrayStateChangerWin::EnsureTrayIconVisible() {
61  DCHECK(CalledOnValidThread());
62
63  if (!CreateTrayNotify()) {
64    VLOG(1) << "Unable to create COM object for ITrayNotify.";
65    return;
66  }
67
68  scoped_ptr<NOTIFYITEM> notify_item = RegisterCallback();
69
70  // If the user has already hidden us explicitly, try to honor their choice by
71  // not changing anything.
72  if (notify_item->preference == PREFERENCE_SHOW_NEVER)
73    return;
74
75  // If we are already on the taskbar, return since nothing needs to be done.
76  if (notify_item->preference == PREFERENCE_SHOW_ALWAYS)
77    return;
78
79  notify_item->preference = PREFERENCE_SHOW_ALWAYS;
80
81  SendNotifyItemUpdate(notify_item.Pass());
82}
83
84STDMETHODIMP_(ULONG) StatusTrayStateChangerWin::AddRef() {
85  DCHECK(CalledOnValidThread());
86  return base::win::IUnknownImpl::AddRef();
87}
88
89STDMETHODIMP_(ULONG) StatusTrayStateChangerWin::Release() {
90  DCHECK(CalledOnValidThread());
91  return base::win::IUnknownImpl::Release();
92}
93
94STDMETHODIMP StatusTrayStateChangerWin::QueryInterface(REFIID riid,
95                                                       PVOID* ptr_void) {
96  DCHECK(CalledOnValidThread());
97  if (riid == __uuidof(INotificationCB)) {
98    *ptr_void = static_cast<INotificationCB*>(this);
99    AddRef();
100    return S_OK;
101  }
102
103  return base::win::IUnknownImpl::QueryInterface(riid, ptr_void);
104}
105
106STDMETHODIMP StatusTrayStateChangerWin::Notify(ULONG event,
107                                               NOTIFYITEM* notify_item) {
108  DCHECK(CalledOnValidThread());
109  DCHECK(notify_item);
110  if (notify_item->hwnd != window_ || notify_item->id != icon_id_ ||
111      base::string16(notify_item->exe_name) != file_name_) {
112    return S_OK;
113  }
114
115  notify_item_.reset(new NOTIFYITEM(*notify_item));
116  return S_OK;
117}
118
119StatusTrayStateChangerWin::~StatusTrayStateChangerWin() {
120  DCHECK(CalledOnValidThread());
121}
122
123bool StatusTrayStateChangerWin::CreateTrayNotify() {
124  DCHECK(CalledOnValidThread());
125
126  tray_notify_.Release();  // Release so this method can be called more than
127                           // once.
128
129  HRESULT hr = tray_notify_.CreateInstance(CLSID_TrayNotify);
130  if (FAILED(hr))
131    return false;
132
133  base::win::ScopedComPtr<ITrayNotifyWin8> tray_notify_win8;
134  hr = tray_notify_win8.QueryFrom(tray_notify_);
135  if (SUCCEEDED(hr)) {
136    interface_version_ = INTERFACE_VERSION_WIN8;
137    return true;
138  }
139
140  base::win::ScopedComPtr<ITrayNotify> tray_notify_legacy;
141  hr = tray_notify_legacy.QueryFrom(tray_notify_);
142  if (SUCCEEDED(hr)) {
143    interface_version_ = INTERFACE_VERSION_LEGACY;
144    return true;
145  }
146
147  return false;
148}
149
150scoped_ptr<NOTIFYITEM> StatusTrayStateChangerWin::RegisterCallback() {
151  // |notify_item_| is used to store the result of the callback from
152  // Explorer.exe, which happens synchronously during
153  // RegisterCallbackWin8 or RegisterCallbackLegacy.
154  DCHECK(notify_item_.get() == NULL);
155
156  // TODO(dewittj): Add UMA logging here to report if either of our strategies
157  // has a tendency to fail on particular versions of Windows.
158  switch (interface_version_) {
159    case INTERFACE_VERSION_WIN8:
160      if (!RegisterCallbackWin8())
161        VLOG(1) << "Unable to successfully run RegisterCallbackWin8.";
162      break;
163    case INTERFACE_VERSION_LEGACY:
164      if (!RegisterCallbackLegacy())
165        VLOG(1) << "Unable to successfully run RegisterCallbackLegacy.";
166      break;
167    default:
168      NOTREACHED();
169  }
170
171  // Adding an intermediate scoped pointer here so that |notify_item_| is reset
172  // to NULL.
173  scoped_ptr<NOTIFYITEM> rv(notify_item_.release());
174  return rv.Pass();
175}
176
177bool StatusTrayStateChangerWin::RegisterCallbackWin8() {
178  base::win::ScopedComPtr<ITrayNotifyWin8> tray_notify_win8;
179  HRESULT hr = tray_notify_win8.QueryFrom(tray_notify_);
180  if (FAILED(hr))
181    return false;
182
183  // The following two lines cause Windows Explorer to call us back with all the
184  // existing tray icons and their preference.  It would also presumably notify
185  // us if changes were made in realtime while we registered as a callback, but
186  // we just want to modify our own entry so we immediately unregister.
187  unsigned long callback_id = 0;
188  hr = tray_notify_win8->RegisterCallback(this, &callback_id);
189  tray_notify_win8->UnregisterCallback(&callback_id);
190  if (FAILED(hr)) {
191    return false;
192  }
193
194  return true;
195}
196
197bool StatusTrayStateChangerWin::RegisterCallbackLegacy() {
198  base::win::ScopedComPtr<ITrayNotify> tray_notify;
199  HRESULT hr = tray_notify.QueryFrom(tray_notify_);
200  if (FAILED(hr)) {
201    return false;
202  }
203
204  // The following two lines cause Windows Explorer to call us back with all the
205  // existing tray icons and their preference.  It would also presumably notify
206  // us if changes were made in realtime while we registered as a callback.  In
207  // this version of the API, there can be only one registered callback so it is
208  // better to unregister as soon as possible.
209  // TODO(dewittj): Try to notice if the notification area icon customization
210  // window is open and postpone this call until the user closes it;
211  // registering the callback while the window is open can cause stale data to
212  // be displayed to the user.
213  hr = tray_notify->RegisterCallback(this);
214  tray_notify->RegisterCallback(NULL);
215  if (FAILED(hr)) {
216    return false;
217  }
218
219  return true;
220}
221
222void StatusTrayStateChangerWin::SendNotifyItemUpdate(
223    scoped_ptr<NOTIFYITEM> notify_item) {
224  if (interface_version_ == INTERFACE_VERSION_LEGACY) {
225    base::win::ScopedComPtr<ITrayNotify> tray_notify;
226    HRESULT hr = tray_notify.QueryFrom(tray_notify_);
227    if (SUCCEEDED(hr))
228      tray_notify->SetPreference(notify_item.get());
229  } else if (interface_version_ == INTERFACE_VERSION_WIN8) {
230    base::win::ScopedComPtr<ITrayNotifyWin8> tray_notify;
231    HRESULT hr = tray_notify.QueryFrom(tray_notify_);
232    if (SUCCEEDED(hr))
233      tray_notify->SetPreference(notify_item.get());
234  }
235}
236