Source code of Windows XP (NT5)
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.

415 lines
10 KiB

  1. #include "shellprv.h"
  2. #include "balmsg.h"
  3. #define BALLOON_SHOW_TIME 15000 // 15 sec
  4. #define BALLOON_REPEAT_DELAY 10000 // 10 sec
  5. #define WM_NOTIFY_MESSAGE (WM_USER + 100)
  6. #define IDT_REMINDER 1
  7. #define IDT_DESTROY 2
  8. #define IDT_QUERYCANCEL 3
  9. #define IDT_NOBALLOON 4
  10. class CUserNotification : public IUserNotification
  11. {
  12. public:
  13. CUserNotification();
  14. // IUnknown
  15. STDMETHODIMP QueryInterface(REFIID riid, void **ppv);
  16. STDMETHODIMP_(ULONG) AddRef();
  17. STDMETHODIMP_(ULONG) Release();
  18. // IUserNotification
  19. STDMETHODIMP SetBalloonInfo(LPCWSTR pszTitle, LPCWSTR pszText, DWORD dwInfoFlags);
  20. STDMETHODIMP SetBalloonRetry(DWORD dwShowTime, DWORD dwInterval, UINT cRetryCount);
  21. STDMETHODIMP SetIconInfo(HICON hIcon, LPCWSTR pszToolTip);
  22. STDMETHODIMP Show(IQueryContinue *pqc, DWORD dwContinuePollInterval);
  23. STDMETHODIMP PlaySound(LPCWSTR pszSoundName);
  24. private:
  25. ~CUserNotification();
  26. static LRESULT CALLBACK s_WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
  27. LRESULT CALLBACK _WndProc(UINT uMsg, WPARAM wParam, LPARAM lParam);
  28. HRESULT _GetWindow();
  29. BOOL _SyncInfo();
  30. BOOL _SyncIcon();
  31. void _DelayDestroy(HRESULT hrDone);
  32. void _Timeout();
  33. void _RemoveNotifyIcon();
  34. LONG _cRef;
  35. HWND _hwnd;
  36. HICON _hicon;
  37. DWORD _dwShowTime;
  38. DWORD _dwInterval;
  39. UINT _cRetryCount;
  40. HRESULT _hrDone;
  41. DWORD _dwContinuePollInterval;
  42. IQueryContinue *_pqc;
  43. DWORD _dwInfoFlags;
  44. WCHAR *_pszTitle;
  45. WCHAR *_pszText;
  46. WCHAR *_pszToolTip;
  47. };
  48. CUserNotification::CUserNotification()
  49. : _cRef(1), _cRetryCount(-1), _dwShowTime(BALLOON_SHOW_TIME),
  50. _dwInterval(BALLOON_REPEAT_DELAY), _dwInfoFlags(NIIF_NONE)
  51. {
  52. }
  53. CUserNotification::~CUserNotification()
  54. {
  55. Str_SetPtrW(&_pszToolTip, NULL);
  56. Str_SetPtrW(&_pszTitle, NULL);
  57. Str_SetPtrW(&_pszText, NULL);
  58. if (_hwnd)
  59. {
  60. _RemoveNotifyIcon();
  61. DestroyWindow(_hwnd);
  62. }
  63. if (_hicon)
  64. DestroyIcon(_hicon);
  65. }
  66. STDMETHODIMP_(ULONG) CUserNotification::AddRef()
  67. {
  68. return InterlockedIncrement(&_cRef);
  69. }
  70. STDMETHODIMP_(ULONG) CUserNotification::Release()
  71. {
  72. if (InterlockedDecrement(&_cRef))
  73. return _cRef;
  74. delete this;
  75. return 0;
  76. }
  77. HRESULT CUserNotification::QueryInterface(REFIID riid, void **ppv)
  78. {
  79. static const QITAB qit[] =
  80. {
  81. QITABENT(CUserNotification, IUserNotification),
  82. { 0 },
  83. };
  84. return QISearch(this, qit, riid, ppv);
  85. }
  86. HRESULT CUserNotification::SetBalloonInfo(LPCWSTR pszTitle, LPCWSTR pszText, DWORD dwInfoFlags)
  87. {
  88. Str_SetPtrW(&_pszTitle, pszTitle);
  89. Str_SetPtrW(&_pszText, pszText);
  90. _dwInfoFlags = dwInfoFlags;
  91. _SyncInfo(); // may fail if balloon _hwnd has not been created yet
  92. return S_OK;
  93. }
  94. HRESULT CUserNotification::SetBalloonRetry(DWORD dwShowTime, DWORD dwInterval, UINT cRetryCount)
  95. {
  96. if (-1 != dwShowTime)
  97. _dwShowTime = dwShowTime;
  98. if (-1 != dwInterval)
  99. _dwInterval = dwInterval;
  100. _cRetryCount = cRetryCount;
  101. return S_OK;
  102. }
  103. HRESULT CUserNotification::SetIconInfo(HICON hIcon, LPCWSTR pszToolTip)
  104. {
  105. if (_hicon)
  106. DestroyIcon(_hicon);
  107. if (hIcon == NULL)
  108. {
  109. _hicon = NULL;
  110. switch(_dwInfoFlags & NIIF_ICON_MASK)
  111. {
  112. case NIIF_INFO:
  113. _hicon = LoadIcon(NULL, IDI_INFORMATION);
  114. break;
  115. case NIIF_WARNING:
  116. _hicon = LoadIcon(NULL, IDI_WARNING);
  117. break;
  118. case NIIF_ERROR:
  119. _hicon = LoadIcon(NULL, IDI_ERROR);
  120. break;
  121. }
  122. }
  123. else
  124. {
  125. _hicon = CopyIcon(hIcon);
  126. }
  127. Str_SetPtrW(&_pszToolTip, pszToolTip);
  128. _SyncIcon();
  129. return S_OK;
  130. }
  131. // returns:
  132. // S_OK
  133. // user clicked on the balloon or icon
  134. // S_FALSE
  135. // query continue callback (pcq) cancelled the notification UI
  136. // HRESULT_FROM_WIN32(ERROR_CANCELLED)
  137. // timeouts expired (user ignored the UI)
  138. HRESULT CUserNotification::Show(IQueryContinue *pqc, DWORD dwContinuePollInterval)
  139. {
  140. HRESULT hr = _GetWindow();
  141. if (SUCCEEDED(hr))
  142. {
  143. if (pqc)
  144. {
  145. _pqc = pqc; // don't need a ref since we don't return from here
  146. _dwContinuePollInterval = dwContinuePollInterval > 100 ? dwContinuePollInterval : 500;
  147. SetTimer(_hwnd, IDT_QUERYCANCEL, _dwContinuePollInterval, NULL);
  148. }
  149. // if there is no balloon info specified then there won't be a "balloon timeout" event
  150. // thus we need to do this ourselves. this lets people use this object for non balloon
  151. // notification icons
  152. if ((NULL == _pszTitle) && (NULL == _pszText))
  153. {
  154. SetTimer(_hwnd, IDT_NOBALLOON, _dwShowTime, NULL);
  155. }
  156. MSG msg;
  157. while (GetMessage(&msg, NULL, 0, 0))
  158. {
  159. TranslateMessage(&msg);
  160. DispatchMessage(&msg);
  161. }
  162. hr = _hrDone;
  163. if (pqc)
  164. {
  165. KillTimer(_hwnd, IDT_QUERYCANCEL); // in case any are in the queue
  166. _pqc = NULL; // to avoid possible problems
  167. }
  168. }
  169. return hr;
  170. }
  171. HRESULT CUserNotification::PlaySound(LPCWSTR pszSoundName)
  172. {
  173. SHPlaySound(pszSoundName);
  174. return S_OK;
  175. }
  176. // take down our notification icon
  177. void CUserNotification::_RemoveNotifyIcon()
  178. {
  179. NOTIFYICONDATA nid = { sizeof(nid), _hwnd, 0 };
  180. Shell_NotifyIcon(NIM_DELETE, &nid);
  181. }
  182. // the balloon related data (title, body test, dwInfoFlags, timeout
  183. BOOL CUserNotification::_SyncInfo()
  184. {
  185. BOOL bRet = FALSE;
  186. if (_hwnd)
  187. {
  188. NOTIFYICONDATA nid = { sizeof(nid), _hwnd, 0, NIF_INFO };
  189. if (_pszTitle)
  190. lstrcpyn(nid.szInfoTitle, _pszTitle, ARRAYSIZE(nid.szInfoTitle));
  191. if (_pszText)
  192. lstrcpyn(nid.szInfo, _pszText, ARRAYSIZE(nid.szInfo));
  193. nid.dwInfoFlags = _dwInfoFlags;
  194. nid.uTimeout = _dwShowTime;
  195. bRet = Shell_NotifyIcon(NIM_MODIFY, &nid);
  196. }
  197. return bRet;
  198. }
  199. BOOL CUserNotification::_SyncIcon()
  200. {
  201. BOOL bRet = FALSE;
  202. if (_hwnd)
  203. {
  204. NOTIFYICONDATA nid = { sizeof(nid), _hwnd, 0, NIF_ICON | NIF_TIP};
  205. nid.hIcon = _hicon ? _hicon : LoadIcon(NULL, IDI_WINLOGO);
  206. if (_pszToolTip)
  207. lstrcpyn(nid.szTip, _pszToolTip, ARRAYSIZE(nid.szTip));
  208. bRet = Shell_NotifyIcon(NIM_MODIFY, &nid);
  209. }
  210. return bRet;
  211. }
  212. HRESULT CUserNotification::_GetWindow()
  213. {
  214. HRESULT hr = S_OK;
  215. if (NULL == _hwnd)
  216. {
  217. _hwnd = SHCreateWorkerWindow(s_WndProc, NULL, 0, 0, NULL, this);
  218. if (_hwnd)
  219. {
  220. NOTIFYICONDATA nid = { sizeof(nid), _hwnd, 0, NIF_MESSAGE, WM_NOTIFY_MESSAGE };
  221. if (Shell_NotifyIcon(NIM_ADD, &nid))
  222. {
  223. _SyncIcon();
  224. _SyncInfo();
  225. }
  226. else
  227. {
  228. DestroyWindow(_hwnd);
  229. _hwnd = NULL;
  230. hr = E_FAIL;
  231. }
  232. }
  233. }
  234. return hr;
  235. }
  236. void CUserNotification::_DelayDestroy(HRESULT hrDone)
  237. {
  238. _hrDone = hrDone;
  239. SetTimer(_hwnd, IDT_DESTROY, 250, NULL);
  240. }
  241. void CUserNotification::_Timeout()
  242. {
  243. if (_cRetryCount)
  244. {
  245. _cRetryCount--;
  246. SetTimer(_hwnd, IDT_REMINDER, _dwInterval, NULL);
  247. }
  248. else
  249. {
  250. // timeout, same HRESULT as user cancel
  251. _DelayDestroy(HRESULT_FROM_WIN32(ERROR_CANCELLED));
  252. }
  253. }
  254. LRESULT CALLBACK CUserNotification::_WndProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
  255. {
  256. LRESULT lres = 0;
  257. switch (uMsg)
  258. {
  259. case WM_NCDESTROY:
  260. SetWindowLongPtr(_hwnd, 0, NULL);
  261. _hwnd = NULL;
  262. break;
  263. case WM_TIMER:
  264. KillTimer(_hwnd, wParam); // make all timers single shot
  265. switch (wParam)
  266. {
  267. case IDT_REMINDER:
  268. _SyncInfo();
  269. break;
  270. case IDT_DESTROY:
  271. _RemoveNotifyIcon();
  272. PostQuitMessage(0); // exit our msg loop
  273. break;
  274. case IDT_QUERYCANCEL:
  275. if (_pqc && (S_OK == _pqc->QueryContinue()))
  276. SetTimer(_hwnd, IDT_QUERYCANCEL, _dwContinuePollInterval, NULL);
  277. else
  278. _DelayDestroy(S_FALSE); // callback cancelled
  279. break;
  280. case IDT_NOBALLOON:
  281. _Timeout();
  282. break;
  283. }
  284. break;
  285. case WM_NOTIFY_MESSAGE:
  286. switch (lParam)
  287. {
  288. case NIN_BALLOONSHOW:
  289. case NIN_BALLOONHIDE:
  290. break;
  291. case NIN_BALLOONTIMEOUT:
  292. _Timeout();
  293. break;
  294. case NIN_BALLOONUSERCLICK:
  295. case WM_LBUTTONDOWN:
  296. case WM_RBUTTONDOWN:
  297. _DelayDestroy(S_OK); // user click
  298. break;
  299. default:
  300. break;
  301. }
  302. break;
  303. default:
  304. lres = DefWindowProc(_hwnd, uMsg, wParam, lParam);
  305. break;
  306. }
  307. return lres;
  308. }
  309. LRESULT CALLBACK CUserNotification::s_WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  310. {
  311. CUserNotification *pun = (CUserNotification *)GetWindowLongPtr(hwnd, 0);
  312. if (WM_CREATE == uMsg)
  313. {
  314. CREATESTRUCT *pcs = (CREATESTRUCT *)lParam;
  315. pun = (CUserNotification *)pcs->lpCreateParams;
  316. pun->_hwnd = hwnd;
  317. SetWindowLongPtr(hwnd, 0, (LONG_PTR)pun);
  318. }
  319. return pun ? pun->_WndProc(uMsg, wParam, lParam) : DefWindowProc(hwnd, uMsg, wParam, lParam);
  320. }
  321. STDAPI CUserNotification_CreateInstance(IUnknown* punkOuter, REFIID riid, void **ppv)
  322. {
  323. HRESULT hr;
  324. CUserNotification* p = new CUserNotification();
  325. if (p)
  326. {
  327. hr = p->QueryInterface(riid, ppv);
  328. p->Release();
  329. }
  330. else
  331. {
  332. hr = E_OUTOFMEMORY;
  333. *ppv = NULL;
  334. }
  335. return hr;
  336. }
  337. STDAPI SHBalloonMessage(const BALLOON_MESSAGE *pbm)
  338. {
  339. HRESULT hr;
  340. if (sizeof(*pbm) == pbm->dwSize)
  341. {
  342. IUserNotification *pun;
  343. hr = CoCreateInstance(CLSID_UserNotification, NULL, CLSCTX_ALL,
  344. IID_PPV_ARG(IUserNotification, &pun));
  345. if (SUCCEEDED(hr))
  346. {
  347. pun->SetBalloonRetry(-1, -1, pbm->cRetryCount);
  348. pun->SetIconInfo(pbm->hIcon ? pbm->hIcon : LoadIcon(NULL, IDI_WINLOGO), pbm->pszTitle);
  349. pun->SetBalloonInfo(pbm->pszTitle, pbm->pszText, pbm->dwInfoFlags);
  350. hr = pun->Show(NULL, 0);
  351. pun->Release();
  352. }
  353. }
  354. else
  355. hr = E_INVALIDARG;
  356. return hr;
  357. }