Something went wrong. Try again.
Reactos
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479/* * Copyright 2018 Hermes Belusca-Maito * * Pass on icon notification messages to the systray implementation * in the currently running shell. * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */
#include "precomp.h"
#include <mmsystem.h>#undef PlaySound
WINE_DEFAULT_DEBUG_CHANNEL(shell_notify);
/* Use Windows-compatible window callback message */#define WM_TRAYNOTIFY (WM_USER + 100)
/* Notification icon ID */#define ID_NOTIFY_ICON 0
/* Balloon timers */#define ID_BALLOON_TIMEOUT 1#define ID_BALLOON_DELAYREMOVE 2#define ID_BALLOON_QUERYCONT 3#define ID_BALLOON_SHOWTIME 4
#define BALLOON_DELAYREMOVE_TIMEOUT 250 // milliseconds
CUserNotification::CUserNotification() : m_hWorkerWnd(NULL), m_hIcon(NULL), m_dwInfoFlags(0), m_uShowTime(15000), m_uInterval(10000), m_cRetryCount(-1), m_uContinuePoolInterval(0), m_bIsShown(FALSE), m_hRes(S_OK), m_pqc(NULL){}
CUserNotification::~CUserNotification(){ /* If we have a notification window... */ if (m_hWorkerWnd) { /* ... remove the notification icon and destroy the window */ RemoveIcon(); ::DestroyWindow(m_hWorkerWnd); m_hWorkerWnd = NULL; }
/* Destroy our local icon copy */ if (m_hIcon) ::DestroyIcon(m_hIcon);}
VOID CUserNotification::RemoveIcon(){ NOTIFYICONDATAW nid = {0};
nid.cbSize = NOTIFYICONDATAW_V3_SIZE; // sizeof(nid); nid.hWnd = m_hWorkerWnd; nid.uID = ID_NOTIFY_ICON;
/* Remove the notification icon */ ::Shell_NotifyIconW(NIM_DELETE, &nid);}
VOID CUserNotification::DelayRemoveIcon(IN HRESULT hRes){ /* Set the return value for CUserNotification::Show() and defer icon removal */ m_hRes = hRes; ::SetTimer(m_hWorkerWnd, ID_BALLOON_DELAYREMOVE, BALLOON_DELAYREMOVE_TIMEOUT, NULL);}
VOID CUserNotification::TimeoutIcon(){ /* * The balloon timed out, we need to wait before showing it again. * If we retried too many times, delete the notification icon. */ if (m_cRetryCount > 0) { /* Decrement the retry count */ --m_cRetryCount;
/* Set the timeout interval timer */ ::SetTimer(m_hWorkerWnd, ID_BALLOON_TIMEOUT, m_uInterval, NULL); } else { /* No other retry: delete the notification icon */ DelayRemoveIcon(HRESULT_FROM_WIN32(ERROR_CANCELLED)); }}
VOID CUserNotification::SetUpNotifyData( IN UINT uFlags, IN OUT PNOTIFYICONDATAW pnid){ pnid->cbSize = NOTIFYICONDATAW_V3_SIZE; // sizeof(nid); pnid->hWnd = m_hWorkerWnd; pnid->uID = ID_NOTIFY_ICON; // pnid->uVersion = NOTIFYICON_VERSION;
if (uFlags & NIF_MESSAGE) { pnid->uFlags |= NIF_MESSAGE; pnid->uCallbackMessage = WM_TRAYNOTIFY; }
if (uFlags & NIF_ICON) { pnid->uFlags |= NIF_ICON; /* Use a default icon if we do not have one already */ pnid->hIcon = (m_hIcon ? m_hIcon : LoadIcon(NULL, IDI_WINLOGO)); }
if (uFlags & NIF_TIP) { pnid->uFlags |= NIF_TIP; ::StringCchCopyW(pnid->szTip, _countof(pnid->szTip), m_szTip); }
if (uFlags & NIF_INFO) { pnid->uFlags |= NIF_INFO;
// pnid->uTimeout = m_uShowTime; // NOTE: Deprecated pnid->dwInfoFlags = m_dwInfoFlags;
::StringCchCopyW(pnid->szInfo, _countof(pnid->szInfo), m_szInfo); ::StringCchCopyW(pnid->szInfoTitle, _countof(pnid->szInfoTitle), m_szInfoTitle); }}
/* IUserNotification Implementation */
HRESULT STDMETHODCALLTYPECUserNotification::SetBalloonInfo( IN LPCWSTR pszTitle, IN LPCWSTR pszText, IN DWORD dwInfoFlags){ NOTIFYICONDATAW nid = {0};
m_szInfo = pszText; m_szInfoTitle = pszTitle; m_dwInfoFlags = dwInfoFlags;
/* Update the notification icon if we have one */ if (!m_hWorkerWnd) return S_OK;
/* Modify the notification icon */ SetUpNotifyData(NIF_INFO, &nid); if (::Shell_NotifyIconW(NIM_MODIFY, &nid)) return S_OK; else return E_FAIL;}
HRESULT STDMETHODCALLTYPECUserNotification::SetBalloonRetry( IN DWORD dwShowTime, // Time intervals in milliseconds IN DWORD dwInterval, IN UINT cRetryCount){ m_uShowTime = dwShowTime; m_uInterval = dwInterval; m_cRetryCount = cRetryCount; return S_OK;}
HRESULT STDMETHODCALLTYPECUserNotification::SetIconInfo( IN HICON hIcon, IN LPCWSTR pszToolTip){ NOTIFYICONDATAW nid = {0};
/* Destroy our local icon copy */ if (m_hIcon) ::DestroyIcon(m_hIcon);
if (hIcon) { /* Copy the icon from the user */ m_hIcon = ::CopyIcon(hIcon); } else { /* Use the same icon as the one for the balloon if specified */ UINT uIcon = (m_dwInfoFlags & NIIF_ICON_MASK); LPCWSTR pIcon = NULL;
if (uIcon == NIIF_INFO) pIcon = IDI_INFORMATION; else if (uIcon == NIIF_WARNING) pIcon = IDI_WARNING; else if (uIcon == NIIF_ERROR) pIcon = IDI_ERROR; else if (uIcon == NIIF_USER) pIcon = NULL;
m_hIcon = (pIcon ? ::LoadIconW(NULL, pIcon) : NULL); }
m_szTip = pszToolTip;
/* Update the notification icon if we have one */ if (!m_hWorkerWnd) return S_OK;
/* Modify the notification icon */ SetUpNotifyData(NIF_ICON | NIF_TIP, &nid); if (::Shell_NotifyIconW(NIM_MODIFY, &nid)) return S_OK; else return E_FAIL;}
LRESULT CALLBACKCUserNotification::WorkerWndProc( IN HWND hWnd, IN UINT uMsg, IN WPARAM wParam, IN LPARAM lParam){ /* Retrieve the current user notification object stored in the window extra bits */ CUserNotification* pThis = reinterpret_cast<CUserNotification*>(::GetWindowLongPtrW(hWnd, 0)); ASSERT(pThis); ASSERT(hWnd == pThis->m_hWorkerWnd);
TRACE("Msg = 0x%x\n", uMsg); switch (uMsg) { /* * We do not receive any WM_(NC)CREATE message since worker windows * are first created using the default window procedure DefWindowProcW. * The window procedure is changed only subsequently to the user one. * We however receive WM_(NC)DESTROY messages. */ case WM_DESTROY: { /* Post a WM_QUIT message only if the Show() method's message loop is running */ if (pThis->m_bIsShown) ::PostQuitMessage(0); return 0; }
case WM_NCDESTROY: { ::SetWindowLongPtrW(hWnd, 0, (LONG_PTR)NULL); pThis->m_hWorkerWnd = NULL; return 0; }
case WM_QUERYENDSESSION: { /* * User session is ending or a shutdown is occurring: perform cleanup. * Set the return value for CUserNotification::Show() and remove the notification. */ pThis->m_hRes = HRESULT_FROM_WIN32(ERROR_CANCELLED); pThis->RemoveIcon(); ::DestroyWindow(pThis->m_hWorkerWnd); return TRUE; }
case WM_TIMER: { TRACE("WM_TIMER(0x%lx)\n", wParam);
/* Destroy the associated timer */ ::KillTimer(hWnd, (UINT_PTR)wParam);
if (wParam == ID_BALLOON_TIMEOUT) { /* Timeout interval timer expired: display the balloon again */ NOTIFYICONDATAW nid = {0}; pThis->SetUpNotifyData(NIF_INFO, &nid); ::Shell_NotifyIconW(NIM_MODIFY, &nid); } else if (wParam == ID_BALLOON_DELAYREMOVE) { /* Delay-remove timer expired: remove the notification */ pThis->RemoveIcon(); ::DestroyWindow(pThis->m_hWorkerWnd); } else if (wParam == ID_BALLOON_QUERYCONT) { /* * Query-continue timer expired: ask the user whether the * notification should continue to be displayed or not. */ if (pThis->m_pqc && pThis->m_pqc->QueryContinue() == S_OK) { /* The notification can be displayed */ ::SetTimer(hWnd, ID_BALLOON_QUERYCONT, pThis->m_uContinuePoolInterval, NULL); } else { /* The notification should be removed */ pThis->DelayRemoveIcon(S_FALSE); } } else if (wParam == ID_BALLOON_SHOWTIME) { /* Show-time timer expired: wait before showing the balloon again */ pThis->TimeoutIcon(); } return 0; }
/* * Shell User Notification message. * We use NOTIFYICON_VERSION == 0 or 3 callback version, with: * wParam == identifier of the taskbar icon in which the event occurred; * lParam == holds the mouse or keyboard message associated with the event. */ case WM_TRAYNOTIFY: { TRACE("WM_TRAYNOTIFY - wParam = 0x%lx ; lParam = 0x%lx\n", wParam, lParam); ASSERT(wParam == ID_NOTIFY_ICON);
switch (lParam) { case NIN_BALLOONSHOW: TRACE("NIN_BALLOONSHOW\n"); break;
case NIN_BALLOONHIDE: TRACE("NIN_BALLOONHIDE\n"); break;
/* The balloon timed out, or the user closed it by clicking on the 'X' button */ case NIN_BALLOONTIMEOUT: { TRACE("NIN_BALLOONTIMEOUT\n"); pThis->TimeoutIcon(); break; }
/* The user clicked on the balloon: delete the notification icon */ case NIN_BALLOONUSERCLICK: TRACE("NIN_BALLOONUSERCLICK\n"); /* Fall back to icon click behaviour */
/* The user clicked on the notification icon: delete it */ case WM_LBUTTONDOWN: case WM_RBUTTONDOWN: { pThis->DelayRemoveIcon(S_OK); break; }
default: break; }
return 0; } }
return ::DefWindowProcW(hWnd, uMsg, wParam, lParam);}
// Blocks until the notification times out.HRESULT STDMETHODCALLTYPECUserNotification::Show( IN IQueryContinue* pqc, IN DWORD dwContinuePollInterval){ NOTIFYICONDATAW nid = {0}; MSG msg;
/* Create the hidden notification message worker window if we do not have one already */ if (!m_hWorkerWnd) { m_hWorkerWnd = ::SHCreateWorkerWindowW(CUserNotification::WorkerWndProc, NULL, 0, 0, NULL, (LONG_PTR)this); if (!m_hWorkerWnd) { FAILED_UNEXPECTEDLY(E_FAIL); return E_FAIL; }
/* Add and display the notification icon */ SetUpNotifyData(NIF_MESSAGE | NIF_ICON | NIF_TIP | NIF_INFO, &nid); if (!::Shell_NotifyIconW(NIM_ADD, &nid)) { ::DestroyWindow(m_hWorkerWnd); m_hWorkerWnd = NULL; return E_FAIL; } }
m_hRes = S_OK;
/* Set up the user-continue callback mechanism */ m_pqc = pqc; if (pqc) { m_uContinuePoolInterval = dwContinuePollInterval; ::SetTimer(m_hWorkerWnd, ID_BALLOON_QUERYCONT, m_uContinuePoolInterval, NULL); }
/* Control how long the balloon notification is displayed */ if ((nid.uFlags & NIF_INFO) && !*nid.szInfo /* && !*nid.szInfoTitle */) ::SetTimer(m_hWorkerWnd, ID_BALLOON_SHOWTIME, m_uShowTime, NULL);
/* Dispatch messsages to the worker window */ m_bIsShown = TRUE; while (::GetMessageW(&msg, NULL, 0, 0)) { ::TranslateMessage(&msg); ::DispatchMessageW(&msg); } m_bIsShown = FALSE;
/* Reset the user-continue callback mechanism */ if (pqc) { ::KillTimer(m_hWorkerWnd, ID_BALLOON_QUERYCONT); m_uContinuePoolInterval = 0; } m_pqc = NULL;
/* Return the notification error code */ return m_hRes;}
#if 0 // IUserNotification2// Blocks until the notification times out.HRESULT STDMETHODCALLTYPECUserNotification::Show( IN IQueryContinue* pqc, IN DWORD dwContinuePollInterval, IN IUserNotificationCallback* pSink){ return S_OK;}#endif
HRESULT STDMETHODCALLTYPECUserNotification::PlaySound( IN LPCWSTR pszSoundName){ /* Call the Win32 API - Ignore the PlaySoundW() return value as on Windows */ ::PlaySoundW(pszSoundName, NULL, SND_ALIAS | SND_APPLICATION | SND_NOSTOP | SND_NODEFAULT | SND_ASYNC); return S_OK;}