You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
397 lines
10 KiB
397 lines
10 KiB
#include "shellprv.h"
|
|
|
|
#define BALLOON_SHOW_TIME 15000 // 15 sec
|
|
#define BALLOON_REPEAT_DELAY 10000 // 10 sec
|
|
|
|
#define WM_NOTIFY_MESSAGE (WM_USER + 100)
|
|
|
|
#define IDT_REMINDER 1
|
|
#define IDT_DESTROY 2
|
|
#define IDT_QUERYCANCEL 3
|
|
#define IDT_NOBALLOON 4
|
|
|
|
class CUserNotification : public IUserNotification
|
|
{
|
|
public:
|
|
CUserNotification();
|
|
|
|
// IUnknown
|
|
STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
|
|
STDMETHODIMP_(ULONG) AddRef();
|
|
STDMETHODIMP_(ULONG) Release();
|
|
|
|
// IUserNotification
|
|
STDMETHODIMP SetBalloonInfo(LPCWSTR pszTitle, LPCWSTR pszText, DWORD dwInfoFlags);
|
|
STDMETHODIMP SetBalloonRetry(DWORD dwShowTime, DWORD dwInterval, UINT cRetryCount);
|
|
STDMETHODIMP SetIconInfo(HICON hIcon, LPCWSTR pszToolTip);
|
|
STDMETHODIMP Show(IQueryContinue *pqc, DWORD dwContinuePollInterval);
|
|
STDMETHODIMP PlaySound(LPCWSTR pszSoundName);
|
|
|
|
private:
|
|
~CUserNotification();
|
|
static LRESULT CALLBACK s_WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
|
|
LRESULT CALLBACK _WndProc(UINT uMsg, WPARAM wParam, LPARAM lParam);
|
|
HRESULT _GetWindow();
|
|
BOOL _SyncInfo();
|
|
BOOL _SyncIcon();
|
|
void _DelayDestroy(HRESULT hrDone);
|
|
void _Timeout();
|
|
void _RemoveNotifyIcon();
|
|
|
|
LONG _cRef;
|
|
HWND _hwnd;
|
|
HICON _hicon;
|
|
DWORD _dwShowTime;
|
|
DWORD _dwInterval;
|
|
UINT _cRetryCount;
|
|
HRESULT _hrDone;
|
|
DWORD _dwContinuePollInterval;
|
|
IQueryContinue *_pqc;
|
|
|
|
DWORD _dwInfoFlags;
|
|
WCHAR *_pszTitle;
|
|
WCHAR *_pszText;
|
|
WCHAR *_pszToolTip;
|
|
};
|
|
|
|
CUserNotification::CUserNotification()
|
|
: _cRef(1), _cRetryCount(-1), _dwShowTime(BALLOON_SHOW_TIME),
|
|
_dwInterval(BALLOON_REPEAT_DELAY), _dwInfoFlags(NIIF_NONE)
|
|
{
|
|
}
|
|
|
|
CUserNotification::~CUserNotification()
|
|
{
|
|
Str_SetPtrW(&_pszToolTip, NULL);
|
|
Str_SetPtrW(&_pszTitle, NULL);
|
|
Str_SetPtrW(&_pszText, NULL);
|
|
|
|
if (_hwnd)
|
|
{
|
|
_RemoveNotifyIcon();
|
|
DestroyWindow(_hwnd);
|
|
}
|
|
|
|
if (_hicon)
|
|
DestroyIcon(_hicon);
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CUserNotification::AddRef()
|
|
{
|
|
return InterlockedIncrement(&_cRef);
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CUserNotification::Release()
|
|
{
|
|
ASSERT( 0 != _cRef );
|
|
ULONG cRef = InterlockedDecrement(&_cRef);
|
|
if ( 0 == cRef )
|
|
{
|
|
delete this;
|
|
}
|
|
return cRef;
|
|
}
|
|
|
|
HRESULT CUserNotification::QueryInterface(REFIID riid, void **ppv)
|
|
{
|
|
static const QITAB qit[] =
|
|
{
|
|
QITABENT(CUserNotification, IUserNotification),
|
|
{ 0 },
|
|
};
|
|
return QISearch(this, qit, riid, ppv);
|
|
}
|
|
|
|
HRESULT CUserNotification::SetBalloonInfo(LPCWSTR pszTitle, LPCWSTR pszText, DWORD dwInfoFlags)
|
|
{
|
|
Str_SetPtrW(&_pszTitle, pszTitle);
|
|
Str_SetPtrW(&_pszText, pszText);
|
|
_dwInfoFlags = dwInfoFlags;
|
|
_SyncInfo(); // may fail if balloon _hwnd has not been created yet
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CUserNotification::SetBalloonRetry(DWORD dwShowTime, DWORD dwInterval, UINT cRetryCount)
|
|
{
|
|
if (-1 != dwShowTime)
|
|
_dwShowTime = dwShowTime;
|
|
|
|
if (-1 != dwInterval)
|
|
_dwInterval = dwInterval;
|
|
|
|
_cRetryCount = cRetryCount;
|
|
return S_OK;
|
|
}
|
|
|
|
HRESULT CUserNotification::SetIconInfo(HICON hIcon, LPCWSTR pszToolTip)
|
|
{
|
|
if (_hicon)
|
|
DestroyIcon(_hicon);
|
|
|
|
if (hIcon == NULL)
|
|
{
|
|
_hicon = NULL;
|
|
switch(_dwInfoFlags & NIIF_ICON_MASK)
|
|
{
|
|
case NIIF_INFO:
|
|
_hicon = LoadIcon(NULL, IDI_INFORMATION);
|
|
break;
|
|
case NIIF_WARNING:
|
|
_hicon = LoadIcon(NULL, IDI_WARNING);
|
|
break;
|
|
case NIIF_ERROR:
|
|
_hicon = LoadIcon(NULL, IDI_ERROR);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
_hicon = CopyIcon(hIcon);
|
|
}
|
|
|
|
Str_SetPtrW(&_pszToolTip, pszToolTip);
|
|
_SyncIcon();
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
// returns:
|
|
// S_OK
|
|
// user clicked on the balloon or icon
|
|
// S_FALSE
|
|
// query continue callback (pcq) cancelled the notification UI
|
|
// HRESULT_FROM_WIN32(ERROR_CANCELLED)
|
|
// timeouts expired (user ignored the UI)
|
|
HRESULT CUserNotification::Show(IQueryContinue *pqc, DWORD dwContinuePollInterval)
|
|
{
|
|
HRESULT hr = _GetWindow();
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
if (pqc)
|
|
{
|
|
_pqc = pqc; // don't need a ref since we don't return from here
|
|
_dwContinuePollInterval = dwContinuePollInterval > 100 ? dwContinuePollInterval : 500;
|
|
SetTimer(_hwnd, IDT_QUERYCANCEL, _dwContinuePollInterval, NULL);
|
|
}
|
|
|
|
// if there is no balloon info specified then there won't be a "balloon timeout" event
|
|
// thus we need to do this ourselves. this lets people use this object for non balloon
|
|
// notification icons
|
|
if ((NULL == _pszTitle) && (NULL == _pszText))
|
|
{
|
|
SetTimer(_hwnd, IDT_NOBALLOON, _dwShowTime, NULL);
|
|
}
|
|
|
|
MSG msg;
|
|
while (GetMessage(&msg, NULL, 0, 0))
|
|
{
|
|
TranslateMessage(&msg);
|
|
DispatchMessage(&msg);
|
|
}
|
|
hr = _hrDone;
|
|
if (pqc)
|
|
{
|
|
KillTimer(_hwnd, IDT_QUERYCANCEL); // in case any are in the queue
|
|
_pqc = NULL; // to avoid possible problems
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
HRESULT CUserNotification::PlaySound(LPCWSTR pszSoundName)
|
|
{
|
|
SHPlaySound(pszSoundName);
|
|
return S_OK;
|
|
}
|
|
|
|
// take down our notification icon
|
|
void CUserNotification::_RemoveNotifyIcon()
|
|
{
|
|
NOTIFYICONDATA nid = { sizeof(nid), _hwnd, 0 };
|
|
Shell_NotifyIcon(NIM_DELETE, &nid);
|
|
}
|
|
|
|
// the balloon related data (title, body test, dwInfoFlags, timeout
|
|
BOOL CUserNotification::_SyncInfo()
|
|
{
|
|
BOOL bRet = FALSE;
|
|
if (_hwnd)
|
|
{
|
|
NOTIFYICONDATA nid = { sizeof(nid), _hwnd, 0, NIF_INFO };
|
|
if (_pszTitle)
|
|
{
|
|
StringCchCopy(nid.szInfoTitle, ARRAYSIZE(nid.szInfoTitle), _pszTitle); // ok to truncate
|
|
}
|
|
if (_pszText)
|
|
{
|
|
StringCchCopy(nid.szInfo, ARRAYSIZE(nid.szInfo), _pszText); // ok to truncate
|
|
}
|
|
nid.dwInfoFlags = _dwInfoFlags;
|
|
nid.uTimeout = _dwShowTime;
|
|
|
|
bRet = Shell_NotifyIcon(NIM_MODIFY, &nid);
|
|
}
|
|
return bRet;
|
|
}
|
|
|
|
BOOL CUserNotification::_SyncIcon()
|
|
{
|
|
BOOL bRet = FALSE;
|
|
if (_hwnd)
|
|
{
|
|
NOTIFYICONDATA nid = { sizeof(nid), _hwnd, 0, NIF_ICON | NIF_TIP};
|
|
nid.hIcon = _hicon ? _hicon : LoadIcon(NULL, IDI_WINLOGO);
|
|
if (_pszToolTip)
|
|
{
|
|
StringCchCopy(nid.szTip, ARRAYSIZE(nid.szTip), _pszToolTip); // ok to truncate
|
|
}
|
|
|
|
bRet = Shell_NotifyIcon(NIM_MODIFY, &nid);
|
|
}
|
|
return bRet;
|
|
}
|
|
|
|
HRESULT CUserNotification::_GetWindow()
|
|
{
|
|
HRESULT hr = S_OK;
|
|
if (NULL == _hwnd)
|
|
{
|
|
_hwnd = SHCreateWorkerWindow(s_WndProc, NULL, 0, 0, NULL, this);
|
|
if (_hwnd)
|
|
{
|
|
NOTIFYICONDATA nid = { sizeof(nid), _hwnd, 0, NIF_MESSAGE, WM_NOTIFY_MESSAGE };
|
|
|
|
if (Shell_NotifyIcon(NIM_ADD, &nid))
|
|
{
|
|
_SyncIcon();
|
|
_SyncInfo();
|
|
}
|
|
else
|
|
{
|
|
DestroyWindow(_hwnd);
|
|
_hwnd = NULL;
|
|
hr = E_FAIL;
|
|
}
|
|
}
|
|
}
|
|
return hr;
|
|
}
|
|
|
|
void CUserNotification::_DelayDestroy(HRESULT hrDone)
|
|
{
|
|
_hrDone = hrDone;
|
|
SetTimer(_hwnd, IDT_DESTROY, 250, NULL);
|
|
}
|
|
|
|
void CUserNotification::_Timeout()
|
|
{
|
|
if (_cRetryCount)
|
|
{
|
|
_cRetryCount--;
|
|
SetTimer(_hwnd, IDT_REMINDER, _dwInterval, NULL);
|
|
}
|
|
else
|
|
{
|
|
// timeout, same HRESULT as user cancel
|
|
_DelayDestroy(HRESULT_FROM_WIN32(ERROR_CANCELLED));
|
|
}
|
|
}
|
|
|
|
LRESULT CALLBACK CUserNotification::_WndProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
LRESULT lres = 0;
|
|
|
|
switch (uMsg)
|
|
{
|
|
case WM_NCDESTROY:
|
|
SetWindowLongPtr(_hwnd, 0, NULL);
|
|
_hwnd = NULL;
|
|
break;
|
|
|
|
case WM_TIMER:
|
|
KillTimer(_hwnd, wParam); // make all timers single shot
|
|
switch (wParam)
|
|
{
|
|
case IDT_REMINDER:
|
|
_SyncInfo();
|
|
break;
|
|
|
|
case IDT_DESTROY:
|
|
_RemoveNotifyIcon();
|
|
PostQuitMessage(0); // exit our msg loop
|
|
break;
|
|
|
|
case IDT_QUERYCANCEL:
|
|
if (_pqc && (S_OK == _pqc->QueryContinue()))
|
|
SetTimer(_hwnd, IDT_QUERYCANCEL, _dwContinuePollInterval, NULL);
|
|
else
|
|
_DelayDestroy(S_FALSE); // callback cancelled
|
|
break;
|
|
|
|
case IDT_NOBALLOON:
|
|
_Timeout();
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case WM_NOTIFY_MESSAGE:
|
|
switch (lParam)
|
|
{
|
|
case NIN_BALLOONSHOW:
|
|
case NIN_BALLOONHIDE:
|
|
break;
|
|
|
|
case NIN_BALLOONTIMEOUT:
|
|
_Timeout();
|
|
break;
|
|
|
|
case NIN_BALLOONUSERCLICK:
|
|
case WM_LBUTTONDOWN:
|
|
case WM_RBUTTONDOWN:
|
|
_DelayDestroy(S_OK); // user click
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
lres = DefWindowProc(_hwnd, uMsg, wParam, lParam);
|
|
break;
|
|
}
|
|
return lres;
|
|
}
|
|
|
|
LRESULT CALLBACK CUserNotification::s_WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
|
|
{
|
|
CUserNotification *pun = (CUserNotification *)GetWindowLongPtr(hwnd, 0);
|
|
|
|
if (WM_CREATE == uMsg)
|
|
{
|
|
CREATESTRUCT *pcs = (CREATESTRUCT *)lParam;
|
|
pun = (CUserNotification *)pcs->lpCreateParams;
|
|
pun->_hwnd = hwnd;
|
|
SetWindowLongPtr(hwnd, 0, (LONG_PTR)pun);
|
|
}
|
|
|
|
return pun ? pun->_WndProc(uMsg, wParam, lParam) : DefWindowProc(hwnd, uMsg, wParam, lParam);
|
|
}
|
|
|
|
STDAPI CUserNotification_CreateInstance(IUnknown* punkOuter, REFIID riid, void **ppv)
|
|
{
|
|
HRESULT hr;
|
|
CUserNotification* p = new CUserNotification();
|
|
if (p)
|
|
{
|
|
hr = p->QueryInterface(riid, ppv);
|
|
p->Release();
|
|
}
|
|
else
|
|
{
|
|
hr = E_OUTOFMEMORY;
|
|
*ppv = NULL;
|
|
}
|
|
return hr;
|
|
}
|