status_tray_win.cc revision 5c02ac1a9c1b504631c0a3d2b6e737b5d738bae1
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
19static const UINT kStatusIconMessage = WM_APP + 1;
20
21namespace {
22// |kBaseIconId| is 2 to avoid conflicts with plugins that hard-code id 1.
23const UINT kBaseIconId = 2;
24
25UINT 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.
34class 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
90StatusTrayWin::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
120StatusTrayWin::~StatusTrayWin() {
121  if (window_)
122    DestroyWindow(window_);
123
124  if (atom_)
125    UnregisterClass(MAKEINTATOM(atom_), instance_);
126}
127
128void 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
137LRESULT 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
149LRESULT 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
200StatusIcon* 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
218UINT StatusTrayWin::NextIconId() {
219  UINT icon_id = next_icon_id_++;
220  return kBaseIconId + static_cast<UINT>(NAMED_STATUS_ICON_COUNT) + icon_id;
221}
222
223void StatusTrayWin::SetStatusTrayStateChangerProxyForTest(
224    scoped_ptr<StatusTrayStateChangerProxy> proxy) {
225  state_changer_proxy_ = proxy.Pass();
226}
227
228StatusTray* StatusTray::Create() {
229  return new StatusTrayWin();
230}
231