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 |