| OLD | NEW |
| (Empty) |
| 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 | |
| 7 namespace { | |
| 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. | |
| 19 class __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. | |
| 31 class __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 | |
| 42 const CLSID CLSID_TrayNotify = { | |
| 43 0x25DEAD04, | |
| 44 0x1EAC, | |
| 45 0x4911, | |
| 46 {0x9E, 0x3A, 0xAD, 0x0A, 0x4A, 0xB5, 0x60, 0xFD}}; | |
| 47 | |
| 48 } // namespace | |
| 49 | |
| 50 StatusTrayStateChangerWin::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 | |
| 60 void 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 | |
| 84 STDMETHODIMP_(ULONG) StatusTrayStateChangerWin::AddRef() { | |
| 85 DCHECK(CalledOnValidThread()); | |
| 86 return base::win::IUnknownImpl::AddRef(); | |
| 87 } | |
| 88 | |
| 89 STDMETHODIMP_(ULONG) StatusTrayStateChangerWin::Release() { | |
| 90 DCHECK(CalledOnValidThread()); | |
| 91 return base::win::IUnknownImpl::Release(); | |
| 92 } | |
| 93 | |
| 94 STDMETHODIMP 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 | |
| 106 STDMETHODIMP 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 | |
| 119 StatusTrayStateChangerWin::~StatusTrayStateChangerWin() { | |
| 120 DCHECK(CalledOnValidThread()); | |
| 121 } | |
| 122 | |
| 123 bool 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 | |
| 150 scoped_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 | |
| 177 bool 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 | |
| 197 bool 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 | |
| 222 void 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 } | |
| OLD | NEW |