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. 4 5 #include "chrome/browser/ui/views/status_icons/status_tray_win.h" 6 7 #include <commctrl.h> 8 9 #include "base/bind.h" 10 #include "base/threading/non_thread_safe.h" 11 #include "base/threading/thread.h" 12 #include "base/win/wrapped_window_proc.h" 13 #include "chrome/browser/ui/views/status_icons/status_icon_win.h" 14 #include "chrome/browser/ui/views/status_icons/status_tray_state_changer_win.h" 15 #include "chrome/common/chrome_constants.h" 16 #include "ui/gfx/screen.h" 17 #include "ui/gfx/win/hwnd_util.h" 18 19 static const UINT kStatusIconMessage = WM_APP + 1; 20 21 namespace { 22 // |kBaseIconId| is 2 to avoid conflicts with plugins that hard-code id 1. 23 const UINT kBaseIconId = 2; 24 25 UINT ReservedIconId(StatusTray::StatusIconType type) { 26 return kBaseIconId + static_cast<UINT>(type); 27 } 28 } // namespace 29 30 // Default implementation for StatusTrayStateChanger that communicates to 31 // Exporer.exe via COM. It spawns a background thread with a fresh COM 32 // apartment and requests that the visibility be increased unless the user 33 // has explicitly set the icon to be hidden. 34 class StatusTrayStateChangerProxyImpl : public StatusTrayStateChangerProxy, 35 public base::NonThreadSafe { 36 public: 37 StatusTrayStateChangerProxyImpl() 38 : pending_requests_(0), 39 worker_thread_("StatusIconCOMWorkerThread"), 40 weak_factory_(this) { 41 worker_thread_.init_com_with_mta(false); 42 } 43 44 virtual void EnqueueChange(UINT icon_id, HWND window) OVERRIDE { 45 DCHECK(CalledOnValidThread()); 46 if (pending_requests_ == 0) 47 worker_thread_.Start(); 48 49 ++pending_requests_; 50 worker_thread_.message_loop_proxy()->PostTaskAndReply( 51 FROM_HERE, 52 base::Bind( 53 &StatusTrayStateChangerProxyImpl::EnqueueChangeOnWorkerThread, 54 icon_id, 55 window), 56 base::Bind(&StatusTrayStateChangerProxyImpl::ChangeDone, 57 weak_factory_.GetWeakPtr())); 58 } 59 60 private: 61 // Must be called only on |worker_thread_|, to ensure the correct COM 62 // apartment. 63 static void EnqueueChangeOnWorkerThread(UINT icon_id, HWND window) { 64 // It appears that IUnknowns are coincidentally compatible with 65 // scoped_refptr. Normally I wouldn't depend on that but it seems that 66 // base::win::IUnknownImpl itself depends on that coincidence so it's 67 // already being assumed elsewhere. 68 scoped_refptr<StatusTrayStateChangerWin> status_tray_state_changer( 69 new StatusTrayStateChangerWin(icon_id, window)); 70 status_tray_state_changer->EnsureTrayIconVisible(); 71 } 72 73 // Called on UI thread. 74 void ChangeDone() { 75 DCHECK(CalledOnValidThread()); 76 DCHECK_GT(pending_requests_, 0); 77 78 if (--pending_requests_ == 0) 79 worker_thread_.Stop(); 80 } 81 82 private: 83 int pending_requests_; 84 base::Thread worker_thread_; 85 base::WeakPtrFactory<StatusTrayStateChangerProxyImpl> weak_factory_; 86 87 DISALLOW_COPY_AND_ASSIGN(StatusTrayStateChangerProxyImpl); 88 }; 89 90 StatusTrayWin::StatusTrayWin() 91 : next_icon_id_(1), 92 atom_(0), 93 instance_(NULL), 94 window_(NULL) { 95 // Register our window class 96 WNDCLASSEX window_class; 97 base::win::InitializeWindowClass( 98 chrome::kStatusTrayWindowClass, 99 &base::win::WrappedWindowProc<StatusTrayWin::WndProcStatic>, 100 0, 0, 0, NULL, NULL, NULL, NULL, NULL, 101 &window_class); 102 instance_ = window_class.hInstance; 103 atom_ = RegisterClassEx(&window_class); 104 CHECK(atom_); 105 106 // If the taskbar is re-created after we start up, we have to rebuild all of 107 // our icons. 108 taskbar_created_message_ = RegisterWindowMessage(TEXT("TaskbarCreated")); 109 110 // Create an offscreen window for handling messages for the status icons. We 111 // create a hidden WS_POPUP window instead of an HWND_MESSAGE window, because 112 // only top-level windows such as popups can receive broadcast messages like 113 // "TaskbarCreated". 114 window_ = CreateWindow(MAKEINTATOM(atom_), 115 0, WS_POPUP, 0, 0, 0, 0, 0, 0, instance_, 0); 116 gfx::CheckWindowCreated(window_); 117 gfx::SetWindowUserData(window_, this); 118 } 119 120 StatusTrayWin::~StatusTrayWin() { 121 if (window_) 122 DestroyWindow(window_); 123 124 if (atom_) 125 UnregisterClass(MAKEINTATOM(atom_), instance_); 126 } 127 128 void StatusTrayWin::UpdateIconVisibilityInBackground( 129 StatusIconWin* status_icon) { 130 if (!state_changer_proxy_.get()) 131 state_changer_proxy_.reset(new StatusTrayStateChangerProxyImpl); 132 133 state_changer_proxy_->EnqueueChange(status_icon->icon_id(), 134 status_icon->window()); 135 } 136 137 LRESULT CALLBACK StatusTrayWin::WndProcStatic(HWND hwnd, 138 UINT message, 139 WPARAM wparam, 140 LPARAM lparam) { 141 StatusTrayWin* msg_wnd = reinterpret_cast<StatusTrayWin*>( 142 GetWindowLongPtr(hwnd, GWLP_USERDATA)); 143 if (msg_wnd) 144 return msg_wnd->WndProc(hwnd, message, wparam, lparam); 145 else 146 return ::DefWindowProc(hwnd, message, wparam, lparam); 147 } 148 149 LRESULT CALLBACK StatusTrayWin::WndProc(HWND hwnd, 150 UINT message, 151 WPARAM wparam, 152 LPARAM lparam) { 153 if (message == taskbar_created_message_) { 154 // We need to reset all of our icons because the taskbar went away. 155 for (StatusIcons::const_iterator i(status_icons().begin()); 156 i != status_icons().end(); ++i) { 157 StatusIconWin* win_icon = static_cast<StatusIconWin*>(*i); 158 win_icon->ResetIcon(); 159 } 160 return TRUE; 161 } else if (message == kStatusIconMessage) { 162 StatusIconWin* win_icon = NULL; 163 164 // Find the selected status icon. 165 for (StatusIcons::const_iterator i(status_icons().begin()); 166 i != status_icons().end(); 167 ++i) { 168 StatusIconWin* current_win_icon = static_cast<StatusIconWin*>(*i); 169 if (current_win_icon->icon_id() == wparam) { 170 win_icon = current_win_icon; 171 break; 172 } 173 } 174 175 // It is possible for this procedure to be called with an obsolete icon 176 // id. In that case we should just return early before handling any 177 // actions. 178 if (!win_icon) 179 return TRUE; 180 181 switch (lparam) { 182 case TB_INDETERMINATE: 183 win_icon->HandleBalloonClickEvent(); 184 return TRUE; 185 186 case WM_LBUTTONDOWN: 187 case WM_RBUTTONDOWN: 188 case WM_CONTEXTMENU: 189 // Walk our icons, find which one was clicked on, and invoke its 190 // HandleClickEvent() method. 191 gfx::Point cursor_pos( 192 gfx::Screen::GetNativeScreen()->GetCursorScreenPoint()); 193 win_icon->HandleClickEvent(cursor_pos, lparam == WM_LBUTTONDOWN); 194 return TRUE; 195 } 196 } 197 return ::DefWindowProc(hwnd, message, wparam, lparam); 198 } 199 200 StatusIcon* StatusTrayWin::CreatePlatformStatusIcon( 201 StatusTray::StatusIconType type, 202 const gfx::ImageSkia& image, 203 const base::string16& tool_tip) { 204 UINT next_icon_id; 205 if (type == StatusTray::OTHER_ICON) 206 next_icon_id = NextIconId(); 207 else 208 next_icon_id = ReservedIconId(type); 209 210 StatusIcon* icon = 211 new StatusIconWin(this, next_icon_id, window_, kStatusIconMessage); 212 213 icon->SetImage(image); 214 icon->SetToolTip(tool_tip); 215 return icon; 216 } 217 218 UINT StatusTrayWin::NextIconId() { 219 UINT icon_id = next_icon_id_++; 220 return kBaseIconId + static_cast<UINT>(NAMED_STATUS_ICON_COUNT) + icon_id; 221 } 222 223 void StatusTrayWin::SetStatusTrayStateChangerProxyForTest( 224 scoped_ptr<StatusTrayStateChangerProxy> proxy) { 225 state_changer_proxy_ = proxy.Pass(); 226 } 227 228 StatusTray* StatusTray::Create() { 229 return new StatusTrayWin(); 230 } 231