1 // Copyright (c) 2012 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_win.h"
10 #include "base/profiler/scoped_tracker.h"
11 #include "base/threading/non_thread_safe.h"
12 #include "base/threading/thread.h"
13 #include "base/win/wrapped_window_proc.h"
14 #include "chrome/browser/lifetime/application_lifetime.h"
15 #include "chrome/browser/ui/views/status_icons/status_icon_win.h"
16 #include "chrome/browser/ui/views/status_icons/status_tray_state_changer_win.h"
17 #include "chrome/common/chrome_constants.h"
18 #include "components/browser_watcher/exit_funnel_win.h"
19 #include "ui/gfx/screen.h"
20 #include "ui/gfx/win/hwnd_util.h"
22 static const UINT kStatusIconMessage
= WM_APP
+ 1;
25 // |kBaseIconId| is 2 to avoid conflicts with plugins that hard-code id 1.
26 const UINT kBaseIconId
= 2;
28 UINT
ReservedIconId(StatusTray::StatusIconType type
) {
29 return kBaseIconId
+ static_cast<UINT
>(type
);
32 // See http://crbug.com/412384.
33 void TraceSessionEnding(LPARAM lparam
) {
34 browser_watcher::ExitFunnel funnel
;
35 if (!funnel
.Init(chrome::kBrowserExitCodesRegistryPath
,
36 base::GetCurrentProcessHandle())) {
40 // This exit path is the prime suspect for most our unclean shutdowns.
41 // Trace all the possible options to WM_ENDSESSION. This may result in
42 // multiple events for a single shutdown, but that's fine.
43 funnel
.RecordEvent(L
"TraybarEndSession");
45 if (lparam
& ENDSESSION_CLOSEAPP
)
46 funnel
.RecordEvent(L
"ES_CloseApp");
47 if (lparam
& ENDSESSION_CRITICAL
)
48 funnel
.RecordEvent(L
"ES_Critical");
49 if (lparam
& ENDSESSION_LOGOFF
)
50 funnel
.RecordEvent(L
"ES_Logoff");
51 const LPARAM kKnownBits
=
52 ENDSESSION_CLOSEAPP
| ENDSESSION_CRITICAL
| ENDSESSION_LOGOFF
;
53 if (lparam
& ~kKnownBits
)
54 funnel
.RecordEvent(L
"ES_Other");
59 // Default implementation for StatusTrayStateChanger that communicates to
60 // Exporer.exe via COM. It spawns a background thread with a fresh COM
61 // apartment and requests that the visibility be increased unless the user
62 // has explicitly set the icon to be hidden.
63 class StatusTrayStateChangerProxyImpl
: public StatusTrayStateChangerProxy
,
64 public base::NonThreadSafe
{
66 StatusTrayStateChangerProxyImpl()
67 : pending_requests_(0),
68 worker_thread_("StatusIconCOMWorkerThread"),
70 worker_thread_
.init_com_with_mta(false);
73 virtual void EnqueueChange(UINT icon_id
, HWND window
) override
{
74 DCHECK(CalledOnValidThread());
75 if (pending_requests_
== 0)
76 worker_thread_
.Start();
79 worker_thread_
.message_loop_proxy()->PostTaskAndReply(
82 &StatusTrayStateChangerProxyImpl::EnqueueChangeOnWorkerThread
,
85 base::Bind(&StatusTrayStateChangerProxyImpl::ChangeDone
,
86 weak_factory_
.GetWeakPtr()));
90 // Must be called only on |worker_thread_|, to ensure the correct COM
92 static void EnqueueChangeOnWorkerThread(UINT icon_id
, HWND window
) {
93 // It appears that IUnknowns are coincidentally compatible with
94 // scoped_refptr. Normally I wouldn't depend on that but it seems that
95 // base::win::IUnknownImpl itself depends on that coincidence so it's
96 // already being assumed elsewhere.
97 scoped_refptr
<StatusTrayStateChangerWin
> status_tray_state_changer(
98 new StatusTrayStateChangerWin(icon_id
, window
));
99 status_tray_state_changer
->EnsureTrayIconVisible();
102 // Called on UI thread.
104 DCHECK(CalledOnValidThread());
105 DCHECK_GT(pending_requests_
, 0);
107 if (--pending_requests_
== 0)
108 worker_thread_
.Stop();
112 int pending_requests_
;
113 base::Thread worker_thread_
;
114 base::WeakPtrFactory
<StatusTrayStateChangerProxyImpl
> weak_factory_
;
116 DISALLOW_COPY_AND_ASSIGN(StatusTrayStateChangerProxyImpl
);
119 StatusTrayWin::StatusTrayWin()
124 // Register our window class
125 WNDCLASSEX window_class
;
126 base::win::InitializeWindowClass(
127 chrome::kStatusTrayWindowClass
,
128 &base::win::WrappedWindowProc
<StatusTrayWin::WndProcStatic
>,
129 0, 0, 0, NULL
, NULL
, NULL
, NULL
, NULL
,
131 instance_
= window_class
.hInstance
;
132 atom_
= RegisterClassEx(&window_class
);
135 // If the taskbar is re-created after we start up, we have to rebuild all of
137 taskbar_created_message_
= RegisterWindowMessage(TEXT("TaskbarCreated"));
139 // Create an offscreen window for handling messages for the status icons. We
140 // create a hidden WS_POPUP window instead of an HWND_MESSAGE window, because
141 // only top-level windows such as popups can receive broadcast messages like
143 window_
= CreateWindow(MAKEINTATOM(atom_
),
144 0, WS_POPUP
, 0, 0, 0, 0, 0, 0, instance_
, 0);
145 gfx::CheckWindowCreated(window_
);
146 gfx::SetWindowUserData(window_
, this);
149 StatusTrayWin::~StatusTrayWin() {
151 DestroyWindow(window_
);
154 UnregisterClass(MAKEINTATOM(atom_
), instance_
);
157 void StatusTrayWin::UpdateIconVisibilityInBackground(
158 StatusIconWin
* status_icon
) {
159 if (!state_changer_proxy_
.get())
160 state_changer_proxy_
.reset(new StatusTrayStateChangerProxyImpl
);
162 state_changer_proxy_
->EnqueueChange(status_icon
->icon_id(),
163 status_icon
->window());
166 LRESULT CALLBACK
StatusTrayWin::WndProcStatic(HWND hwnd
,
170 // TODO(vadimt): Remove ScopedTracker below once crbug.com/440919 is fixed.
171 tracked_objects::ScopedTracker
tracking_profile(
172 FROM_HERE_WITH_EXPLICIT_FUNCTION("440919 StatusTrayWin::WndProcStatic"));
174 StatusTrayWin
* msg_wnd
= reinterpret_cast<StatusTrayWin
*>(
175 GetWindowLongPtr(hwnd
, GWLP_USERDATA
));
177 return msg_wnd
->WndProc(hwnd
, message
, wparam
, lparam
);
179 return ::DefWindowProc(hwnd
, message
, wparam
, lparam
);
182 LRESULT CALLBACK
StatusTrayWin::WndProc(HWND hwnd
,
186 if (message
== taskbar_created_message_
) {
187 // We need to reset all of our icons because the taskbar went away.
188 for (StatusIcons::const_iterator
i(status_icons().begin());
189 i
!= status_icons().end(); ++i
) {
190 StatusIconWin
* win_icon
= static_cast<StatusIconWin
*>(*i
);
191 win_icon
->ResetIcon();
194 } else if (message
== kStatusIconMessage
) {
195 StatusIconWin
* win_icon
= NULL
;
197 // Find the selected status icon.
198 for (StatusIcons::const_iterator
i(status_icons().begin());
199 i
!= status_icons().end();
201 StatusIconWin
* current_win_icon
= static_cast<StatusIconWin
*>(*i
);
202 if (current_win_icon
->icon_id() == wparam
) {
203 win_icon
= current_win_icon
;
208 // It is possible for this procedure to be called with an obsolete icon
209 // id. In that case we should just return early before handling any
215 case TB_INDETERMINATE
:
216 win_icon
->HandleBalloonClickEvent();
222 // Walk our icons, find which one was clicked on, and invoke its
223 // HandleClickEvent() method.
224 gfx::Point
cursor_pos(
225 gfx::Screen::GetNativeScreen()->GetCursorScreenPoint());
226 win_icon
->HandleClickEvent(cursor_pos
, lparam
== WM_LBUTTONDOWN
);
229 } else if (message
== WM_ENDSESSION
) {
230 // If Chrome is in background-only mode, this is the only notification
231 // it gets that Windows is exiting. Make sure we shutdown in an orderly
233 TraceSessionEnding(lparam
);
234 chrome::SessionEnding();
236 return ::DefWindowProc(hwnd
, message
, wparam
, lparam
);
239 StatusIcon
* StatusTrayWin::CreatePlatformStatusIcon(
240 StatusTray::StatusIconType type
,
241 const gfx::ImageSkia
& image
,
242 const base::string16
& tool_tip
) {
244 if (type
== StatusTray::OTHER_ICON
)
245 next_icon_id
= NextIconId();
247 next_icon_id
= ReservedIconId(type
);
250 new StatusIconWin(this, next_icon_id
, window_
, kStatusIconMessage
);
252 icon
->SetImage(image
);
253 icon
->SetToolTip(tool_tip
);
257 UINT
StatusTrayWin::NextIconId() {
258 UINT icon_id
= next_icon_id_
++;
259 return kBaseIconId
+ static_cast<UINT
>(NAMED_STATUS_ICON_COUNT
) + icon_id
;
262 void StatusTrayWin::SetStatusTrayStateChangerProxyForTest(
263 scoped_ptr
<StatusTrayStateChangerProxy
> proxy
) {
264 state_changer_proxy_
= proxy
.Pass();
267 StatusTray
* StatusTray::Create() {
268 return new StatusTrayWin();