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 |