416 lines
10 KiB
C++
416 lines
10 KiB
C++
#include "shellprv.h"
|
|
#include "balmsg.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()
|
|
{
|
|
if (InterlockedDecrement(&_cRef))
|
|
return _cRef;
|
|
|
|
delete this;
|
|
return 0;
|
|
}
|
|
|
|
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)
|
|
lstrcpyn(nid.szInfoTitle, _pszTitle, ARRAYSIZE(nid.szInfoTitle));
|
|
if (_pszText)
|
|
lstrcpyn(nid.szInfo, _pszText, ARRAYSIZE(nid.szInfo));
|
|
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)
|
|
lstrcpyn(nid.szTip, _pszToolTip, ARRAYSIZE(nid.szTip));
|
|
|
|
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;
|
|
}
|
|
|
|
STDAPI SHBalloonMessage(const BALLOON_MESSAGE *pbm)
|
|
{
|
|
HRESULT hr;
|
|
|
|
if (sizeof(*pbm) == pbm->dwSize)
|
|
{
|
|
IUserNotification *pun;
|
|
hr = CoCreateInstance(CLSID_UserNotification, NULL, CLSCTX_ALL,
|
|
IID_PPV_ARG(IUserNotification, &pun));
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
pun->SetBalloonRetry(-1, -1, pbm->cRetryCount);
|
|
pun->SetIconInfo(pbm->hIcon ? pbm->hIcon : LoadIcon(NULL, IDI_WINLOGO), pbm->pszTitle);
|
|
pun->SetBalloonInfo(pbm->pszTitle, pbm->pszText, pbm->dwInfoFlags);
|
|
|
|
hr = pun->Show(NULL, 0);
|
|
|
|
pun->Release();
|
|
}
|
|
}
|
|
else
|
|
hr = E_INVALIDARG;
|
|
return hr;
|
|
}
|