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