2 * Copyright 2018 Hermes Belusca-Maito
4 * Pass on icon notification messages to the systray implementation
5 * in the currently running shell.
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.
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.
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
27 WINE_DEFAULT_DEBUG_CHANNEL(shell_notify
);
30 /* Use Windows-compatible window callback message */
31 #define WM_TRAYNOTIFY (WM_USER + 100)
33 /* Notification icon ID */
34 #define ID_NOTIFY_ICON 0
37 #define ID_BALLOON_TIMEOUT 1
38 #define ID_BALLOON_DELAYREMOVE 2
39 #define ID_BALLOON_QUERYCONT 3
40 #define ID_BALLOON_SHOWTIME 4
42 #define BALLOON_DELAYREMOVE_TIMEOUT 250 // milliseconds
45 CUserNotification::CUserNotification() :
52 m_uContinuePoolInterval(0),
59 CUserNotification::~CUserNotification()
61 /* If we have a notification window... */
64 /* ... remove the notification icon and destroy the window */
66 ::DestroyWindow(m_hWorkerWnd
);
70 /* Destroy our local icon copy */
72 ::DestroyIcon(m_hIcon
);
75 VOID
CUserNotification::RemoveIcon()
77 NOTIFYICONDATAW nid
= {0};
79 nid
.cbSize
= NOTIFYICONDATAW_V3_SIZE
; // sizeof(nid);
80 nid
.hWnd
= m_hWorkerWnd
;
81 nid
.uID
= ID_NOTIFY_ICON
;
83 /* Remove the notification icon */
84 ::Shell_NotifyIconW(NIM_DELETE
, &nid
);
87 VOID
CUserNotification::DelayRemoveIcon(IN HRESULT hRes
)
89 /* Set the return value for CUserNotification::Show() and defer icon removal */
91 ::SetTimer(m_hWorkerWnd
, ID_BALLOON_DELAYREMOVE
,
92 BALLOON_DELAYREMOVE_TIMEOUT
, NULL
);
95 VOID
CUserNotification::TimeoutIcon()
98 * The balloon timed out, we need to wait before showing it again.
99 * If we retried too many times, delete the notification icon.
101 if (m_cRetryCount
> 0)
103 /* Decrement the retry count */
106 /* Set the timeout interval timer */
107 ::SetTimer(m_hWorkerWnd
, ID_BALLOON_TIMEOUT
, m_uInterval
, NULL
);
111 /* No other retry: delete the notification icon */
112 DelayRemoveIcon(HRESULT_FROM_WIN32(ERROR_CANCELLED
));
116 VOID
CUserNotification::SetUpNotifyData(
118 IN OUT PNOTIFYICONDATAW pnid
)
120 pnid
->cbSize
= NOTIFYICONDATAW_V3_SIZE
; // sizeof(nid);
121 pnid
->hWnd
= m_hWorkerWnd
;
122 pnid
->uID
= ID_NOTIFY_ICON
;
123 // pnid->uVersion = NOTIFYICON_VERSION;
125 if (uFlags
& NIF_MESSAGE
)
127 pnid
->uFlags
|= NIF_MESSAGE
;
128 pnid
->uCallbackMessage
= WM_TRAYNOTIFY
;
131 if (uFlags
& NIF_ICON
)
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
));
138 if (uFlags
& NIF_TIP
)
140 pnid
->uFlags
|= NIF_TIP
;
141 ::StringCchCopyW(pnid
->szTip
, _countof(pnid
->szTip
), m_szTip
);
144 if (uFlags
& NIF_INFO
)
146 pnid
->uFlags
|= NIF_INFO
;
148 // pnid->uTimeout = m_uShowTime; // NOTE: Deprecated
149 pnid
->dwInfoFlags
= m_dwInfoFlags
;
151 ::StringCchCopyW(pnid
->szInfo
, _countof(pnid
->szInfo
), m_szInfo
);
152 ::StringCchCopyW(pnid
->szInfoTitle
, _countof(pnid
->szInfoTitle
), m_szInfoTitle
);
157 /* IUserNotification Implementation */
159 HRESULT STDMETHODCALLTYPE
160 CUserNotification::SetBalloonInfo(
163 IN DWORD dwInfoFlags
)
165 NOTIFYICONDATAW nid
= {0};
168 m_szInfoTitle
= pszTitle
;
169 m_dwInfoFlags
= dwInfoFlags
;
171 /* Update the notification icon if we have one */
175 /* Modify the notification icon */
176 SetUpNotifyData(NIF_INFO
, &nid
);
177 if (::Shell_NotifyIconW(NIM_MODIFY
, &nid
))
183 HRESULT STDMETHODCALLTYPE
184 CUserNotification::SetBalloonRetry(
185 IN DWORD dwShowTime
, // Time intervals in milliseconds
189 m_uShowTime
= dwShowTime
;
190 m_uInterval
= dwInterval
;
191 m_cRetryCount
= cRetryCount
;
195 HRESULT STDMETHODCALLTYPE
196 CUserNotification::SetIconInfo(
198 IN LPCWSTR pszToolTip
)
200 NOTIFYICONDATAW nid
= {0};
202 /* Destroy our local icon copy */
204 ::DestroyIcon(m_hIcon
);
208 /* Copy the icon from the user */
209 m_hIcon
= ::CopyIcon(hIcon
);
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
;
217 if (uIcon
== NIIF_INFO
)
218 pIcon
= IDI_INFORMATION
;
219 else if (uIcon
== NIIF_WARNING
)
221 else if (uIcon
== NIIF_ERROR
)
223 else if (uIcon
== NIIF_USER
)
226 m_hIcon
= (pIcon
? ::LoadIconW(NULL
, pIcon
) : NULL
);
229 m_szTip
= pszToolTip
;
231 /* Update the notification icon if we have one */
235 /* Modify the notification icon */
236 SetUpNotifyData(NIF_ICON
| NIF_TIP
, &nid
);
237 if (::Shell_NotifyIconW(NIM_MODIFY
, &nid
))
245 CUserNotification::WorkerWndProc(
251 /* Retrieve the current user notification object stored in the window extra bits */
252 CUserNotification
* pThis
= reinterpret_cast<CUserNotification
*>(::GetWindowLongPtrW(hWnd
, 0));
254 ASSERT(hWnd
== pThis
->m_hWorkerWnd
);
256 TRACE("Msg = 0x%x\n", uMsg
);
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.
267 /* Post a WM_QUIT message only if the Show() method's message loop is running */
268 if (pThis
->m_bIsShown
)
269 ::PostQuitMessage(0);
275 ::SetWindowLongPtrW(hWnd
, 0, (LONG_PTR
)NULL
);
276 pThis
->m_hWorkerWnd
= NULL
;
280 case WM_QUERYENDSESSION
:
283 * User session is ending or a shutdown is occurring: perform cleanup.
284 * Set the return value for CUserNotification::Show() and remove the notification.
286 pThis
->m_hRes
= HRESULT_FROM_WIN32(ERROR_CANCELLED
);
288 ::DestroyWindow(pThis
->m_hWorkerWnd
);
294 TRACE("WM_TIMER(0x%lx)\n", wParam
);
296 /* Destroy the associated timer */
297 ::KillTimer(hWnd
, (UINT_PTR
)wParam
);
299 if (wParam
== ID_BALLOON_TIMEOUT
)
301 /* Timeout interval timer expired: display the balloon again */
302 NOTIFYICONDATAW nid
= {0};
303 pThis
->SetUpNotifyData(NIF_INFO
, &nid
);
304 ::Shell_NotifyIconW(NIM_MODIFY
, &nid
);
306 else if (wParam
== ID_BALLOON_DELAYREMOVE
)
308 /* Delay-remove timer expired: remove the notification */
310 ::DestroyWindow(pThis
->m_hWorkerWnd
);
312 else if (wParam
== ID_BALLOON_QUERYCONT
)
315 * Query-continue timer expired: ask the user whether the
316 * notification should continue to be displayed or not.
318 if (pThis
->m_pqc
&& pThis
->m_pqc
->QueryContinue() == S_OK
)
320 /* The notification can be displayed */
321 ::SetTimer(hWnd
, ID_BALLOON_QUERYCONT
, pThis
->m_uContinuePoolInterval
, NULL
);
325 /* The notification should be removed */
326 pThis
->DelayRemoveIcon(S_FALSE
);
329 else if (wParam
== ID_BALLOON_SHOWTIME
)
331 /* Show-time timer expired: wait before showing the balloon again */
332 pThis
->TimeoutIcon();
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.
345 TRACE("WM_TRAYNOTIFY - wParam = 0x%lx ; lParam = 0x%lx\n", wParam
, lParam
);
346 ASSERT(wParam
== ID_NOTIFY_ICON
);
350 case NIN_BALLOONSHOW
:
351 TRACE("NIN_BALLOONSHOW\n");
354 case NIN_BALLOONHIDE
:
355 TRACE("NIN_BALLOONHIDE\n");
358 /* The balloon timed out, or the user closed it by clicking on the 'X' button */
359 case NIN_BALLOONTIMEOUT
:
361 TRACE("NIN_BALLOONTIMEOUT\n");
362 pThis
->TimeoutIcon();
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 */
371 /* The user clicked on the notification icon: delete it */
375 pThis
->DelayRemoveIcon(S_OK
);
387 return ::DefWindowProcW(hWnd
, uMsg
, wParam
, lParam
);
391 // Blocks until the notification times out.
392 HRESULT STDMETHODCALLTYPE
393 CUserNotification::Show(
394 IN IQueryContinue
* pqc
,
395 IN DWORD dwContinuePollInterval
)
397 NOTIFYICONDATAW nid
= {0};
400 /* Create the hidden notification message worker window if we do not have one already */
403 m_hWorkerWnd
= ::SHCreateWorkerWindowW(CUserNotification::WorkerWndProc
,
404 NULL
, 0, 0, NULL
, (LONG_PTR
)this);
407 FAILED_UNEXPECTEDLY(E_FAIL
);
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
))
415 ::DestroyWindow(m_hWorkerWnd
);
423 /* Set up the user-continue callback mechanism */
427 m_uContinuePoolInterval
= dwContinuePollInterval
;
428 ::SetTimer(m_hWorkerWnd
, ID_BALLOON_QUERYCONT
, m_uContinuePoolInterval
, NULL
);
431 /* Control how long the balloon notification is displayed */
432 if ((nid
.uFlags
& NIF_INFO
) && !*nid
.szInfo
/* && !*nid.szInfoTitle */)
433 ::SetTimer(m_hWorkerWnd
, ID_BALLOON_SHOWTIME
, m_uShowTime
, NULL
);
435 /* Dispatch messsages to the worker window */
437 while (::GetMessageW(&msg
, NULL
, 0, 0))
439 ::TranslateMessage(&msg
);
440 ::DispatchMessageW(&msg
);
444 /* Reset the user-continue callback mechanism */
447 ::KillTimer(m_hWorkerWnd
, ID_BALLOON_QUERYCONT
);
448 m_uContinuePoolInterval
= 0;
452 /* Return the notification error code */
456 #if 0 // IUserNotification2
457 // Blocks until the notification times out.
458 HRESULT STDMETHODCALLTYPE
459 CUserNotification::Show(
460 IN IQueryContinue
* pqc
,
461 IN DWORD dwContinuePollInterval
,
462 IN IUserNotificationCallback
* pSink
)
468 HRESULT STDMETHODCALLTYPE
469 CUserNotification::PlaySound(
470 IN LPCWSTR pszSoundName
)
472 /* Call the Win32 API - Ignore the PlaySoundW() return value as on Windows */
473 ::PlaySoundW(pszSoundName
,
475 SND_ALIAS
| SND_APPLICATION
|
476 SND_NOSTOP
| SND_NODEFAULT
| SND_ASYNC
);