Add StatusTrayStateChangerWindow from chrome.
This commit is contained in:
parent
01e93c108c
commit
08e7c07c57
2 changed files with 369 additions and 0 deletions
|
@ -0,0 +1,236 @@
|
||||||
|
// Copyright 2014 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
#include "chrome/browser/ui/views/status_icons/status_tray_state_changer_win.h"
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Status Tray API
|
||||||
|
|
||||||
|
// The folowing describes the interface to the undocumented Windows Exporer APIs
|
||||||
|
// for manipulating with the status tray area. This code should be used with
|
||||||
|
// care as it can change with versions (even minor versions) of Windows.
|
||||||
|
|
||||||
|
// ITrayNotify is an interface describing the API for manipulating the state of
|
||||||
|
// the Windows notification area, as well as for registering for change
|
||||||
|
// notifications.
|
||||||
|
class __declspec(uuid("FB852B2C-6BAD-4605-9551-F15F87830935")) ITrayNotify
|
||||||
|
: public IUnknown {
|
||||||
|
public:
|
||||||
|
virtual HRESULT STDMETHODCALLTYPE
|
||||||
|
RegisterCallback(INotificationCB* callback) = 0;
|
||||||
|
virtual HRESULT STDMETHODCALLTYPE
|
||||||
|
SetPreference(const NOTIFYITEM* notify_item) = 0;
|
||||||
|
virtual HRESULT STDMETHODCALLTYPE EnableAutoTray(BOOL enabled) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ITrayNotifyWin8 is the interface that replaces ITrayNotify for newer versions
|
||||||
|
// of Windows.
|
||||||
|
class __declspec(uuid("D133CE13-3537-48BA-93A7-AFCD5D2053B4")) ITrayNotifyWin8
|
||||||
|
: public IUnknown {
|
||||||
|
public:
|
||||||
|
virtual HRESULT STDMETHODCALLTYPE
|
||||||
|
RegisterCallback(INotificationCB* callback, unsigned long*) = 0;
|
||||||
|
virtual HRESULT STDMETHODCALLTYPE UnregisterCallback(unsigned long*) = 0;
|
||||||
|
virtual HRESULT STDMETHODCALLTYPE SetPreference(NOTIFYITEM const*) = 0;
|
||||||
|
virtual HRESULT STDMETHODCALLTYPE EnableAutoTray(BOOL) = 0;
|
||||||
|
virtual HRESULT STDMETHODCALLTYPE DoAction(BOOL) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const CLSID CLSID_TrayNotify = {
|
||||||
|
0x25DEAD04,
|
||||||
|
0x1EAC,
|
||||||
|
0x4911,
|
||||||
|
{0x9E, 0x3A, 0xAD, 0x0A, 0x4A, 0xB5, 0x60, 0xFD}};
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
StatusTrayStateChangerWin::StatusTrayStateChangerWin(UINT icon_id, HWND window)
|
||||||
|
: interface_version_(INTERFACE_VERSION_UNKNOWN),
|
||||||
|
icon_id_(icon_id),
|
||||||
|
window_(window) {
|
||||||
|
wchar_t module_name[MAX_PATH];
|
||||||
|
::GetModuleFileName(NULL, module_name, MAX_PATH);
|
||||||
|
|
||||||
|
file_name_ = module_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StatusTrayStateChangerWin::EnsureTrayIconVisible() {
|
||||||
|
DCHECK(CalledOnValidThread());
|
||||||
|
|
||||||
|
if (!CreateTrayNotify()) {
|
||||||
|
VLOG(1) << "Unable to create COM object for ITrayNotify.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
scoped_ptr<NOTIFYITEM> notify_item = RegisterCallback();
|
||||||
|
|
||||||
|
// If the user has already hidden us explicitly, try to honor their choice by
|
||||||
|
// not changing anything.
|
||||||
|
if (notify_item->preference == PREFERENCE_SHOW_NEVER)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If we are already on the taskbar, return since nothing needs to be done.
|
||||||
|
if (notify_item->preference == PREFERENCE_SHOW_ALWAYS)
|
||||||
|
return;
|
||||||
|
|
||||||
|
notify_item->preference = PREFERENCE_SHOW_ALWAYS;
|
||||||
|
|
||||||
|
SendNotifyItemUpdate(notify_item.Pass());
|
||||||
|
}
|
||||||
|
|
||||||
|
STDMETHODIMP_(ULONG) StatusTrayStateChangerWin::AddRef() {
|
||||||
|
DCHECK(CalledOnValidThread());
|
||||||
|
return base::win::IUnknownImpl::AddRef();
|
||||||
|
}
|
||||||
|
|
||||||
|
STDMETHODIMP_(ULONG) StatusTrayStateChangerWin::Release() {
|
||||||
|
DCHECK(CalledOnValidThread());
|
||||||
|
return base::win::IUnknownImpl::Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
STDMETHODIMP StatusTrayStateChangerWin::QueryInterface(REFIID riid,
|
||||||
|
PVOID* ptr_void) {
|
||||||
|
DCHECK(CalledOnValidThread());
|
||||||
|
if (riid == __uuidof(INotificationCB)) {
|
||||||
|
*ptr_void = static_cast<INotificationCB*>(this);
|
||||||
|
AddRef();
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base::win::IUnknownImpl::QueryInterface(riid, ptr_void);
|
||||||
|
}
|
||||||
|
|
||||||
|
STDMETHODIMP StatusTrayStateChangerWin::Notify(ULONG event,
|
||||||
|
NOTIFYITEM* notify_item) {
|
||||||
|
DCHECK(CalledOnValidThread());
|
||||||
|
DCHECK(notify_item);
|
||||||
|
if (notify_item->hwnd != window_ || notify_item->id != icon_id_ ||
|
||||||
|
base::string16(notify_item->exe_name) != file_name_) {
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
notify_item_.reset(new NOTIFYITEM(*notify_item));
|
||||||
|
return S_OK;
|
||||||
|
}
|
||||||
|
|
||||||
|
StatusTrayStateChangerWin::~StatusTrayStateChangerWin() {
|
||||||
|
DCHECK(CalledOnValidThread());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StatusTrayStateChangerWin::CreateTrayNotify() {
|
||||||
|
DCHECK(CalledOnValidThread());
|
||||||
|
|
||||||
|
tray_notify_.Release(); // Release so this method can be called more than
|
||||||
|
// once.
|
||||||
|
|
||||||
|
HRESULT hr = tray_notify_.CreateInstance(CLSID_TrayNotify);
|
||||||
|
if (FAILED(hr))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
base::win::ScopedComPtr<ITrayNotifyWin8> tray_notify_win8;
|
||||||
|
hr = tray_notify_win8.QueryFrom(tray_notify_);
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
interface_version_ = INTERFACE_VERSION_WIN8;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
base::win::ScopedComPtr<ITrayNotify> tray_notify_legacy;
|
||||||
|
hr = tray_notify_legacy.QueryFrom(tray_notify_);
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
interface_version_ = INTERFACE_VERSION_LEGACY;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
scoped_ptr<NOTIFYITEM> StatusTrayStateChangerWin::RegisterCallback() {
|
||||||
|
// |notify_item_| is used to store the result of the callback from
|
||||||
|
// Explorer.exe, which happens synchronously during
|
||||||
|
// RegisterCallbackWin8 or RegisterCallbackLegacy.
|
||||||
|
DCHECK(notify_item_.get() == NULL);
|
||||||
|
|
||||||
|
// TODO(dewittj): Add UMA logging here to report if either of our strategies
|
||||||
|
// has a tendency to fail on particular versions of Windows.
|
||||||
|
switch (interface_version_) {
|
||||||
|
case INTERFACE_VERSION_WIN8:
|
||||||
|
if (!RegisterCallbackWin8())
|
||||||
|
VLOG(1) << "Unable to successfully run RegisterCallbackWin8.";
|
||||||
|
break;
|
||||||
|
case INTERFACE_VERSION_LEGACY:
|
||||||
|
if (!RegisterCallbackLegacy())
|
||||||
|
VLOG(1) << "Unable to successfully run RegisterCallbackLegacy.";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
NOTREACHED();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Adding an intermediate scoped pointer here so that |notify_item_| is reset
|
||||||
|
// to NULL.
|
||||||
|
scoped_ptr<NOTIFYITEM> rv(notify_item_.release());
|
||||||
|
return rv.Pass();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StatusTrayStateChangerWin::RegisterCallbackWin8() {
|
||||||
|
base::win::ScopedComPtr<ITrayNotifyWin8> tray_notify_win8;
|
||||||
|
HRESULT hr = tray_notify_win8.QueryFrom(tray_notify_);
|
||||||
|
if (FAILED(hr))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// The following two lines cause Windows Explorer to call us back with all the
|
||||||
|
// existing tray icons and their preference. It would also presumably notify
|
||||||
|
// us if changes were made in realtime while we registered as a callback, but
|
||||||
|
// we just want to modify our own entry so we immediately unregister.
|
||||||
|
unsigned long callback_id = 0;
|
||||||
|
hr = tray_notify_win8->RegisterCallback(this, &callback_id);
|
||||||
|
tray_notify_win8->UnregisterCallback(&callback_id);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool StatusTrayStateChangerWin::RegisterCallbackLegacy() {
|
||||||
|
base::win::ScopedComPtr<ITrayNotify> tray_notify;
|
||||||
|
HRESULT hr = tray_notify.QueryFrom(tray_notify_);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following two lines cause Windows Explorer to call us back with all the
|
||||||
|
// existing tray icons and their preference. It would also presumably notify
|
||||||
|
// us if changes were made in realtime while we registered as a callback. In
|
||||||
|
// this version of the API, there can be only one registered callback so it is
|
||||||
|
// better to unregister as soon as possible.
|
||||||
|
// TODO(dewittj): Try to notice if the notification area icon customization
|
||||||
|
// window is open and postpone this call until the user closes it;
|
||||||
|
// registering the callback while the window is open can cause stale data to
|
||||||
|
// be displayed to the user.
|
||||||
|
hr = tray_notify->RegisterCallback(this);
|
||||||
|
tray_notify->RegisterCallback(NULL);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void StatusTrayStateChangerWin::SendNotifyItemUpdate(
|
||||||
|
scoped_ptr<NOTIFYITEM> notify_item) {
|
||||||
|
if (interface_version_ == INTERFACE_VERSION_LEGACY) {
|
||||||
|
base::win::ScopedComPtr<ITrayNotify> tray_notify;
|
||||||
|
HRESULT hr = tray_notify.QueryFrom(tray_notify_);
|
||||||
|
if (SUCCEEDED(hr))
|
||||||
|
tray_notify->SetPreference(notify_item.get());
|
||||||
|
} else if (interface_version_ == INTERFACE_VERSION_WIN8) {
|
||||||
|
base::win::ScopedComPtr<ITrayNotifyWin8> tray_notify;
|
||||||
|
HRESULT hr = tray_notify.QueryFrom(tray_notify_);
|
||||||
|
if (SUCCEEDED(hr))
|
||||||
|
tray_notify->SetPreference(notify_item.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,133 @@
|
||||||
|
// Copyright 2014 The Chromium Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
#ifndef CHROME_BROWSER_UI_VIEWS_STATUS_ICONS_STATUS_TRAY_STATE_CHANGER_WIN_H_
|
||||||
|
#define CHROME_BROWSER_UI_VIEWS_STATUS_ICONS_STATUS_TRAY_STATE_CHANGER_WIN_H_
|
||||||
|
|
||||||
|
#include "base/memory/scoped_ptr.h"
|
||||||
|
#include "base/strings/string16.h"
|
||||||
|
#include "base/threading/non_thread_safe.h"
|
||||||
|
#include "base/win/iunknown_impl.h"
|
||||||
|
#include "base/win/scoped_comptr.h"
|
||||||
|
|
||||||
|
// The known values for NOTIFYITEM's dwPreference member.
|
||||||
|
enum NOTIFYITEM_PREFERENCE {
|
||||||
|
// In Windows UI: "Only show notifications."
|
||||||
|
PREFERENCE_SHOW_WHEN_ACTIVE = 0,
|
||||||
|
// In Windows UI: "Hide icon and notifications."
|
||||||
|
PREFERENCE_SHOW_NEVER = 1,
|
||||||
|
// In Windows UI: "Show icon and notifications."
|
||||||
|
PREFERENCE_SHOW_ALWAYS = 2
|
||||||
|
};
|
||||||
|
|
||||||
|
// NOTIFYITEM describes an entry in Explorer's registry of status icons.
|
||||||
|
// Explorer keeps entries around for a process even after it exits.
|
||||||
|
struct NOTIFYITEM {
|
||||||
|
PWSTR exe_name; // The file name of the creating executable.
|
||||||
|
PWSTR tip; // The last hover-text value associated with this status
|
||||||
|
// item.
|
||||||
|
HICON icon; // The icon associated with this status item.
|
||||||
|
HWND hwnd; // The HWND associated with the status item.
|
||||||
|
DWORD preference; // Determines the behavior of the icon with respect to
|
||||||
|
// the taskbar. Values taken from NOTIFYITEM_PREFERENCE.
|
||||||
|
UINT id; // The ID specified by the application. (hWnd, uID) is
|
||||||
|
// unique.
|
||||||
|
GUID guid; // The GUID specified by the application, alternative to
|
||||||
|
// uID.
|
||||||
|
};
|
||||||
|
|
||||||
|
// INotificationCB is an interface that applications can implement in order to
|
||||||
|
// receive notifications about the state of the notification area manager.
|
||||||
|
class __declspec(uuid("D782CCBA-AFB0-43F1-94DB-FDA3779EACCB")) INotificationCB
|
||||||
|
: public IUnknown {
|
||||||
|
public:
|
||||||
|
virtual HRESULT STDMETHODCALLTYPE
|
||||||
|
Notify(ULONG event, NOTIFYITEM* notify_item) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// A class that is capable of reading and writing the state of the notification
|
||||||
|
// area in the Windows taskbar. It is used to promote a tray icon from the
|
||||||
|
// overflow area to the taskbar, and refuses to do anything if the user has
|
||||||
|
// explicitly marked an icon to be always hidden.
|
||||||
|
class StatusTrayStateChangerWin : public INotificationCB,
|
||||||
|
public base::win::IUnknownImpl,
|
||||||
|
public base::NonThreadSafe {
|
||||||
|
public:
|
||||||
|
StatusTrayStateChangerWin(UINT icon_id, HWND window);
|
||||||
|
|
||||||
|
// Call this method to move the icon matching |icon_id| and |window| to the
|
||||||
|
// taskbar from the overflow area. This will not make any changes if the
|
||||||
|
// icon has been set to |PREFERENCE_SHOW_NEVER|, in order to comply with
|
||||||
|
// the explicit wishes/configuration of the user.
|
||||||
|
void EnsureTrayIconVisible();
|
||||||
|
|
||||||
|
// IUnknown.
|
||||||
|
virtual ULONG STDMETHODCALLTYPE AddRef() OVERRIDE;
|
||||||
|
virtual ULONG STDMETHODCALLTYPE Release() OVERRIDE;
|
||||||
|
virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID, PVOID*) OVERRIDE;
|
||||||
|
|
||||||
|
// INotificationCB.
|
||||||
|
// Notify is called in response to RegisterCallback for each current
|
||||||
|
// entry in Explorer's list of notification area icons, and ever time
|
||||||
|
// one of them changes, until UnregisterCallback is called or |this|
|
||||||
|
// is destroyed.
|
||||||
|
virtual HRESULT STDMETHODCALLTYPE Notify(ULONG, NOTIFYITEM*);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
virtual ~StatusTrayStateChangerWin();
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class StatusTrayStateChangerWinTest;
|
||||||
|
|
||||||
|
enum InterfaceVersion {
|
||||||
|
INTERFACE_VERSION_LEGACY = 0,
|
||||||
|
INTERFACE_VERSION_WIN8,
|
||||||
|
INTERFACE_VERSION_UNKNOWN
|
||||||
|
};
|
||||||
|
|
||||||
|
// Creates an instance of TrayNotify, and ensures that it supports either
|
||||||
|
// ITrayNotify or ITrayNotifyWin8. Returns true on success.
|
||||||
|
bool CreateTrayNotify();
|
||||||
|
|
||||||
|
// Returns the NOTIFYITEM that corresponds to this executable and the
|
||||||
|
// HWND/ID pair that were used to create the StatusTrayStateChangerWin.
|
||||||
|
// Internally it calls the appropriate RegisterCallback{Win8,Legacy}.
|
||||||
|
scoped_ptr<NOTIFYITEM> RegisterCallback();
|
||||||
|
|
||||||
|
// Calls RegisterCallback with the appropriate interface required by
|
||||||
|
// different versions of Windows. This will result in |notify_item_| being
|
||||||
|
// updated when a matching item is passed into
|
||||||
|
// StatusTrayStateChangerWin::Notify.
|
||||||
|
bool RegisterCallbackWin8();
|
||||||
|
bool RegisterCallbackLegacy();
|
||||||
|
|
||||||
|
// Sends an update to Explorer with the passed NOTIFYITEM.
|
||||||
|
void SendNotifyItemUpdate(scoped_ptr<NOTIFYITEM> notify_item);
|
||||||
|
|
||||||
|
// Storing IUnknown since we will need to use different interfaces
|
||||||
|
// for different versions of Windows.
|
||||||
|
base::win::ScopedComPtr<IUnknown> tray_notify_;
|
||||||
|
InterfaceVersion interface_version_;
|
||||||
|
|
||||||
|
// The ID assigned to the notification area icon that we want to manipulate.
|
||||||
|
const UINT icon_id_;
|
||||||
|
// The HWND associated with the notification area icon that we want to
|
||||||
|
// manipulate. This is an unretained pointer, do not dereference.
|
||||||
|
const HWND window_;
|
||||||
|
// Executable name of the current program. Along with |icon_id_| and
|
||||||
|
// |window_|, this uniquely identifies a notification area entry to Explorer.
|
||||||
|
base::string16 file_name_;
|
||||||
|
|
||||||
|
// Temporary storage for the matched NOTIFYITEM. This is necessary because
|
||||||
|
// Notify doesn't return anything. The call flow looks like this:
|
||||||
|
// TrayNotify->RegisterCallback()
|
||||||
|
// ... other COM stack frames ..
|
||||||
|
// StatusTrayStateChangerWin->Notify(NOTIFYITEM);
|
||||||
|
// so we can't just return the notifyitem we're looking for.
|
||||||
|
scoped_ptr<NOTIFYITEM> notify_item_;
|
||||||
|
|
||||||
|
DISALLOW_COPY_AND_ASSIGN(StatusTrayStateChangerWin);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif // CHROME_BROWSER_UI_VIEWS_STATUS_ICONS_STATUS_TRAY_STATE_CHANGER_WIN_H_
|
Loading…
Reference in a new issue