4 * Copyright 2006 - 2007 Thomas Weidenmueller <w3seek@reactos.org>
5 * Copyright 2018 Ged Murphy <gedmurphy@reactos.org>
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
24 struct InternalIconData
: NOTIFYICONDATA
26 // Must keep a separate copy since the original is unioned with uTimeout.
30 struct IconWatcherData
34 NOTIFYICONDATA IconData
;
36 IconWatcherData(CONST NOTIFYICONDATA
*iconData
) :
37 hProcess(NULL
), ProcessId(0)
39 IconData
.cbSize
= sizeof(NOTIFYICONDATA
);
40 IconData
.hWnd
= iconData
->hWnd
;
41 IconData
.uID
= iconData
->uID
;
42 IconData
.guidItem
= iconData
->guidItem
;
49 CloseHandle(hProcess
);
56 CAtlList
<IconWatcherData
*> m_WatcherList
;
57 CRITICAL_SECTION m_ListLock
;
58 HANDLE m_hWatcherThread
;
66 virtual ~CIconWatcher();
68 bool Initialize(_In_ HWND hWndParent
);
71 bool AddIconToWatcher(_In_ CONST NOTIFYICONDATA
*iconData
);
72 bool RemoveIconFromWatcher(_In_ CONST NOTIFYICONDATA
*iconData
);
74 IconWatcherData
* GetListEntry(_In_opt_ CONST NOTIFYICONDATA
*iconData
, _In_opt_ HANDLE hProcess
, _In_
bool Remove
);
78 static UINT WINAPI
WatcherThread(_In_opt_ LPVOID lpParam
);
86 static const int TimerInterval
= 2000;
87 static const int BalloonsTimerId
= 1;
88 static const int MinTimeout
= 10000;
89 static const int MaxTimeout
= 30000;
90 static const int CooldownBetweenBalloons
= 2000;
95 InternalIconData
* pSource
;
97 WCHAR szInfoTitle
[64];
101 Info(InternalIconData
* source
)
104 StringCchCopy(szInfo
, _countof(szInfo
), source
->szInfo
);
105 StringCchCopy(szInfoTitle
, _countof(szInfoTitle
), source
->szInfoTitle
);
106 uIcon
= source
->dwInfoFlags
& NIIF_ICON_MASK
;
107 if (source
->dwInfoFlags
== NIIF_USER
)
108 uIcon
= reinterpret_cast<WPARAM
>(source
->hIcon
);
109 uTimeout
= source
->uTimeout
;
115 CTooltips
* m_tooltips
;
117 CAtlList
<Info
> m_queue
;
119 CNotifyToolbar
* m_toolbar
;
121 InternalIconData
* m_current
;
122 bool m_currentClosed
;
129 void Init(HWND hwndParent
, CNotifyToolbar
* toolbar
, CTooltips
* balloons
);
132 bool OnTimer(int timerId
);
133 void UpdateInfo(InternalIconData
* notifyItem
);
134 void RemoveInfo(InternalIconData
* notifyItem
);
139 int IndexOf(InternalIconData
* pdata
);
140 void SetTimer(int length
);
141 void Show(Info
& info
);
142 void Close(IN OUT InternalIconData
* notifyItem
, IN UINT uReason
);
145 class CNotifyToolbar
:
146 public CWindowImplBaseT
< CToolbar
<InternalIconData
>, CControlWinTraits
>
148 HIMAGELIST m_ImageList
;
149 int m_VisibleButtonCount
;
151 CBalloonQueue
* m_BalloonQueue
;
155 virtual ~CNotifyToolbar();
157 int GetVisibleButtonCount();
158 int FindItem(IN HWND hWnd
, IN UINT uID
, InternalIconData
** pdata
);
159 int FindExistingSharedIcon(HICON handle
);
160 BOOL
AddButton(IN CONST NOTIFYICONDATA
*iconData
);
161 BOOL
SwitchVersion(IN CONST NOTIFYICONDATA
*iconData
);
162 BOOL
UpdateButton(IN CONST NOTIFYICONDATA
*iconData
);
163 BOOL
RemoveButton(IN CONST NOTIFYICONDATA
*iconData
);
164 VOID
ResizeImagelist();
165 bool SendNotifyCallback(InternalIconData
* notifyItem
, UINT uMsg
);
168 VOID
SendMouseEvent(IN WORD wIndex
, IN UINT uMsg
, IN WPARAM wParam
);
169 LRESULT
OnMouseEvent(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
170 LRESULT
OnTooltipShow(INT uCode
, LPNMHDR hdr
, BOOL
& bHandled
);
173 BEGIN_MSG_MAP(CNotifyToolbar
)
174 MESSAGE_RANGE_HANDLER(WM_MOUSEFIRST
, WM_MOUSELAST
, OnMouseEvent
)
175 NOTIFY_CODE_HANDLER(TTN_SHOW
, OnTooltipShow
)
178 void Initialize(HWND hWndParent
, CBalloonQueue
* queue
);
181 extern const WCHAR szSysPagerWndClass
[];
184 public CComCoClass
<CSysPagerWnd
>,
185 public CComObjectRootEx
<CComMultiThreadModelNoCS
>,
186 public CWindowImpl
< CSysPagerWnd
, CWindow
, CControlWinTraits
>,
190 CNotifyToolbar Toolbar
;
191 CTooltips m_Balloons
;
192 CBalloonQueue m_BalloonQueue
;
196 virtual ~CSysPagerWnd();
198 LRESULT
DrawBackground(HDC hdc
);
199 LRESULT
OnEraseBackground(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
200 LRESULT
OnCreate(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
201 LRESULT
OnDestroy(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
202 LRESULT
OnGetInfoTip(INT uCode
, LPNMHDR hdr
, BOOL
& bHandled
);
203 LRESULT
OnCustomDraw(INT uCode
, LPNMHDR hdr
, BOOL
& bHandled
);
204 LRESULT
OnSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
205 LRESULT
OnCtxMenu(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
206 LRESULT
OnBalloonPop(UINT uCode
, LPNMHDR hdr
, BOOL
& bHandled
);
207 LRESULT
OnTimer(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
208 LRESULT
OnCopyData(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
209 LRESULT
OnSettingChanged(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
210 LRESULT
OnGetMinimumSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
214 HRESULT WINAPI
GetWindow(HWND
* phwnd
)
222 HRESULT WINAPI
ContextSensitiveHelp(BOOL fEnterMode
)
227 DECLARE_NOT_AGGREGATABLE(CSysPagerWnd
)
229 DECLARE_PROTECT_FINAL_CONSTRUCT()
230 BEGIN_COM_MAP(CSysPagerWnd
)
231 COM_INTERFACE_ENTRY_IID(IID_IOleWindow
, IOleWindow
)
234 BOOL
NotifyIcon(DWORD dwMessage
, _In_ CONST NOTIFYICONDATA
*iconData
);
235 void GetSize(IN BOOL IsHorizontal
, IN PSIZE size
);
237 DECLARE_WND_CLASS_EX(szSysPagerWndClass
, CS_DBLCLKS
, COLOR_3DFACE
)
239 BEGIN_MSG_MAP(CSysPagerWnd
)
240 MESSAGE_HANDLER(WM_CREATE
, OnCreate
)
241 MESSAGE_HANDLER(WM_DESTROY
, OnDestroy
)
242 MESSAGE_HANDLER(WM_ERASEBKGND
, OnEraseBackground
)
243 MESSAGE_HANDLER(WM_SIZE
, OnSize
)
244 MESSAGE_HANDLER(WM_CONTEXTMENU
, OnCtxMenu
)
245 MESSAGE_HANDLER(WM_TIMER
, OnTimer
)
246 MESSAGE_HANDLER(WM_COPYDATA
, OnCopyData
)
247 MESSAGE_HANDLER(WM_SETTINGCHANGE
, OnSettingChanged
)
248 MESSAGE_HANDLER(TNWM_GETMINIMUMSIZE
, OnGetMinimumSize
)
249 NOTIFY_CODE_HANDLER(TTN_POP
, OnBalloonPop
)
250 NOTIFY_CODE_HANDLER(TBN_GETINFOTIPW
, OnGetInfoTip
)
251 NOTIFY_CODE_HANDLER(NM_CUSTOMDRAW
, OnCustomDraw
)
254 HRESULT
Initialize(IN HWND hWndParent
);
257 CIconWatcher::CIconWatcher() :
258 m_hWatcherThread(NULL
),
265 CIconWatcher::~CIconWatcher()
268 DeleteCriticalSection(&m_ListLock
);
271 CloseHandle(m_WakeUpEvent
);
272 if (m_hWatcherThread
)
273 CloseHandle(m_hWatcherThread
);
276 bool CIconWatcher::Initialize(_In_ HWND hWndParent
)
278 m_hwndSysTray
= hWndParent
;
280 InitializeCriticalSection(&m_ListLock
);
281 m_WakeUpEvent
= CreateEventW(NULL
, FALSE
, FALSE
, NULL
);
282 if (m_WakeUpEvent
== NULL
)
285 m_hWatcherThread
= (HANDLE
)_beginthreadex(NULL
,
291 if (m_hWatcherThread
== NULL
)
297 void CIconWatcher::Uninitialize()
301 SetEvent(m_WakeUpEvent
);
303 EnterCriticalSection(&m_ListLock
);
306 for (size_t i
= 0; i
< m_WatcherList
.GetCount(); i
++)
308 Pos
= m_WatcherList
.FindIndex(i
);
311 IconWatcherData
*Icon
;
312 Icon
= m_WatcherList
.GetAt(Pos
);
316 m_WatcherList
.RemoveAll();
318 LeaveCriticalSection(&m_ListLock
);
321 bool CIconWatcher::AddIconToWatcher(_In_ CONST NOTIFYICONDATA
*iconData
)
324 (void)GetWindowThreadProcessId(iconData
->hWnd
, &ProcessId
);
327 hProcess
= OpenProcess(SYNCHRONIZE
, FALSE
, ProcessId
);
328 if (hProcess
== NULL
)
333 IconWatcherData
*Icon
= new IconWatcherData(iconData
);
334 Icon
->hProcess
= hProcess
;
335 Icon
->ProcessId
= ProcessId
;
338 EnterCriticalSection(&m_ListLock
);
340 // The likelyhood of someone having more than 64 icons in their tray is
341 // pretty slim. We could spin up a new thread for each multiple of 64, but
342 // it's not worth the effort, so we just won't bother watching those icons
343 if (m_WatcherList
.GetCount() < MAXIMUM_WAIT_OBJECTS
)
345 m_WatcherList
.AddTail(Icon
);
346 SetEvent(m_WakeUpEvent
);
350 LeaveCriticalSection(&m_ListLock
);
360 bool CIconWatcher::RemoveIconFromWatcher(_In_ CONST NOTIFYICONDATA
*iconData
)
362 EnterCriticalSection(&m_ListLock
);
364 IconWatcherData
*Icon
;
365 Icon
= GetListEntry(iconData
, NULL
, true);
367 SetEvent(m_WakeUpEvent
);
368 LeaveCriticalSection(&m_ListLock
);
374 IconWatcherData
* CIconWatcher::GetListEntry(_In_opt_ CONST NOTIFYICONDATA
*iconData
, _In_opt_ HANDLE hProcess
, _In_
bool Remove
)
376 IconWatcherData
*Entry
= NULL
;
377 POSITION NextPosition
= m_WatcherList
.GetHeadPosition();
381 Position
= NextPosition
;
383 Entry
= m_WatcherList
.GetNext(NextPosition
);
386 if ((iconData
&& ((Entry
->IconData
.hWnd
== iconData
->hWnd
) && (Entry
->IconData
.uID
== iconData
->uID
))) ||
387 (hProcess
&& (Entry
->hProcess
== hProcess
)))
390 m_WatcherList
.RemoveAt(Position
);
396 } while (NextPosition
!= NULL
);
401 UINT WINAPI
CIconWatcher::WatcherThread(_In_opt_ LPVOID lpParam
)
403 CIconWatcher
* This
= reinterpret_cast<CIconWatcher
*>(lpParam
);
404 HANDLE
*WatchList
= NULL
;
409 EnterCriticalSection(&This
->m_ListLock
);
412 Size
= This
->m_WatcherList
.GetCount() + 1;
413 ASSERT(Size
<= MAXIMUM_WAIT_OBJECTS
);
417 WatchList
= new HANDLE
[Size
];
418 WatchList
[0] = This
->m_WakeUpEvent
;
421 for (size_t i
= 0; i
< This
->m_WatcherList
.GetCount(); i
++)
423 Pos
= This
->m_WatcherList
.FindIndex(i
);
426 IconWatcherData
*Icon
;
427 Icon
= This
->m_WatcherList
.GetAt(Pos
);
428 WatchList
[i
+ 1] = Icon
->hProcess
;
432 LeaveCriticalSection(&This
->m_ListLock
);
435 Status
= WaitForMultipleObjects(Size
,
439 if (Status
== WAIT_OBJECT_0
)
441 // We've been kicked, we have updates to our list (or we're exiting the thread)
443 TRACE("Updating watched icon list");
445 else if ((Status
>= WAIT_OBJECT_0
+ 1) && (Status
< Size
))
447 IconWatcherData
*Icon
;
448 Icon
= This
->GetListEntry(NULL
, WatchList
[Status
], false);
450 TRACE("Pid %lu owns a notification icon and has stopped without deleting it. We'll cleanup on its behalf", Icon
->ProcessId
);
452 TRAYNOTIFYDATAW tnid
= {0};
453 tnid
.dwSignature
= NI_NOTIFY_SIG
;
454 tnid
.dwMessage
= NIM_DELETE
;
455 CopyMemory(&tnid
.nid
, &Icon
->IconData
, Icon
->IconData
.cbSize
);
459 data
.cbData
= sizeof(tnid
);
462 BOOL Success
= ::SendMessage(This
->m_hwndSysTray
, WM_COPYDATA
,
463 (WPARAM
)&Icon
->IconData
, (LPARAM
)&data
);
466 // If we failed to handle the delete message, forcibly remove it
467 This
->RemoveIconFromWatcher(&Icon
->IconData
);
472 if (Status
== WAIT_FAILED
)
474 Status
= GetLastError();
476 ERR("Failed to wait on process handles : %lu\n", Status
);
477 This
->Uninitialize();
491 CBalloonQueue::CBalloonQueue() :
496 m_currentClosed(false),
501 void CBalloonQueue::Init(HWND hwndParent
, CNotifyToolbar
* toolbar
, CTooltips
* balloons
)
503 m_hwndParent
= hwndParent
;
505 m_tooltips
= balloons
;
508 void CBalloonQueue::Deinit()
512 ::KillTimer(m_hwndParent
, m_timer
);
516 bool CBalloonQueue::OnTimer(int timerId
)
518 if (timerId
!= m_timer
)
521 ::KillTimer(m_hwndParent
, m_timer
);
524 if (m_current
&& !m_currentClosed
)
526 Close(m_current
, NIN_BALLOONTIMEOUT
);
531 m_currentClosed
= false;
532 if (!m_queue
.IsEmpty())
534 Info info
= m_queue
.RemoveHead();
542 void CBalloonQueue::UpdateInfo(InternalIconData
* notifyItem
)
545 HRESULT hr
= StringCchLength(notifyItem
->szInfo
, _countof(notifyItem
->szInfo
), &len
);
546 if (SUCCEEDED(hr
) && len
> 0)
548 Info
info(notifyItem
);
550 // If m_current == notifyItem, we want to replace the previous balloon even if there is a queue.
551 if (m_current
!= notifyItem
&& (m_current
!= NULL
|| !m_queue
.IsEmpty()))
553 m_queue
.AddTail(info
);
562 Close(notifyItem
, NIN_BALLOONHIDE
);
566 void CBalloonQueue::RemoveInfo(InternalIconData
* notifyItem
)
568 Close(notifyItem
, NIN_BALLOONHIDE
);
570 POSITION position
= m_queue
.GetHeadPosition();
571 while(position
!= NULL
)
573 Info
& info
= m_queue
.GetNext(position
);
574 if (info
.pSource
== notifyItem
)
576 m_queue
.RemoveAt(position
);
581 void CBalloonQueue::CloseCurrent()
583 if (m_current
!= NULL
)
585 Close(m_current
, NIN_BALLOONTIMEOUT
);
589 int CBalloonQueue::IndexOf(InternalIconData
* pdata
)
591 int count
= m_toolbar
->GetButtonCount();
592 for (int i
= 0; i
< count
; i
++)
594 if (m_toolbar
->GetItemData(i
) == pdata
)
600 void CBalloonQueue::SetTimer(int length
)
602 m_timer
= ::SetTimer(m_hwndParent
, BalloonsTimerId
, length
, NULL
);
605 void CBalloonQueue::Show(Info
& info
)
607 TRACE("ShowBalloonTip called for flags=%x text=%ws; title=%ws\n", info
.uIcon
, info
.szInfo
, info
.szInfoTitle
);
609 // TODO: NIF_REALTIME, NIIF_NOSOUND, other Vista+ flags
611 const int index
= IndexOf(info
.pSource
);
613 m_toolbar
->GetItemRect(index
, &rc
);
614 m_toolbar
->ClientToScreen(&rc
);
615 const WORD x
= (rc
.left
+ rc
.right
) / 2;
616 const WORD y
= (rc
.top
+ rc
.bottom
) / 2;
618 m_tooltips
->SetTitle(info
.szInfoTitle
, info
.uIcon
);
619 m_tooltips
->TrackPosition(x
, y
);
620 m_tooltips
->UpdateTipText(m_hwndParent
, reinterpret_cast<LPARAM
>(m_toolbar
->m_hWnd
), info
.szInfo
);
621 m_tooltips
->TrackActivate(m_hwndParent
, reinterpret_cast<LPARAM
>(m_toolbar
->m_hWnd
));
623 m_current
= info
.pSource
;
624 int timeout
= info
.uTimeout
;
625 if (timeout
< MinTimeout
) timeout
= MinTimeout
;
626 if (timeout
> MaxTimeout
) timeout
= MaxTimeout
;
630 m_toolbar
->SendNotifyCallback(m_current
, NIN_BALLOONSHOW
);
633 void CBalloonQueue::Close(IN OUT InternalIconData
* notifyItem
, IN UINT uReason
)
635 TRACE("HideBalloonTip called\n");
637 if (m_current
== notifyItem
&& !m_currentClosed
)
639 m_toolbar
->SendNotifyCallback(m_current
, uReason
);
642 m_currentClosed
= true;
643 m_tooltips
->TrackDeactivate();
644 SetTimer(CooldownBetweenBalloons
);
652 CNotifyToolbar::CNotifyToolbar() :
654 m_VisibleButtonCount(0),
659 CNotifyToolbar::~CNotifyToolbar()
663 int CNotifyToolbar::GetVisibleButtonCount()
665 return m_VisibleButtonCount
;
668 int CNotifyToolbar::FindItem(IN HWND hWnd
, IN UINT uID
, InternalIconData
** pdata
)
670 int count
= GetButtonCount();
672 for (int i
= 0; i
< count
; i
++)
674 InternalIconData
* data
= GetItemData(i
);
676 if (data
->hWnd
== hWnd
&&
688 int CNotifyToolbar::FindExistingSharedIcon(HICON handle
)
690 int count
= GetButtonCount();
691 for (int i
= 0; i
< count
; i
++)
693 InternalIconData
* data
= GetItemData(i
);
694 if (data
->hIcon
== handle
)
705 BOOL
CNotifyToolbar::AddButton(_In_ CONST NOTIFYICONDATA
*iconData
)
707 TBBUTTON tbBtn
= { 0 };
708 InternalIconData
* notifyItem
;
711 TRACE("Adding icon %d from hWnd %08x flags%s%s state%s%s",
712 iconData
->uID
, iconData
->hWnd
,
713 (iconData
->uFlags
& NIF_ICON
) ? " ICON" : "",
714 (iconData
->uFlags
& NIF_STATE
) ? " STATE" : "",
715 (iconData
->dwState
& NIS_HIDDEN
) ? " HIDDEN" : "",
716 (iconData
->dwState
& NIS_SHAREDICON
) ? " SHARED" : "");
718 int index
= FindItem(iconData
->hWnd
, iconData
->uID
, ¬ifyItem
);
721 TRACE("Icon %d from hWnd %08x ALREADY EXISTS!", iconData
->uID
, iconData
->hWnd
);
725 notifyItem
= new InternalIconData();
726 ZeroMemory(notifyItem
, sizeof(*notifyItem
));
728 notifyItem
->hWnd
= iconData
->hWnd
;
729 notifyItem
->uID
= iconData
->uID
;
731 tbBtn
.fsState
= TBSTATE_ENABLED
;
732 tbBtn
.fsStyle
= BTNS_NOPREFIX
;
733 tbBtn
.dwData
= (DWORD_PTR
)notifyItem
;
734 tbBtn
.iString
= (INT_PTR
) text
;
735 tbBtn
.idCommand
= GetButtonCount();
737 if (iconData
->uFlags
& NIF_STATE
)
739 notifyItem
->dwState
= iconData
->dwState
& iconData
->dwStateMask
;
742 if (iconData
->uFlags
& NIF_MESSAGE
)
744 notifyItem
->uCallbackMessage
= iconData
->uCallbackMessage
;
747 if (iconData
->uFlags
& NIF_ICON
)
749 notifyItem
->hIcon
= iconData
->hIcon
;
750 BOOL hasSharedIcon
= notifyItem
->dwState
& NIS_SHAREDICON
;
753 INT iIcon
= FindExistingSharedIcon(notifyItem
->hIcon
);
756 notifyItem
->hIcon
= NULL
;
757 TRACE("Shared icon requested, but HICON not found!!!");
759 tbBtn
.iBitmap
= iIcon
;
763 tbBtn
.iBitmap
= ImageList_AddIcon(m_ImageList
, notifyItem
->hIcon
);
767 if (iconData
->uFlags
& NIF_TIP
)
769 StringCchCopy(notifyItem
->szTip
, _countof(notifyItem
->szTip
), iconData
->szTip
);
772 if (iconData
->uFlags
& NIF_INFO
)
774 // NOTE: In Vista+, the uTimeout value is disregarded, and the accessibility settings are used always.
775 StringCchCopy(notifyItem
->szInfo
, _countof(notifyItem
->szInfo
), iconData
->szInfo
);
776 StringCchCopy(notifyItem
->szInfoTitle
, _countof(notifyItem
->szInfoTitle
), iconData
->szInfoTitle
);
777 notifyItem
->dwInfoFlags
= iconData
->dwInfoFlags
;
778 notifyItem
->uTimeout
= iconData
->uTimeout
;
781 if (notifyItem
->dwState
& NIS_HIDDEN
)
783 tbBtn
.fsState
|= TBSTATE_HIDDEN
;
787 m_VisibleButtonCount
++;
790 /* TODO: support VERSION_4 (NIF_GUID, NIF_REALTIME, NIF_SHOWTIP) */
792 CToolbar::AddButton(&tbBtn
);
793 SetButtonSize(GetSystemMetrics(SM_CXSMICON
), GetSystemMetrics(SM_CYSMICON
));
795 if (iconData
->uFlags
& NIF_INFO
)
797 m_BalloonQueue
->UpdateInfo(notifyItem
);
803 BOOL
CNotifyToolbar::SwitchVersion(_In_ CONST NOTIFYICONDATA
*iconData
)
805 InternalIconData
* notifyItem
;
806 int index
= FindItem(iconData
->hWnd
, iconData
->uID
, ¬ifyItem
);
809 WARN("Icon %d from hWnd %08x DOES NOT EXIST!", iconData
->uID
, iconData
->hWnd
);
813 if (iconData
->uVersion
!= 0 && iconData
->uVersion
!= NOTIFYICON_VERSION
)
815 WARN("Tried to set the version of icon %d from hWnd %08x, to an unknown value %d. Vista+ program?", iconData
->uID
, iconData
->hWnd
, iconData
->uVersion
);
819 // We can not store the version in the uVersion field, because it's union'd with uTimeout,
820 // which we also need to keep track of.
821 notifyItem
->uVersionCopy
= iconData
->uVersion
;
826 BOOL
CNotifyToolbar::UpdateButton(_In_ CONST NOTIFYICONDATA
*iconData
)
828 InternalIconData
* notifyItem
;
829 TBBUTTONINFO tbbi
= { 0 };
831 TRACE("Updating icon %d from hWnd %08x flags%s%s state%s%s",
832 iconData
->uID
, iconData
->hWnd
,
833 (iconData
->uFlags
& NIF_ICON
) ? " ICON" : "",
834 (iconData
->uFlags
& NIF_STATE
) ? " STATE" : "",
835 (iconData
->dwState
& NIS_HIDDEN
) ? " HIDDEN" : "",
836 (iconData
->dwState
& NIS_SHAREDICON
) ? " SHARED" : "");
838 int index
= FindItem(iconData
->hWnd
, iconData
->uID
, ¬ifyItem
);
841 WARN("Icon %d from hWnd %08x DOES NOT EXIST!", iconData
->uID
, iconData
->hWnd
);
842 return AddButton(iconData
);
846 GetButton(index
, &btn
);
847 int oldIconIndex
= btn
.iBitmap
;
849 tbbi
.cbSize
= sizeof(tbbi
);
850 tbbi
.dwMask
= TBIF_BYINDEX
| TBIF_COMMAND
;
851 tbbi
.idCommand
= index
;
853 if (iconData
->uFlags
& NIF_STATE
)
855 if (iconData
->dwStateMask
& NIS_HIDDEN
&&
856 (notifyItem
->dwState
& NIS_HIDDEN
) != (iconData
->dwState
& NIS_HIDDEN
))
858 tbbi
.dwMask
|= TBIF_STATE
;
859 if (iconData
->dwState
& NIS_HIDDEN
)
861 tbbi
.fsState
|= TBSTATE_HIDDEN
;
862 m_VisibleButtonCount
--;
866 tbbi
.fsState
&= ~TBSTATE_HIDDEN
;
867 m_VisibleButtonCount
++;
871 notifyItem
->dwState
&= ~iconData
->dwStateMask
;
872 notifyItem
->dwState
|= (iconData
->dwState
& iconData
->dwStateMask
);
875 if (iconData
->uFlags
& NIF_MESSAGE
)
877 notifyItem
->uCallbackMessage
= iconData
->uCallbackMessage
;
880 if (iconData
->uFlags
& NIF_ICON
)
882 BOOL hasSharedIcon
= notifyItem
->dwState
& NIS_SHAREDICON
;
885 INT iIcon
= FindExistingSharedIcon(iconData
->hIcon
);
888 notifyItem
->hIcon
= iconData
->hIcon
;
889 tbbi
.dwMask
|= TBIF_IMAGE
;
894 TRACE("Shared icon requested, but HICON not found!!! IGNORING!");
899 notifyItem
->hIcon
= iconData
->hIcon
;
900 tbbi
.dwMask
|= TBIF_IMAGE
;
901 tbbi
.iImage
= ImageList_ReplaceIcon(m_ImageList
, oldIconIndex
, notifyItem
->hIcon
);
905 if (iconData
->uFlags
& NIF_TIP
)
907 StringCchCopy(notifyItem
->szTip
, _countof(notifyItem
->szTip
), iconData
->szTip
);
910 if (iconData
->uFlags
& NIF_INFO
)
912 // NOTE: In Vista+, the uTimeout value is disregarded, and the accessibility settings are used always.
913 StringCchCopy(notifyItem
->szInfo
, _countof(notifyItem
->szInfo
), iconData
->szInfo
);
914 StringCchCopy(notifyItem
->szInfoTitle
, _countof(notifyItem
->szInfoTitle
), iconData
->szInfoTitle
);
915 notifyItem
->dwInfoFlags
= iconData
->dwInfoFlags
;
916 notifyItem
->uTimeout
= iconData
->uTimeout
;
919 /* TODO: support VERSION_4 (NIF_GUID, NIF_REALTIME, NIF_SHOWTIP) */
921 SetButtonInfo(index
, &tbbi
);
923 if (iconData
->uFlags
& NIF_INFO
)
925 m_BalloonQueue
->UpdateInfo(notifyItem
);
931 BOOL
CNotifyToolbar::RemoveButton(_In_ CONST NOTIFYICONDATA
*iconData
)
933 InternalIconData
* notifyItem
;
935 TRACE("Removing icon %d from hWnd %08x", iconData
->uID
, iconData
->hWnd
);
937 int index
= FindItem(iconData
->hWnd
, iconData
->uID
, ¬ifyItem
);
940 TRACE("Icon %d from hWnd %08x ALREADY MISSING!", iconData
->uID
, iconData
->hWnd
);
945 if (!(notifyItem
->dwState
& NIS_HIDDEN
))
947 m_VisibleButtonCount
--;
950 if (!(notifyItem
->dwState
& NIS_SHAREDICON
))
953 GetButton(index
, &btn
);
954 int oldIconIndex
= btn
.iBitmap
;
955 ImageList_Remove(m_ImageList
, oldIconIndex
);
957 // Update other icons!
958 int count
= GetButtonCount();
959 for (int i
= 0; i
< count
; i
++)
964 if (btn
.iBitmap
> oldIconIndex
)
966 TBBUTTONINFO tbbi2
= { 0 };
967 tbbi2
.cbSize
= sizeof(tbbi2
);
968 tbbi2
.dwMask
= TBIF_BYINDEX
| TBIF_IMAGE
;
969 tbbi2
.iImage
= btn
.iBitmap
-1;
970 SetButtonInfo(i
, &tbbi2
);
975 m_BalloonQueue
->RemoveInfo(notifyItem
);
984 VOID
CNotifyToolbar::ResizeImagelist()
989 if (!ImageList_GetIconSize(m_ImageList
, &cx
, &cy
))
992 if (cx
== GetSystemMetrics(SM_CXSMICON
) && cy
== GetSystemMetrics(SM_CYSMICON
))
995 iml
= ImageList_Create(GetSystemMetrics(SM_CXSMICON
), GetSystemMetrics(SM_CYSMICON
), ILC_COLOR32
| ILC_MASK
, 0, 1000);
999 ImageList_Destroy(m_ImageList
);
1001 SetImageList(m_ImageList
);
1003 int count
= GetButtonCount();
1004 for (int i
= 0; i
< count
; i
++)
1006 InternalIconData
* data
= GetItemData(i
);
1007 BOOL hasSharedIcon
= data
->dwState
& NIS_SHAREDICON
;
1008 INT iIcon
= hasSharedIcon
? FindExistingSharedIcon(data
->hIcon
) : -1;
1010 iIcon
= ImageList_AddIcon(iml
, data
->hIcon
);
1011 TBBUTTONINFO tbbi
= { sizeof(tbbi
), TBIF_BYINDEX
| TBIF_IMAGE
, 0, iIcon
};
1012 SetButtonInfo(i
, &tbbi
);
1015 SetButtonSize(GetSystemMetrics(SM_CXSMICON
), GetSystemMetrics(SM_CYSMICON
));
1018 bool CNotifyToolbar::SendNotifyCallback(InternalIconData
* notifyItem
, UINT uMsg
)
1020 if (!::IsWindow(notifyItem
->hWnd
))
1022 // We detect and destroy icons with invalid handles only on mouse move over systray, same as MS does.
1023 // Alternatively we could search for them periodically (would waste more resources).
1024 TRACE("Destroying icon %d with invalid handle hWnd=%08x\n", notifyItem
->uID
, notifyItem
->hWnd
);
1026 RemoveButton(notifyItem
);
1028 /* Ask the parent to resize */
1029 NMHDR nmh
= {GetParent(), 0, NTNWM_REALIGN
};
1030 GetParent().SendMessage(WM_NOTIFY
, 0, (LPARAM
) &nmh
);
1036 GetWindowThreadProcessId(notifyItem
->hWnd
, &pid
);
1038 if (pid
== GetCurrentProcessId() ||
1039 (uMsg
>= WM_MOUSEFIRST
&& uMsg
<= WM_MOUSELAST
))
1041 ::PostMessage(notifyItem
->hWnd
,
1042 notifyItem
->uCallbackMessage
,
1048 SendMessage(notifyItem
->hWnd
,
1049 notifyItem
->uCallbackMessage
,
1056 VOID
CNotifyToolbar::SendMouseEvent(IN WORD wIndex
, IN UINT uMsg
, IN WPARAM wParam
)
1058 static LPCWSTR eventNames
[] = {
1062 L
"WM_LBUTTONDBLCLK",
1065 L
"WM_RBUTTONDBLCLK",
1068 L
"WM_MBUTTONDBLCLK",
1075 InternalIconData
* notifyItem
= GetItemData(wIndex
);
1077 if (uMsg
>= WM_MOUSEFIRST
&& uMsg
<= WM_MOUSELAST
)
1079 TRACE("Sending message %S from button %d to %p (msg=%x, w=%x, l=%x)...\n",
1080 eventNames
[uMsg
- WM_MOUSEFIRST
], wIndex
,
1081 notifyItem
->hWnd
, notifyItem
->uCallbackMessage
, notifyItem
->uID
, uMsg
);
1084 SendNotifyCallback(notifyItem
, uMsg
);
1087 LRESULT
CNotifyToolbar::OnMouseEvent(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1089 POINT pt
= { GET_X_LPARAM(lParam
), GET_Y_LPARAM(lParam
) };
1091 INT iBtn
= HitTest(&pt
);
1095 SendMouseEvent(iBtn
, uMsg
, wParam
);
1102 static VOID
GetTooltipText(LPARAM data
, LPTSTR szTip
, DWORD cchTip
)
1104 InternalIconData
* notifyItem
= reinterpret_cast<InternalIconData
*>(data
);
1107 StringCchCopy(szTip
, cchTip
, notifyItem
->szTip
);
1111 StringCchCopy(szTip
, cchTip
, L
"");
1115 LRESULT
CNotifyToolbar::OnTooltipShow(INT uCode
, LPNMHDR hdr
, BOOL
& bHandled
)
1118 ::GetWindowRect(hdr
->hwndFrom
, &rcTip
);
1120 SIZE szTip
= { rcTip
.right
- rcTip
.left
, rcTip
.bottom
- rcTip
.top
};
1122 INT iBtn
= GetHotItem();
1126 MONITORINFO monInfo
= { 0 };
1127 HMONITOR hMon
= MonitorFromWindow(m_hWnd
, MONITOR_DEFAULTTONEAREST
);
1129 monInfo
.cbSize
= sizeof(monInfo
);
1132 GetMonitorInfo(hMon
, &monInfo
);
1134 ::GetWindowRect(GetDesktopWindow(), &monInfo
.rcMonitor
);
1136 GetItemRect(iBtn
, &rcItem
);
1138 POINT ptItem
= { rcItem
.left
, rcItem
.top
};
1139 SIZE szItem
= { rcItem
.right
- rcItem
.left
, rcItem
.bottom
- rcItem
.top
};
1140 ClientToScreen(&ptItem
);
1142 ptItem
.x
+= szItem
.cx
/ 2;
1143 ptItem
.y
-= szTip
.cy
;
1145 if (ptItem
.x
+ szTip
.cx
> monInfo
.rcMonitor
.right
)
1146 ptItem
.x
= monInfo
.rcMonitor
.right
- szTip
.cx
;
1148 if (ptItem
.y
+ szTip
.cy
> monInfo
.rcMonitor
.bottom
)
1149 ptItem
.y
= monInfo
.rcMonitor
.bottom
- szTip
.cy
;
1151 if (ptItem
.x
< monInfo
.rcMonitor
.left
)
1152 ptItem
.x
= monInfo
.rcMonitor
.left
;
1154 if (ptItem
.y
< monInfo
.rcMonitor
.top
)
1155 ptItem
.y
= monInfo
.rcMonitor
.top
;
1157 TRACE("ptItem { %d, %d }\n", ptItem
.x
, ptItem
.y
);
1159 ::SetWindowPos(hdr
->hwndFrom
, NULL
, ptItem
.x
, ptItem
.y
, 0, 0, SWP_NOSIZE
| SWP_NOZORDER
| SWP_NOACTIVATE
);
1168 void CNotifyToolbar::Initialize(HWND hWndParent
, CBalloonQueue
* queue
)
1170 m_BalloonQueue
= queue
;
1173 WS_CHILD
| WS_VISIBLE
| WS_CLIPCHILDREN
|
1174 TBSTYLE_FLAT
| TBSTYLE_TOOLTIPS
| TBSTYLE_WRAPABLE
| TBSTYLE_TRANSPARENT
|
1175 CCS_TOP
| CCS_NORESIZE
| CCS_NOPARENTALIGN
| CCS_NODIVIDER
;
1177 SubclassWindow(CToolbar::Create(hWndParent
, styles
));
1179 // Force the toolbar tooltips window to always show tooltips even if not foreground
1180 HWND tooltipsWnd
= (HWND
)SendMessageW(TB_GETTOOLTIPS
);
1183 ::SetWindowLong(tooltipsWnd
, GWL_STYLE
, ::GetWindowLong(tooltipsWnd
, GWL_STYLE
) | TTS_ALWAYSTIP
);
1186 SetWindowTheme(m_hWnd
, L
"TrayNotify", NULL
);
1188 m_ImageList
= ImageList_Create(GetSystemMetrics(SM_CXSMICON
), GetSystemMetrics(SM_CYSMICON
), ILC_COLOR32
| ILC_MASK
, 0, 1000);
1189 SetImageList(m_ImageList
);
1191 TBMETRICS tbm
= {sizeof(tbm
)};
1192 tbm
.dwMask
= TBMF_BARPAD
| TBMF_BUTTONSPACING
| TBMF_PAD
;
1197 tbm
.cxButtonSpacing
= 1;
1198 tbm
.cyButtonSpacing
= 1;
1201 SetButtonSize(GetSystemMetrics(SM_CXSMICON
), GetSystemMetrics(SM_CYSMICON
));
1207 const WCHAR szSysPagerWndClass
[] = L
"SysPager";
1209 CSysPagerWnd::CSysPagerWnd() {}
1211 CSysPagerWnd::~CSysPagerWnd() {}
1213 LRESULT
CSysPagerWnd::OnEraseBackground(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1215 HDC hdc
= (HDC
) wParam
;
1224 GetClientRect(&rect
);
1225 DrawThemeParentBackground(m_hWnd
, hdc
, &rect
);
1230 LRESULT
CSysPagerWnd::OnCreate(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1232 Toolbar
.Initialize(m_hWnd
, &m_BalloonQueue
);
1233 CIconWatcher::Initialize(m_hWnd
);
1235 HWND hWndTop
= GetAncestor(m_hWnd
, GA_ROOT
);
1237 m_Balloons
.Create(hWndTop
, TTS_NOPREFIX
| TTS_BALLOON
| TTS_CLOSE
);
1239 TOOLINFOW ti
= { 0 };
1240 ti
.cbSize
= TTTOOLINFOW_V1_SIZE
;
1241 ti
.uFlags
= TTF_TRACK
| TTF_IDISHWND
;
1242 ti
.uId
= reinterpret_cast<UINT_PTR
>(Toolbar
.m_hWnd
);
1247 BOOL ret
= m_Balloons
.AddTool(&ti
);
1250 WARN("AddTool failed, LastError=%d (probably meaningless unless non-zero)\n", GetLastError());
1253 m_BalloonQueue
.Init(m_hWnd
, &Toolbar
, &m_Balloons
);
1255 // Explicitly request running applications to re-register their systray icons
1256 ::SendNotifyMessageW(HWND_BROADCAST
,
1257 RegisterWindowMessageW(L
"TaskbarCreated"),
1263 LRESULT
CSysPagerWnd::OnDestroy(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1265 m_BalloonQueue
.Deinit();
1266 CIconWatcher::Uninitialize();
1270 BOOL
CSysPagerWnd::NotifyIcon(DWORD dwMessage
, _In_ CONST NOTIFYICONDATA
*iconData
)
1274 int VisibleButtonCount
= Toolbar
.GetVisibleButtonCount();
1276 TRACE("NotifyIcon received. Code=%d\n", dwMessage
);
1280 ret
= Toolbar
.AddButton(iconData
);
1283 (void)AddIconToWatcher(iconData
);
1288 ret
= Toolbar
.UpdateButton(iconData
);
1292 ret
= Toolbar
.RemoveButton(iconData
);
1295 (void)RemoveIconFromWatcher(iconData
);
1304 case NIM_SETVERSION
:
1305 ret
= Toolbar
.SwitchVersion(iconData
);
1309 TRACE("NotifyIcon received with unknown code %d.\n", dwMessage
);
1313 if (VisibleButtonCount
!= Toolbar
.GetVisibleButtonCount())
1315 /* Ask the parent to resize */
1316 NMHDR nmh
= {GetParent(), 0, NTNWM_REALIGN
};
1317 GetParent().SendMessage(WM_NOTIFY
, 0, (LPARAM
) &nmh
);
1323 void CSysPagerWnd::GetSize(IN BOOL IsHorizontal
, IN PSIZE size
)
1325 /* Get the ideal height or width */
1327 /* Unfortunately this doens't work correctly in ros */
1328 Toolbar
.GetIdealSize(!IsHorizontal
, size
);
1330 /* Make the reference dimension an exact multiple of the icon size */
1332 size
->cy
-= size
->cy
% GetSystemMetrics(SM_CYSMICON
);
1334 size
->cx
-= size
->cx
% GetSystemMetrics(SM_CXSMICON
);
1339 INT cyButton
= GetSystemMetrics(SM_CYSMICON
) + 2;
1340 INT cxButton
= GetSystemMetrics(SM_CXSMICON
) + 2;
1341 int VisibleButtonCount
= Toolbar
.GetVisibleButtonCount();
1345 rows
= max(size
->cy
/ cyButton
, 1);
1346 columns
= (VisibleButtonCount
+ rows
- 1) / rows
;
1350 columns
= max(size
->cx
/ cxButton
, 1);
1351 rows
= (VisibleButtonCount
+ columns
- 1) / columns
;
1353 size
->cx
= columns
* cxButton
;
1354 size
->cy
= rows
* cyButton
;
1358 LRESULT
CSysPagerWnd::OnGetInfoTip(INT uCode
, LPNMHDR hdr
, BOOL
& bHandled
)
1360 NMTBGETINFOTIPW
* nmtip
= (NMTBGETINFOTIPW
*) hdr
;
1361 GetTooltipText(nmtip
->lParam
, nmtip
->pszText
, nmtip
->cchTextMax
);
1365 LRESULT
CSysPagerWnd::OnCustomDraw(INT uCode
, LPNMHDR hdr
, BOOL
& bHandled
)
1367 NMCUSTOMDRAW
* cdraw
= (NMCUSTOMDRAW
*) hdr
;
1368 switch (cdraw
->dwDrawStage
)
1371 return CDRF_NOTIFYITEMDRAW
;
1373 case CDDS_ITEMPREPAINT
:
1374 return TBCDRF_NOBACKGROUND
| TBCDRF_NOEDGES
| TBCDRF_NOOFFSET
| TBCDRF_NOMARK
| TBCDRF_NOETCHEDEFFECT
;
1379 LRESULT
CSysPagerWnd::OnSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1383 szClient
.cx
= LOWORD(lParam
);
1384 szClient
.cy
= HIWORD(lParam
);
1386 Ret
= DefWindowProc(uMsg
, wParam
, lParam
);
1390 Toolbar
.SetWindowPos(NULL
, 0, 0, szClient
.cx
, szClient
.cy
, SWP_NOZORDER
);
1394 Toolbar
.GetClientRect(&rc
);
1396 SIZE szBar
= { rc
.right
- rc
.left
, rc
.bottom
- rc
.top
};
1398 INT xOff
= (szClient
.cx
- szBar
.cx
) / 2;
1399 INT yOff
= (szClient
.cy
- szBar
.cy
) / 2;
1401 Toolbar
.SetWindowPos(NULL
, xOff
, yOff
, szBar
.cx
, szBar
.cy
, SWP_NOZORDER
);
1406 LRESULT
CSysPagerWnd::OnCtxMenu(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1412 LRESULT
CSysPagerWnd::OnBalloonPop(UINT uCode
, LPNMHDR hdr
, BOOL
& bHandled
)
1414 m_BalloonQueue
.CloseCurrent();
1419 LRESULT
CSysPagerWnd::OnTimer(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1421 if (m_BalloonQueue
.OnTimer(wParam
))
1429 LRESULT
CSysPagerWnd::OnCopyData(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1431 PCOPYDATASTRUCT cpData
= (PCOPYDATASTRUCT
)lParam
;
1432 if (cpData
->dwData
== 1)
1434 /* A taskbar NotifyIcon notification */
1435 PTRAYNOTIFYDATAW pData
= (PTRAYNOTIFYDATAW
)cpData
->lpData
;
1436 if (pData
->dwSignature
== NI_NOTIFY_SIG
)
1437 return NotifyIcon(pData
->dwMessage
, &pData
->nid
);
1439 // TODO: Handle other types of taskbar notifications
1444 LRESULT
CSysPagerWnd::OnSettingChanged(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1446 if (wParam
== SPI_SETNONCLIENTMETRICS
)
1448 Toolbar
.ResizeImagelist();
1453 LRESULT
CSysPagerWnd::OnGetMinimumSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1455 GetSize((BOOL
)wParam
, (PSIZE
)lParam
);
1459 HRESULT
CSysPagerWnd::Initialize(IN HWND hWndParent
)
1461 /* Create the window. The tray window is going to move it to the correct
1462 position and resize it as needed. */
1463 DWORD dwStyle
= WS_CHILD
| WS_CLIPSIBLINGS
| WS_VISIBLE
;
1464 Create(hWndParent
, 0, NULL
, dwStyle
);
1468 SetWindowTheme(m_hWnd
, L
"TrayNotify", NULL
);
1473 HRESULT
CSysPagerWnd_CreateInstance(HWND hwndParent
, REFIID riid
, void **ppv
)
1475 return ShellObjectCreatorInit
<CSysPagerWnd
>(hwndParent
, riid
, ppv
);