Home | History | Annotate | Download | only in status_icons
      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