ReactOS  0.4.15-dev-1171-gab82533
CUserNotification.cpp
Go to the documentation of this file.
1 /*
2  * Copyright 2018 Hermes Belusca-Maito
3  *
4  * Pass on icon notification messages to the systray implementation
5  * in the currently running shell.
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20  */
21 
22 #include "precomp.h"
23 
24 #include <mmsystem.h>
25 #undef PlaySound
26 
27 WINE_DEFAULT_DEBUG_CHANNEL(shell_notify);
28 
29 
30 /* Use Windows-compatible window callback message */
31 #define WM_TRAYNOTIFY (WM_USER + 100)
32 
33 /* Notification icon ID */
34 #define ID_NOTIFY_ICON 0
35 
36 /* Balloon timers */
37 #define ID_BALLOON_TIMEOUT 1
38 #define ID_BALLOON_DELAYREMOVE 2
39 #define ID_BALLOON_QUERYCONT 3
40 #define ID_BALLOON_SHOWTIME 4
41 
42 #define BALLOON_DELAYREMOVE_TIMEOUT 250 // milliseconds
43 
44 
46  m_hWorkerWnd(NULL),
47  m_hIcon(NULL),
48  m_dwInfoFlags(0),
49  m_uShowTime(15000),
50  m_uInterval(10000),
51  m_cRetryCount(-1),
52  m_uContinuePoolInterval(0),
53  m_bIsShown(FALSE),
54  m_hRes(S_OK),
55  m_pqc(NULL)
56 {
57 }
58 
60 {
61  /* If we have a notification window... */
62  if (m_hWorkerWnd)
63  {
64  /* ... remove the notification icon and destroy the window */
65  RemoveIcon();
68  }
69 
70  /* Destroy our local icon copy */
71  if (m_hIcon)
73 }
74 
76 {
77  NOTIFYICONDATAW nid = {0};
78 
79  nid.cbSize = NOTIFYICONDATAW_V3_SIZE; // sizeof(nid);
82 
83  /* Remove the notification icon */
85 }
86 
88 {
89  /* Set the return value for CUserNotification::Show() and defer icon removal */
90  m_hRes = hRes;
93 }
94 
96 {
97  /*
98  * The balloon timed out, we need to wait before showing it again.
99  * If we retried too many times, delete the notification icon.
100  */
101  if (m_cRetryCount > 0)
102  {
103  /* Decrement the retry count */
104  --m_cRetryCount;
105 
106  /* Set the timeout interval timer */
108  }
109  else
110  {
111  /* No other retry: delete the notification icon */
113  }
114 }
115 
117  IN UINT uFlags,
118  IN OUT PNOTIFYICONDATAW pnid)
119 {
120  pnid->cbSize = NOTIFYICONDATAW_V3_SIZE; // sizeof(nid);
121  pnid->hWnd = m_hWorkerWnd;
122  pnid->uID = ID_NOTIFY_ICON;
123  // pnid->uVersion = NOTIFYICON_VERSION;
124 
125  if (uFlags & NIF_MESSAGE)
126  {
127  pnid->uFlags |= NIF_MESSAGE;
128  pnid->uCallbackMessage = WM_TRAYNOTIFY;
129  }
130 
131  if (uFlags & NIF_ICON)
132  {
133  pnid->uFlags |= NIF_ICON;
134  /* Use a default icon if we do not have one already */
135  pnid->hIcon = (m_hIcon ? m_hIcon : LoadIcon(NULL, IDI_WINLOGO));
136  }
137 
138  if (uFlags & NIF_TIP)
139  {
140  pnid->uFlags |= NIF_TIP;
141  ::StringCchCopyW(pnid->szTip, _countof(pnid->szTip), m_szTip);
142  }
143 
144  if (uFlags & NIF_INFO)
145  {
146  pnid->uFlags |= NIF_INFO;
147 
148  // pnid->uTimeout = m_uShowTime; // NOTE: Deprecated
149  pnid->dwInfoFlags = m_dwInfoFlags;
150 
151  ::StringCchCopyW(pnid->szInfo, _countof(pnid->szInfo), m_szInfo);
152  ::StringCchCopyW(pnid->szInfoTitle, _countof(pnid->szInfoTitle), m_szInfoTitle);
153  }
154 }
155 
156 
157 /* IUserNotification Implementation */
158 
161  IN LPCWSTR pszTitle,
162  IN LPCWSTR pszText,
163  IN DWORD dwInfoFlags)
164 {
165  NOTIFYICONDATAW nid = {0};
166 
167  m_szInfo = pszText;
168  m_szInfoTitle = pszTitle;
169  m_dwInfoFlags = dwInfoFlags;
170 
171  /* Update the notification icon if we have one */
172  if (!m_hWorkerWnd)
173  return S_OK;
174 
175  /* Modify the notification icon */
176  SetUpNotifyData(NIF_INFO, &nid);
178  return S_OK;
179  else
180  return E_FAIL;
181 }
182 
185  IN DWORD dwShowTime, // Time intervals in milliseconds
186  IN DWORD dwInterval,
187  IN UINT cRetryCount)
188 {
189  m_uShowTime = dwShowTime;
190  m_uInterval = dwInterval;
191  m_cRetryCount = cRetryCount;
192  return S_OK;
193 }
194 
197  IN HICON hIcon,
198  IN LPCWSTR pszToolTip)
199 {
200  NOTIFYICONDATAW nid = {0};
201 
202  /* Destroy our local icon copy */
203  if (m_hIcon)
205 
206  if (hIcon)
207  {
208  /* Copy the icon from the user */
210  }
211  else
212  {
213  /* Use the same icon as the one for the balloon if specified */
214  UINT uIcon = (m_dwInfoFlags & NIIF_ICON_MASK);
215  LPCWSTR pIcon = NULL;
216 
217  if (uIcon == NIIF_INFO)
218  pIcon = IDI_INFORMATION;
219  else if (uIcon == NIIF_WARNING)
220  pIcon = IDI_WARNING;
221  else if (uIcon == NIIF_ERROR)
222  pIcon = IDI_ERROR;
223  else if (uIcon == NIIF_USER)
224  pIcon = NULL;
225 
226  m_hIcon = (pIcon ? ::LoadIconW(NULL, pIcon) : NULL);
227  }
228 
229  m_szTip = pszToolTip;
230 
231  /* Update the notification icon if we have one */
232  if (!m_hWorkerWnd)
233  return S_OK;
234 
235  /* Modify the notification icon */
238  return S_OK;
239  else
240  return E_FAIL;
241 }
242 
243 
246  IN HWND hWnd,
247  IN UINT uMsg,
248  IN WPARAM wParam,
249  IN LPARAM lParam)
250 {
251  /* Retrieve the current user notification object stored in the window extra bits */
252  CUserNotification* pThis = reinterpret_cast<CUserNotification*>(::GetWindowLongPtrW(hWnd, 0));
253  ASSERT(pThis);
254  ASSERT(hWnd == pThis->m_hWorkerWnd);
255 
256  TRACE("Msg = 0x%x\n", uMsg);
257  switch (uMsg)
258  {
259  /*
260  * We do not receive any WM_(NC)CREATE message since worker windows
261  * are first created using the default window procedure DefWindowProcW.
262  * The window procedure is changed only subsequently to the user one.
263  * We however receive WM_(NC)DESTROY messages.
264  */
265  case WM_DESTROY:
266  {
267  /* Post a WM_QUIT message only if the Show() method's message loop is running */
268  if (pThis->m_bIsShown)
270  return 0;
271  }
272 
273  case WM_NCDESTROY:
274  {
276  pThis->m_hWorkerWnd = NULL;
277  return 0;
278  }
279 
280  case WM_QUERYENDSESSION:
281  {
282  /*
283  * User session is ending or a shutdown is occurring: perform cleanup.
284  * Set the return value for CUserNotification::Show() and remove the notification.
285  */
287  pThis->RemoveIcon();
289  return TRUE;
290  }
291 
292  case WM_TIMER:
293  {
294  TRACE("WM_TIMER(0x%lx)\n", wParam);
295 
296  /* Destroy the associated timer */
298 
299  if (wParam == ID_BALLOON_TIMEOUT)
300  {
301  /* Timeout interval timer expired: display the balloon again */
302  NOTIFYICONDATAW nid = {0};
303  pThis->SetUpNotifyData(NIF_INFO, &nid);
305  }
306  else if (wParam == ID_BALLOON_DELAYREMOVE)
307  {
308  /* Delay-remove timer expired: remove the notification */
309  pThis->RemoveIcon();
311  }
312  else if (wParam == ID_BALLOON_QUERYCONT)
313  {
314  /*
315  * Query-continue timer expired: ask the user whether the
316  * notification should continue to be displayed or not.
317  */
318  if (pThis->m_pqc && pThis->m_pqc->QueryContinue() == S_OK)
319  {
320  /* The notification can be displayed */
322  }
323  else
324  {
325  /* The notification should be removed */
326  pThis->DelayRemoveIcon(S_FALSE);
327  }
328  }
329  else if (wParam == ID_BALLOON_SHOWTIME)
330  {
331  /* Show-time timer expired: wait before showing the balloon again */
332  pThis->TimeoutIcon();
333  }
334  return 0;
335  }
336 
337  /*
338  * Shell User Notification message.
339  * We use NOTIFYICON_VERSION == 0 or 3 callback version, with:
340  * wParam == identifier of the taskbar icon in which the event occurred;
341  * lParam == holds the mouse or keyboard message associated with the event.
342  */
343  case WM_TRAYNOTIFY:
344  {
345  TRACE("WM_TRAYNOTIFY - wParam = 0x%lx ; lParam = 0x%lx\n", wParam, lParam);
347 
348  switch (lParam)
349  {
350  case NIN_BALLOONSHOW:
351  TRACE("NIN_BALLOONSHOW\n");
352  break;
353 
354  case NIN_BALLOONHIDE:
355  TRACE("NIN_BALLOONHIDE\n");
356  break;
357 
358  /* The balloon timed out, or the user closed it by clicking on the 'X' button */
359  case NIN_BALLOONTIMEOUT:
360  {
361  TRACE("NIN_BALLOONTIMEOUT\n");
362  pThis->TimeoutIcon();
363  break;
364  }
365 
366  /* The user clicked on the balloon: delete the notification icon */
367  case NIN_BALLOONUSERCLICK:
368  TRACE("NIN_BALLOONUSERCLICK\n");
369  /* Fall back to icon click behaviour */
370 
371  /* The user clicked on the notification icon: delete it */
372  case WM_LBUTTONDOWN:
373  case WM_RBUTTONDOWN:
374  {
375  pThis->DelayRemoveIcon(S_OK);
376  break;
377  }
378 
379  default:
380  break;
381  }
382 
383  return 0;
384  }
385  }
386 
388 }
389 
390 
391 // Blocks until the notification times out.
394  IN IQueryContinue* pqc,
395  IN DWORD dwContinuePollInterval)
396 {
397  NOTIFYICONDATAW nid = {0};
398  MSG msg;
399 
400  /* Create the hidden notification message worker window if we do not have one already */
401  if (!m_hWorkerWnd)
402  {
404  NULL, 0, 0, NULL, (LONG_PTR)this);
405  if (!m_hWorkerWnd)
406  {
408  return E_FAIL;
409  }
410 
411  /* Add and display the notification icon */
412  SetUpNotifyData(NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_INFO, &nid);
413  if (!::Shell_NotifyIconW(NIM_ADD, &nid))
414  {
416  m_hWorkerWnd = NULL;
417  return E_FAIL;
418  }
419  }
420 
421  m_hRes = S_OK;
422 
423  /* Set up the user-continue callback mechanism */
424  m_pqc = pqc;
425  if (pqc)
426  {
427  m_uContinuePoolInterval = dwContinuePollInterval;
429  }
430 
431  /* Control how long the balloon notification is displayed */
432  if ((nid.uFlags & NIF_INFO) && !*nid.szInfo /* && !*nid.szInfoTitle */)
434 
435  /* Dispatch messsages to the worker window */
436  m_bIsShown = TRUE;
437  while (::GetMessageW(&msg, NULL, 0, 0))
438  {
441  }
442  m_bIsShown = FALSE;
443 
444  /* Reset the user-continue callback mechanism */
445  if (pqc)
446  {
449  }
450  m_pqc = NULL;
451 
452  /* Return the notification error code */
453  return m_hRes;
454 }
455 
456 #if 0 // IUserNotification2
457 // Blocks until the notification times out.
460  IN IQueryContinue* pqc,
461  IN DWORD dwContinuePollInterval,
462  IN IUserNotificationCallback* pSink)
463 {
464  return S_OK;
465 }
466 #endif
467 
470  IN LPCWSTR pszSoundName)
471 {
472  /* Call the Win32 API - Ignore the PlaySoundW() return value as on Windows */
473  ::PlaySoundW(pszSoundName,
474  NULL,
477  return S_OK;
478 }
VOID DelayRemoveIcon(IN HRESULT hRes)
#define IN
Definition: typedefs.h:39
#define NIF_MESSAGE
Definition: shellapi.h:102
BOOL WINAPI TranslateMessage(_In_ const MSG *)
static HICON
Definition: imagelist.c:84
#define HRESULT_FROM_WIN32(x)
Definition: winerror.h:92
#define WM_LBUTTONDOWN
Definition: winuser.h:1758
const WCHAR * LPCWSTR
Definition: xmlstorage.h:185
NOTIFYICONDATA nid
Definition: magnifier.c:44
BOOL WINAPI DestroyIcon(_In_ HICON)
Definition: cursoricon.c:2022
#define TRUE
Definition: types.h:120
#define SND_NODEFAULT
Definition: mmsystem.h:155
TW_UINT32 TW_UINT16 TW_UINT16 MSG
Definition: twain.h:1827
#define WM_QUERYENDSESSION
Definition: winuser.h:1604
#define CALLBACK
Definition: compat.h:35
HWND hWnd
Definition: settings.c:17
#define IDI_INFORMATION
Definition: winuser.h:715
UINT_PTR WPARAM
Definition: windef.h:207
#define GetWindowLongPtrW
Definition: winuser.h:4804
UINT uFlags
Definition: api.c:59
#define SND_APPLICATION
Definition: mmsystem.h:165
#define IDI_WARNING
Definition: winuser.h:713
#define ID_BALLOON_QUERYCONT
#define E_FAIL
Definition: ddrawi.h:102
BOOL WINAPI DestroyWindow(_In_ HWND)
static LRESULT CALLBACK WorkerWndProc(IN HWND hWnd, IN UINT uMsg, IN WPARAM wParam, IN LPARAM lParam)
WPARAM wParam
Definition: combotst.c:138
VOID SetUpNotifyData(IN UINT uFlags, IN OUT PNOTIFYICONDATAW pnid)
LRESULT WINAPI DefWindowProcW(_In_ HWND, _In_ UINT, _In_ WPARAM, _In_ LPARAM)
#define IDI_ERROR
Definition: winuser.h:714
virtual HRESULT STDMETHODCALLTYPE SetIconInfo(IN HICON hIcon, IN LPCWSTR pszToolTip)
#define ID_NOTIFY_ICON
virtual HRESULT STDMETHODCALLTYPE SetBalloonRetry(IN DWORD dwShowTime, IN DWORD dwInterval, IN UINT cRetryCount)
#define FALSE
Definition: types.h:117
LRESULT WINAPI DispatchMessageW(_In_ const MSG *)
WINE_DEFAULT_DEBUG_CHANNEL(shell_notify)
#define NOTIFYICONDATAW_V3_SIZE
Definition: shellapi.h:289
virtual HRESULT STDMETHODCALLTYPE SetBalloonInfo(IN LPCWSTR pszTitle, IN LPCWSTR pszText, IN DWORD dwInfoFlags)
STRSAFEAPI StringCchCopyW(STRSAFE_LPWSTR pszDest, size_t cchDest, STRSAFE_LPCWSTR pszSrc)
Definition: strsafe.h:149
#define SND_NOSTOP
Definition: mmsystem.h:158
virtual HRESULT STDMETHODCALLTYPE PlaySound(IN LPCWSTR pszSoundName)
#define S_FALSE
Definition: winerror.h:2357
#define NIF_TIP
Definition: shellapi.h:104
smooth NULL
Definition: ftsmooth.c:416
#define SND_ALIAS
Definition: mmsystem.h:160
LONG_PTR LPARAM
Definition: windef.h:208
#define NIF_ICON
Definition: shellapi.h:103
UINT_PTR WINAPI SetTimer(_In_opt_ HWND, _In_ UINT_PTR, _In_ UINT, _In_opt_ TIMERPROC)
#define WM_DESTROY
Definition: winuser.h:1591
#define WM_RBUTTONDOWN
Definition: winuser.h:1761
#define TRACE(s)
Definition: solgame.cpp:4
LONG HRESULT
Definition: typedefs.h:79
#define _countof(array)
Definition: sndvol32.h:68
#define WM_TIMER
Definition: winuser.h:1724
#define FAILED_UNEXPECTEDLY(hr)
Definition: shellutils.h:71
#define WM_NCDESTROY
Definition: winuser.h:1666
#define IDI_WINLOGO
Definition: winuser.h:704
#define STDMETHODCALLTYPE
Definition: bdasup.h:9
unsigned long DWORD
Definition: ntddk_ex.h:95
#define NIM_DELETE
Definition: shellapi.h:93
#define SND_ASYNC
Definition: mmsystem.h:154
unsigned __int3264 UINT_PTR
Definition: mstsclib_h.h:274
ASSERT((InvokeOnSuccess||InvokeOnError||InvokeOnCancel) ?(CompletionRoutine !=NULL) :TRUE)
BOOL WINAPI KillTimer(_In_opt_ HWND, _In_ UINT_PTR)
virtual HRESULT STDMETHODCALLTYPE Show(IN IQueryContinue *pqc, IN DWORD dwContinuePollInterval)
#define ID_BALLOON_DELAYREMOVE
#define BALLOON_DELAYREMOVE_TIMEOUT
IQueryContinue * m_pqc
HICON WINAPI CopyIcon(_In_ HICON)
Definition: cursoricon.c:1980
#define S_OK
Definition: intsafe.h:51
#define ERROR_CANCELLED
Definition: winerror.h:726
HICON hIcon
Definition: msconfig.c:44
#define ID_BALLOON_SHOWTIME
__int3264 LONG_PTR
Definition: mstsclib_h.h:276
unsigned int UINT
Definition: ndis.h:50
BOOL WINAPI GetMessageW(_Out_ LPMSG, _In_opt_ HWND, _In_ UINT, _In_ UINT)
CHAR szInfo[256]
Definition: shellapi.h:238
#define NIM_ADD
Definition: shellapi.h:91
#define msg(x)
Definition: auth_time.c:54
#define OUT
Definition: typedefs.h:40
HICON WINAPI LoadIconW(_In_opt_ HINSTANCE, _In_ LPCWSTR)
Definition: cursoricon.c:2044
HWND WINAPI SHCreateWorkerWindowW(WNDPROC wndProc, HWND hWndParent, DWORD dwExStyle, DWORD dwStyle, HMENU hMenu, LONG_PTR wnd_extra)
Definition: ordinal.c:2918
#define SetWindowLongPtrW
Definition: winuser.h:5321
#define LoadIcon
Definition: winuser.h:5788
LONG_PTR LRESULT
Definition: windef.h:209
#define ID_BALLOON_TIMEOUT
LPARAM lParam
Definition: combotst.c:139
__analysis_noreturn void WINAPI PostQuitMessage(_In_ int)
BOOL WINAPI Shell_NotifyIconW(DWORD dwMessage, PNOTIFYICONDATAW pnid)
Definition: systray.cpp:128
#define WM_TRAYNOTIFY
#define NIM_MODIFY
Definition: shellapi.h:92
BOOL WINAPI PlaySoundW(LPCWSTR pszSoundW, HMODULE hmod, DWORD fdwSound)
Definition: playsound.c:531