Home | History | Annotate | Download | only in status_icons
      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.
      4 
      5 #include "chrome/browser/ui/views/status_icons/status_tray_state_changer_win.h"
      6 
      7 namespace {
      8 
      9 ////////////////////////////////////////////////////////////////////////////////
     10 // Status Tray API
     11 
     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.
     15 
     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
     18 // notifications.
     19 class __declspec(uuid("FB852B2C-6BAD-4605-9551-F15F87830935")) ITrayNotify
     20     : public IUnknown {
     21  public:
     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;
     27 };
     28 
     29 // ITrayNotifyWin8 is the interface that replaces ITrayNotify for newer versions
     30 // of Windows.
     31 class __declspec(uuid("D133CE13-3537-48BA-93A7-AFCD5D2053B4")) ITrayNotifyWin8
     32     : public IUnknown {
     33  public:
     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;
     40 };
     41 
     42 const CLSID CLSID_TrayNotify = {
     43     0x25DEAD04,
     44     0x1EAC,
     45     0x4911,
     46     {0x9E, 0x3A, 0xAD, 0x0A, 0x4A, 0xB5, 0x60, 0xFD}};
     47 
     48 }  // namespace
     49 
     50 StatusTrayStateChangerWin::StatusTrayStateChangerWin(UINT icon_id, HWND window)
     51     : interface_version_(INTERFACE_VERSION_UNKNOWN),
     52       icon_id_(icon_id),
     53       window_(window) {
     54   wchar_t module_name[MAX_PATH];
     55   ::GetModuleFileName(NULL, module_name, MAX_PATH);
     56 
     57   file_name_ = module_name;
     58 }
     59 
     60 void StatusTrayStateChangerWin::EnsureTrayIconVisible() {
     61   DCHECK(CalledOnValidThread());
     62 
     63   if (!CreateTrayNotify()) {
     64     VLOG(1) << "Unable to create COM object for ITrayNotify.";
     65     return;
     66   }
     67 
     68   scoped_ptr<NOTIFYITEM> notify_item = RegisterCallback();
     69 
     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)
     73     return;
     74 
     75   // If we are already on the taskbar, return since nothing needs to be done.
     76   if (notify_item->preference == PREFERENCE_SHOW_ALWAYS)
     77     return;
     78 
     79   notify_item->preference = PREFERENCE_SHOW_ALWAYS;
     80 
     81   SendNotifyItemUpdate(notify_item.Pass());
     82 }
     83 
     84 STDMETHODIMP_(ULONG) StatusTrayStateChangerWin::AddRef() {
     85   DCHECK(CalledOnValidThread());
     86   return base::win::IUnknownImpl::AddRef();
     87 }
     88 
     89 STDMETHODIMP_(ULONG) StatusTrayStateChangerWin::Release() {
     90   DCHECK(CalledOnValidThread());
     91   return base::win::IUnknownImpl::Release();
     92 }
     93 
     94 STDMETHODIMP StatusTrayStateChangerWin::QueryInterface(REFIID riid,
     95                                                        PVOID* ptr_void) {
     96   DCHECK(CalledOnValidThread());
     97   if (riid == __uuidof(INotificationCB)) {
     98     *ptr_void = static_cast<INotificationCB*>(this);
     99     AddRef();
    100     return S_OK;
    101   }
    102 
    103   return base::win::IUnknownImpl::QueryInterface(riid, ptr_void);
    104 }
    105 
    106 STDMETHODIMP StatusTrayStateChangerWin::Notify(ULONG event,
    107                                                NOTIFYITEM* notify_item) {
    108   DCHECK(CalledOnValidThread());
    109   DCHECK(notify_item);
    110   if (notify_item->hwnd != window_ || notify_item->id != icon_id_ ||
    111       base::string16(notify_item->exe_name) != file_name_) {
    112     return S_OK;
    113   }
    114 
    115   notify_item_.reset(new NOTIFYITEM(*notify_item));
    116   return S_OK;
    117 }
    118 
    119 StatusTrayStateChangerWin::~StatusTrayStateChangerWin() {
    120   DCHECK(CalledOnValidThread());
    121 }
    122 
    123 bool StatusTrayStateChangerWin::CreateTrayNotify() {
    124   DCHECK(CalledOnValidThread());
    125 
    126   tray_notify_.Release();  // Release so this method can be called more than
    127                            // once.
    128 
    129   HRESULT hr = tray_notify_.CreateInstance(CLSID_TrayNotify);
    130   if (FAILED(hr))
    131     return false;
    132 
    133   base::win::ScopedComPtr<ITrayNotifyWin8> tray_notify_win8;
    134   hr = tray_notify_win8.QueryFrom(tray_notify_);
    135   if (SUCCEEDED(hr)) {
    136     interface_version_ = INTERFACE_VERSION_WIN8;
    137     return true;
    138   }
    139 
    140   base::win::ScopedComPtr<ITrayNotify> tray_notify_legacy;
    141   hr = tray_notify_legacy.QueryFrom(tray_notify_);
    142   if (SUCCEEDED(hr)) {
    143     interface_version_ = INTERFACE_VERSION_LEGACY;
    144     return true;
    145   }
    146 
    147   return false;
    148 }
    149 
    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);
    155 
    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.";
    162       break;
    163     case INTERFACE_VERSION_LEGACY:
    164       if (!RegisterCallbackLegacy())
    165         VLOG(1) << "Unable to successfully run RegisterCallbackLegacy.";
    166       break;
    167     default:
    168       NOTREACHED();
    169   }
    170 
    171   // Adding an intermediate scoped pointer here so that |notify_item_| is reset
    172   // to NULL.
    173   scoped_ptr<NOTIFYITEM> rv(notify_item_.release());
    174   return rv.Pass();
    175 }
    176 
    177 bool StatusTrayStateChangerWin::RegisterCallbackWin8() {
    178   base::win::ScopedComPtr<ITrayNotifyWin8> tray_notify_win8;
    179   HRESULT hr = tray_notify_win8.QueryFrom(tray_notify_);
    180   if (FAILED(hr))
    181     return false;
    182 
    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);
    190   if (FAILED(hr)) {
    191     return false;
    192   }
    193 
    194   return true;
    195 }
    196 
    197 bool StatusTrayStateChangerWin::RegisterCallbackLegacy() {
    198   base::win::ScopedComPtr<ITrayNotify> tray_notify;
    199   HRESULT hr = tray_notify.QueryFrom(tray_notify_);
    200   if (FAILED(hr)) {
    201     return false;
    202   }
    203 
    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);
    215   if (FAILED(hr)) {
    216     return false;
    217   }
    218 
    219   return true;
    220 }
    221 
    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_);
    227     if (SUCCEEDED(hr))
    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_);
    232     if (SUCCEEDED(hr))
    233       tray_notify->SetPreference(notify_item.get());
    234   }
    235 }
    236