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.
5 #include "chrome/browser/ui/views/status_icons/status_tray_state_changer_win.h"
9 ////////////////////////////////////////////////////////////////////////////////
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.
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
19 class __declspec(uuid("FB852B2C-6BAD-4605-9551-F15F87830935")) ITrayNotify
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;
29 // ITrayNotifyWin8 is the interface that replaces ITrayNotify for newer versions
31 class __declspec(uuid("D133CE13-3537-48BA-93A7-AFCD5D2053B4")) ITrayNotifyWin8
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;
42 const CLSID CLSID_TrayNotify
= {
46 {0x9E, 0x3A, 0xAD, 0x0A, 0x4A, 0xB5, 0x60, 0xFD}};
50 StatusTrayStateChangerWin::StatusTrayStateChangerWin(UINT icon_id
, HWND window
)
51 : interface_version_(INTERFACE_VERSION_UNKNOWN
),
54 wchar_t module_name
[MAX_PATH
];
55 ::GetModuleFileName(NULL
, module_name
, MAX_PATH
);
57 file_name_
= module_name
;
60 void StatusTrayStateChangerWin::EnsureTrayIconVisible() {
61 DCHECK(CalledOnValidThread());
63 if (!CreateTrayNotify()) {
64 VLOG(1) << "Unable to create COM object for ITrayNotify.";
68 scoped_ptr
<NOTIFYITEM
> notify_item
= RegisterCallback();
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
)
75 // If we are already on the taskbar, return since nothing needs to be done.
76 if (notify_item
->preference
== PREFERENCE_SHOW_ALWAYS
)
79 notify_item
->preference
= PREFERENCE_SHOW_ALWAYS
;
81 SendNotifyItemUpdate(notify_item
.Pass());
84 STDMETHODIMP_(ULONG
) StatusTrayStateChangerWin::AddRef() {
85 DCHECK(CalledOnValidThread());
86 return base::win::IUnknownImpl::AddRef();
89 STDMETHODIMP_(ULONG
) StatusTrayStateChangerWin::Release() {
90 DCHECK(CalledOnValidThread());
91 return base::win::IUnknownImpl::Release();
94 STDMETHODIMP
StatusTrayStateChangerWin::QueryInterface(REFIID riid
,
96 DCHECK(CalledOnValidThread());
97 if (riid
== __uuidof(INotificationCB
)) {
98 *ptr_void
= static_cast<INotificationCB
*>(this);
103 return base::win::IUnknownImpl::QueryInterface(riid
, ptr_void
);
106 STDMETHODIMP
StatusTrayStateChangerWin::Notify(ULONG event
,
107 NOTIFYITEM
* notify_item
) {
108 DCHECK(CalledOnValidThread());
110 if (notify_item
->hwnd
!= window_
|| notify_item
->id
!= icon_id_
||
111 base::string16(notify_item
->exe_name
) != file_name_
) {
115 notify_item_
.reset(new NOTIFYITEM(*notify_item
));
119 StatusTrayStateChangerWin::~StatusTrayStateChangerWin() {
120 DCHECK(CalledOnValidThread());
123 bool StatusTrayStateChangerWin::CreateTrayNotify() {
124 DCHECK(CalledOnValidThread());
126 tray_notify_
.Release(); // Release so this method can be called more than
129 HRESULT hr
= tray_notify_
.CreateInstance(CLSID_TrayNotify
);
133 base::win::ScopedComPtr
<ITrayNotifyWin8
> tray_notify_win8
;
134 hr
= tray_notify_win8
.QueryFrom(tray_notify_
.get());
136 interface_version_
= INTERFACE_VERSION_WIN8
;
140 base::win::ScopedComPtr
<ITrayNotify
> tray_notify_legacy
;
141 hr
= tray_notify_legacy
.QueryFrom(tray_notify_
.get());
143 interface_version_
= INTERFACE_VERSION_LEGACY
;
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
);
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.";
163 case INTERFACE_VERSION_LEGACY
:
164 if (!RegisterCallbackLegacy())
165 VLOG(1) << "Unable to successfully run RegisterCallbackLegacy.";
171 // Adding an intermediate scoped pointer here so that |notify_item_| is reset
173 scoped_ptr
<NOTIFYITEM
> rv(notify_item_
.release());
177 bool StatusTrayStateChangerWin::RegisterCallbackWin8() {
178 base::win::ScopedComPtr
<ITrayNotifyWin8
> tray_notify_win8
;
179 HRESULT hr
= tray_notify_win8
.QueryFrom(tray_notify_
.get());
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
);
197 bool StatusTrayStateChangerWin::RegisterCallbackLegacy() {
198 base::win::ScopedComPtr
<ITrayNotify
> tray_notify
;
199 HRESULT hr
= tray_notify
.QueryFrom(tray_notify_
.get());
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
);
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_
.get());
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_
.get());
233 tray_notify
->SetPreference(notify_item
.get());