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
);
84 static const int TimerInterval
= 2000;
85 static const int BalloonsTimerId
= 1;
86 static const int MinTimeout
= 10000;
87 static const int MaxTimeout
= 30000;
88 static const int CooldownBetweenBalloons
= 2000;
93 InternalIconData
* pSource
;
95 WCHAR szInfoTitle
[64];
99 Info(InternalIconData
* source
)
102 StringCchCopy(szInfo
, _countof(szInfo
), source
->szInfo
);
103 StringCchCopy(szInfoTitle
, _countof(szInfoTitle
), source
->szInfoTitle
);
104 uIcon
= source
->dwInfoFlags
& NIIF_ICON_MASK
;
105 if (source
->dwInfoFlags
== NIIF_USER
)
106 uIcon
= reinterpret_cast<WPARAM
>(source
->hIcon
);
107 uTimeout
= source
->uTimeout
;
113 CTooltips
* m_tooltips
;
115 CAtlList
<Info
> m_queue
;
117 CToolbar
<InternalIconData
> * m_toolbar
;
119 InternalIconData
* m_current
;
120 bool m_currentClosed
;
127 void Init(HWND hwndParent
, CToolbar
<InternalIconData
> * toolbar
, CTooltips
* balloons
);
130 bool OnTimer(int timerId
);
131 void UpdateInfo(InternalIconData
* notifyItem
);
132 void RemoveInfo(InternalIconData
* notifyItem
);
137 int IndexOf(InternalIconData
* pdata
);
138 void SetTimer(int length
);
139 void Show(Info
& info
);
140 void Close(IN OUT InternalIconData
* notifyItem
);
143 class CNotifyToolbar
:
144 public CWindowImplBaseT
< CToolbar
<InternalIconData
>, CControlWinTraits
>
146 HIMAGELIST m_ImageList
;
147 int m_VisibleButtonCount
;
149 CBalloonQueue
* m_BalloonQueue
;
153 virtual ~CNotifyToolbar();
155 int GetVisibleButtonCount();
156 int FindItem(IN HWND hWnd
, IN UINT uID
, InternalIconData
** pdata
);
157 int FindExistingSharedIcon(HICON handle
);
158 BOOL
AddButton(IN CONST NOTIFYICONDATA
*iconData
);
159 BOOL
SwitchVersion(IN CONST NOTIFYICONDATA
*iconData
);
160 BOOL
UpdateButton(IN CONST NOTIFYICONDATA
*iconData
);
161 BOOL
RemoveButton(IN CONST NOTIFYICONDATA
*iconData
);
162 VOID
ResizeImagelist();
165 VOID
SendMouseEvent(IN WORD wIndex
, IN UINT uMsg
, IN WPARAM wParam
);
166 LRESULT
OnMouseEvent(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
167 LRESULT
OnTooltipShow(INT uCode
, LPNMHDR hdr
, BOOL
& bHandled
);
170 BEGIN_MSG_MAP(CNotifyToolbar
)
171 MESSAGE_RANGE_HANDLER(WM_MOUSEFIRST
, WM_MOUSELAST
, OnMouseEvent
)
172 NOTIFY_CODE_HANDLER(TTN_SHOW
, OnTooltipShow
)
175 void Initialize(HWND hWndParent
, CBalloonQueue
* queue
);
178 extern const WCHAR szSysPagerWndClass
[];
181 public CComCoClass
<CSysPagerWnd
>,
182 public CComObjectRootEx
<CComMultiThreadModelNoCS
>,
183 public CWindowImpl
< CSysPagerWnd
, CWindow
, CControlWinTraits
>,
187 CNotifyToolbar Toolbar
;
188 CTooltips m_Balloons
;
189 CBalloonQueue m_BalloonQueue
;
193 virtual ~CSysPagerWnd();
195 LRESULT
DrawBackground(HDC hdc
);
196 LRESULT
OnEraseBackground(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
197 LRESULT
OnCreate(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
198 LRESULT
OnDestroy(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
199 LRESULT
OnGetInfoTip(INT uCode
, LPNMHDR hdr
, BOOL
& bHandled
);
200 LRESULT
OnCustomDraw(INT uCode
, LPNMHDR hdr
, BOOL
& bHandled
);
201 LRESULT
OnSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
202 LRESULT
OnCtxMenu(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
203 LRESULT
OnBalloonPop(UINT uCode
, LPNMHDR hdr
, BOOL
& bHandled
);
204 LRESULT
OnTimer(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
205 LRESULT
OnCopyData(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
206 LRESULT
OnSettingChanged(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
207 LRESULT
OnGetMinimumSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
211 HRESULT WINAPI
GetWindow(HWND
* phwnd
)
219 HRESULT WINAPI
ContextSensitiveHelp(BOOL fEnterMode
)
224 DECLARE_NOT_AGGREGATABLE(CSysPagerWnd
)
226 DECLARE_PROTECT_FINAL_CONSTRUCT()
227 BEGIN_COM_MAP(CSysPagerWnd
)
228 COM_INTERFACE_ENTRY_IID(IID_IOleWindow
, IOleWindow
)
231 BOOL
NotifyIcon(DWORD dwMessage
, _In_ CONST NOTIFYICONDATA
*iconData
);
232 void GetSize(IN BOOL IsHorizontal
, IN PSIZE size
);
234 DECLARE_WND_CLASS_EX(szSysPagerWndClass
, CS_DBLCLKS
, COLOR_3DFACE
)
236 BEGIN_MSG_MAP(CSysPagerWnd
)
237 MESSAGE_HANDLER(WM_CREATE
, OnCreate
)
238 MESSAGE_HANDLER(WM_DESTROY
, OnDestroy
)
239 MESSAGE_HANDLER(WM_ERASEBKGND
, OnEraseBackground
)
240 MESSAGE_HANDLER(WM_SIZE
, OnSize
)
241 MESSAGE_HANDLER(WM_CONTEXTMENU
, OnCtxMenu
)
242 MESSAGE_HANDLER(WM_TIMER
, OnTimer
)
243 MESSAGE_HANDLER(WM_COPYDATA
, OnCopyData
)
244 MESSAGE_HANDLER(WM_SETTINGCHANGE
, OnSettingChanged
)
245 MESSAGE_HANDLER(TNWM_GETMINIMUMSIZE
, OnGetMinimumSize
)
246 NOTIFY_CODE_HANDLER(TTN_POP
, OnBalloonPop
)
247 NOTIFY_CODE_HANDLER(TBN_GETINFOTIPW
, OnGetInfoTip
)
248 NOTIFY_CODE_HANDLER(NM_CUSTOMDRAW
, OnCustomDraw
)
251 HRESULT
Initialize(IN HWND hWndParent
);
254 CIconWatcher::CIconWatcher() :
255 m_hWatcherThread(NULL
),
262 CIconWatcher::~CIconWatcher()
265 DeleteCriticalSection(&m_ListLock
);
268 CloseHandle(m_WakeUpEvent
);
269 if (m_hWatcherThread
)
270 CloseHandle(m_hWatcherThread
);
273 bool CIconWatcher::Initialize(_In_ HWND hWndParent
)
275 m_hwndSysTray
= hWndParent
;
277 InitializeCriticalSection(&m_ListLock
);
278 m_WakeUpEvent
= CreateEventW(NULL
, FALSE
, FALSE
, NULL
);
279 if (m_WakeUpEvent
== NULL
)
282 m_hWatcherThread
= (HANDLE
)_beginthreadex(NULL
,
288 if (m_hWatcherThread
== NULL
)
294 void CIconWatcher::Uninitialize()
298 SetEvent(m_WakeUpEvent
);
300 EnterCriticalSection(&m_ListLock
);
303 for (size_t i
= 0; i
< m_WatcherList
.GetCount(); i
++)
305 Pos
= m_WatcherList
.FindIndex(i
);
308 IconWatcherData
*Icon
;
309 Icon
= m_WatcherList
.GetAt(Pos
);
313 m_WatcherList
.RemoveAll();
315 LeaveCriticalSection(&m_ListLock
);
318 bool CIconWatcher::AddIconToWatcher(_In_ CONST NOTIFYICONDATA
*iconData
)
321 (void)GetWindowThreadProcessId(iconData
->hWnd
, &ProcessId
);
324 hProcess
= OpenProcess(SYNCHRONIZE
, FALSE
, ProcessId
);
325 if (hProcess
== NULL
)
330 IconWatcherData
*Icon
= new IconWatcherData(iconData
);
331 Icon
->hProcess
= hProcess
;
335 EnterCriticalSection(&m_ListLock
);
337 // The likelyhood of someone having more than 64 icons in their tray is
338 // pretty slim. We could spin up a new thread for each multiple of 64, but
339 // it's not worth the effort, so we just won't bother watching those icons
340 if (m_WatcherList
.GetCount() < MAXIMUM_WAIT_OBJECTS
)
342 m_WatcherList
.AddTail(Icon
);
343 SetEvent(m_WakeUpEvent
);
347 LeaveCriticalSection(&m_ListLock
);
357 bool CIconWatcher::RemoveIconFromWatcher(_In_ CONST NOTIFYICONDATA
*iconData
)
359 EnterCriticalSection(&m_ListLock
);
361 IconWatcherData
*Icon
;
362 Icon
= GetListEntry(iconData
, NULL
, true);
364 SetEvent(m_WakeUpEvent
);
365 LeaveCriticalSection(&m_ListLock
);
371 IconWatcherData
* CIconWatcher::GetListEntry(_In_opt_ CONST NOTIFYICONDATA
*iconData
, _In_opt_ HANDLE hProcess
, _In_
bool Remove
)
373 IconWatcherData
*Entry
= NULL
;
374 POSITION NextPosition
= m_WatcherList
.GetHeadPosition();
378 Position
= NextPosition
;
380 Entry
= m_WatcherList
.GetNext(NextPosition
);
383 if ((iconData
&& ((Entry
->IconData
.hWnd
== iconData
->hWnd
) && (Entry
->IconData
.uID
== iconData
->uID
))) ||
384 (hProcess
&& (Entry
->hProcess
== hProcess
)))
387 m_WatcherList
.RemoveAt(Position
);
393 } while (NextPosition
!= NULL
);
398 UINT WINAPI
CIconWatcher::WatcherThread(_In_opt_ LPVOID lpParam
)
400 CIconWatcher
* This
= reinterpret_cast<CIconWatcher
*>(lpParam
);
401 HANDLE
*WatchList
= NULL
;
406 EnterCriticalSection(&This
->m_ListLock
);
409 Size
= This
->m_WatcherList
.GetCount() + 1;
410 ASSERT(Size
<= MAXIMUM_WAIT_OBJECTS
);
414 WatchList
= new HANDLE
[Size
];
415 WatchList
[0] = This
->m_WakeUpEvent
;
418 for (size_t i
= 0; i
< This
->m_WatcherList
.GetCount(); i
++)
420 Pos
= This
->m_WatcherList
.FindIndex(i
);
423 IconWatcherData
*Icon
;
424 Icon
= This
->m_WatcherList
.GetAt(Pos
);
425 WatchList
[i
+ 1] = Icon
->hProcess
;
429 LeaveCriticalSection(&This
->m_ListLock
);
432 Status
= WaitForMultipleObjects(Size
,
436 if (Status
== WAIT_OBJECT_0
)
438 // We've been kicked, we have updates to our list (or we're exiting the thread)
440 TRACE("Updating watched icon list");
442 else if ((Status
>= WAIT_OBJECT_0
+ 1) && (Status
< Size
))
444 IconWatcherData
*Icon
;
445 Icon
= This
->GetListEntry(NULL
, WatchList
[Status
], false);
447 TRACE("Pid %lu owns a notification icon and has stopped without deleting it. We'll cleanup on its behalf", Icon
->ProcessId
);
449 TRAYNOTIFYDATAW tnid
= {0};
450 tnid
.dwSignature
= NI_NOTIFY_SIG
;
451 tnid
.dwMessage
= NIM_DELETE
;
452 CopyMemory(&tnid
.nid
, &Icon
->IconData
, Icon
->IconData
.cbSize
);
456 data
.cbData
= sizeof(tnid
);
459 BOOL Success
= ::SendMessage(This
->m_hwndSysTray
, WM_COPYDATA
,
460 (WPARAM
)&Icon
->IconData
, (LPARAM
)&data
);
463 // If we failed to handle the delete message, forcibly remove it
464 This
->RemoveIconFromWatcher(&Icon
->IconData
);
469 if (Status
== WAIT_FAILED
)
471 Status
= GetLastError();
473 ERR("Failed to wait on process handles : %lu\n", Status
);
474 This
->Uninitialize();
488 CBalloonQueue::CBalloonQueue() :
493 m_currentClosed(false),
498 void CBalloonQueue::Init(HWND hwndParent
, CToolbar
<InternalIconData
> * toolbar
, CTooltips
* balloons
)
500 m_hwndParent
= hwndParent
;
502 m_tooltips
= balloons
;
505 void CBalloonQueue::Deinit()
509 ::KillTimer(m_hwndParent
, m_timer
);
513 bool CBalloonQueue::OnTimer(int timerId
)
515 if (timerId
!= m_timer
)
518 ::KillTimer(m_hwndParent
, m_timer
);
521 if (m_current
&& !m_currentClosed
)
528 m_currentClosed
= false;
529 if (!m_queue
.IsEmpty())
531 Info info
= m_queue
.RemoveHead();
539 void CBalloonQueue::UpdateInfo(InternalIconData
* notifyItem
)
542 HRESULT hr
= StringCchLength(notifyItem
->szInfo
, _countof(notifyItem
->szInfo
), &len
);
543 if (SUCCEEDED(hr
) && len
> 0)
545 Info
info(notifyItem
);
547 // If m_current == notifyItem, we want to replace the previous balloon even if there is a queue.
548 if (m_current
!= notifyItem
&& (m_current
!= NULL
|| !m_queue
.IsEmpty()))
550 m_queue
.AddTail(info
);
563 void CBalloonQueue::RemoveInfo(InternalIconData
* notifyItem
)
567 POSITION position
= m_queue
.GetHeadPosition();
568 while(position
!= NULL
)
570 Info
& info
= m_queue
.GetNext(position
);
571 if (info
.pSource
== notifyItem
)
573 m_queue
.RemoveAt(position
);
578 void CBalloonQueue::CloseCurrent()
580 if (m_current
!= NULL
)
584 int CBalloonQueue::IndexOf(InternalIconData
* pdata
)
586 int count
= m_toolbar
->GetButtonCount();
587 for (int i
= 0; i
< count
; i
++)
589 if (m_toolbar
->GetItemData(i
) == pdata
)
595 void CBalloonQueue::SetTimer(int length
)
597 m_timer
= ::SetTimer(m_hwndParent
, BalloonsTimerId
, length
, NULL
);
600 void CBalloonQueue::Show(Info
& info
)
602 TRACE("ShowBalloonTip called for flags=%x text=%ws; title=%ws\n", info
.uIcon
, info
.szInfo
, info
.szInfoTitle
);
604 // TODO: NIF_REALTIME, NIIF_NOSOUND, other Vista+ flags
606 const int index
= IndexOf(info
.pSource
);
608 m_toolbar
->GetItemRect(index
, &rc
);
609 m_toolbar
->ClientToScreen(&rc
);
610 const WORD x
= (rc
.left
+ rc
.right
) / 2;
611 const WORD y
= (rc
.top
+ rc
.bottom
) / 2;
613 m_tooltips
->SetTitle(info
.szInfoTitle
, info
.uIcon
);
614 m_tooltips
->TrackPosition(x
, y
);
615 m_tooltips
->UpdateTipText(m_hwndParent
, reinterpret_cast<LPARAM
>(m_toolbar
->m_hWnd
), info
.szInfo
);
616 m_tooltips
->TrackActivate(m_hwndParent
, reinterpret_cast<LPARAM
>(m_toolbar
->m_hWnd
));
618 m_current
= info
.pSource
;
619 int timeout
= info
.uTimeout
;
620 if (timeout
< MinTimeout
) timeout
= MinTimeout
;
621 if (timeout
> MaxTimeout
) timeout
= MaxTimeout
;
626 void CBalloonQueue::Close(IN OUT InternalIconData
* notifyItem
)
628 TRACE("HideBalloonTip called\n");
630 if (m_current
== notifyItem
&& !m_currentClosed
)
633 m_currentClosed
= true;
634 m_tooltips
->TrackDeactivate();
635 SetTimer(CooldownBetweenBalloons
);
643 CNotifyToolbar::CNotifyToolbar() :
645 m_VisibleButtonCount(0),
650 CNotifyToolbar::~CNotifyToolbar()
654 int CNotifyToolbar::GetVisibleButtonCount()
656 return m_VisibleButtonCount
;
659 int CNotifyToolbar::FindItem(IN HWND hWnd
, IN UINT uID
, InternalIconData
** pdata
)
661 int count
= GetButtonCount();
663 for (int i
= 0; i
< count
; i
++)
665 InternalIconData
* data
= GetItemData(i
);
667 if (data
->hWnd
== hWnd
&&
679 int CNotifyToolbar::FindExistingSharedIcon(HICON handle
)
681 int count
= GetButtonCount();
682 for (int i
= 0; i
< count
; i
++)
684 InternalIconData
* data
= GetItemData(i
);
685 if (data
->hIcon
== handle
)
696 BOOL
CNotifyToolbar::AddButton(_In_ CONST NOTIFYICONDATA
*iconData
)
699 InternalIconData
* notifyItem
;
702 TRACE("Adding icon %d from hWnd %08x flags%s%s state%s%s",
703 iconData
->uID
, iconData
->hWnd
,
704 (iconData
->uFlags
& NIF_ICON
) ? " ICON" : "",
705 (iconData
->uFlags
& NIF_STATE
) ? " STATE" : "",
706 (iconData
->dwState
& NIS_HIDDEN
) ? " HIDDEN" : "",
707 (iconData
->dwState
& NIS_SHAREDICON
) ? " SHARED" : "");
709 int index
= FindItem(iconData
->hWnd
, iconData
->uID
, ¬ifyItem
);
712 TRACE("Icon %d from hWnd %08x ALREADY EXISTS!", iconData
->uID
, iconData
->hWnd
);
716 notifyItem
= new InternalIconData();
717 ZeroMemory(notifyItem
, sizeof(*notifyItem
));
719 notifyItem
->hWnd
= iconData
->hWnd
;
720 notifyItem
->uID
= iconData
->uID
;
722 tbBtn
.fsState
= TBSTATE_ENABLED
;
723 tbBtn
.fsStyle
= BTNS_NOPREFIX
;
724 tbBtn
.dwData
= (DWORD_PTR
)notifyItem
;
725 tbBtn
.iString
= (INT_PTR
) text
;
726 tbBtn
.idCommand
= GetButtonCount();
728 if (iconData
->uFlags
& NIF_STATE
)
730 notifyItem
->dwState
= iconData
->dwState
& iconData
->dwStateMask
;
733 if (iconData
->uFlags
& NIF_MESSAGE
)
735 notifyItem
->uCallbackMessage
= iconData
->uCallbackMessage
;
738 if (iconData
->uFlags
& NIF_ICON
)
740 notifyItem
->hIcon
= iconData
->hIcon
;
741 BOOL hasSharedIcon
= notifyItem
->dwState
& NIS_SHAREDICON
;
744 INT iIcon
= FindExistingSharedIcon(notifyItem
->hIcon
);
747 notifyItem
->hIcon
= NULL
;
748 TRACE("Shared icon requested, but HICON not found!!!");
750 tbBtn
.iBitmap
= iIcon
;
754 tbBtn
.iBitmap
= ImageList_AddIcon(m_ImageList
, notifyItem
->hIcon
);
758 if (iconData
->uFlags
& NIF_TIP
)
760 StringCchCopy(notifyItem
->szTip
, _countof(notifyItem
->szTip
), iconData
->szTip
);
763 if (iconData
->uFlags
& NIF_INFO
)
765 // NOTE: In Vista+, the uTimeout value is disregarded, and the accessibility settings are used always.
766 StringCchCopy(notifyItem
->szInfo
, _countof(notifyItem
->szInfo
), iconData
->szInfo
);
767 StringCchCopy(notifyItem
->szInfoTitle
, _countof(notifyItem
->szInfoTitle
), iconData
->szInfoTitle
);
768 notifyItem
->dwInfoFlags
= iconData
->dwInfoFlags
;
769 notifyItem
->uTimeout
= iconData
->uTimeout
;
772 if (notifyItem
->dwState
& NIS_HIDDEN
)
774 tbBtn
.fsState
|= TBSTATE_HIDDEN
;
778 m_VisibleButtonCount
++;
781 /* TODO: support VERSION_4 (NIF_GUID, NIF_REALTIME, NIF_SHOWTIP) */
783 CToolbar::AddButton(&tbBtn
);
784 SetButtonSize(GetSystemMetrics(SM_CXSMICON
), GetSystemMetrics(SM_CYSMICON
));
786 if (iconData
->uFlags
& NIF_INFO
)
788 m_BalloonQueue
->UpdateInfo(notifyItem
);
794 BOOL
CNotifyToolbar::SwitchVersion(_In_ CONST NOTIFYICONDATA
*iconData
)
796 InternalIconData
* notifyItem
;
797 int index
= FindItem(iconData
->hWnd
, iconData
->uID
, ¬ifyItem
);
800 WARN("Icon %d from hWnd %08x DOES NOT EXIST!", iconData
->uID
, iconData
->hWnd
);
804 if (iconData
->uVersion
!= 0 && iconData
->uVersion
!= NOTIFYICON_VERSION
)
806 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
);
810 // We can not store the version in the uVersion field, because it's union'd with uTimeout,
811 // which we also need to keep track of.
812 notifyItem
->uVersionCopy
= iconData
->uVersion
;
817 BOOL
CNotifyToolbar::UpdateButton(_In_ CONST NOTIFYICONDATA
*iconData
)
819 InternalIconData
* notifyItem
;
820 TBBUTTONINFO tbbi
= { 0 };
822 TRACE("Updating icon %d from hWnd %08x flags%s%s state%s%s",
823 iconData
->uID
, iconData
->hWnd
,
824 (iconData
->uFlags
& NIF_ICON
) ? " ICON" : "",
825 (iconData
->uFlags
& NIF_STATE
) ? " STATE" : "",
826 (iconData
->dwState
& NIS_HIDDEN
) ? " HIDDEN" : "",
827 (iconData
->dwState
& NIS_SHAREDICON
) ? " SHARED" : "");
829 int index
= FindItem(iconData
->hWnd
, iconData
->uID
, ¬ifyItem
);
832 WARN("Icon %d from hWnd %08x DOES NOT EXIST!", iconData
->uID
, iconData
->hWnd
);
833 return AddButton(iconData
);
837 GetButton(index
, &btn
);
838 int oldIconIndex
= btn
.iBitmap
;
840 tbbi
.cbSize
= sizeof(tbbi
);
841 tbbi
.dwMask
= TBIF_BYINDEX
| TBIF_COMMAND
;
842 tbbi
.idCommand
= index
;
844 if (iconData
->uFlags
& NIF_STATE
)
846 if (iconData
->dwStateMask
& NIS_HIDDEN
&&
847 (notifyItem
->dwState
& NIS_HIDDEN
) != (iconData
->dwState
& NIS_HIDDEN
))
849 tbbi
.dwMask
|= TBIF_STATE
;
850 if (iconData
->dwState
& NIS_HIDDEN
)
852 tbbi
.fsState
|= TBSTATE_HIDDEN
;
853 m_VisibleButtonCount
--;
857 tbbi
.fsState
&= ~TBSTATE_HIDDEN
;
858 m_VisibleButtonCount
++;
862 notifyItem
->dwState
&= ~iconData
->dwStateMask
;
863 notifyItem
->dwState
|= (iconData
->dwState
& iconData
->dwStateMask
);
866 if (iconData
->uFlags
& NIF_MESSAGE
)
868 notifyItem
->uCallbackMessage
= iconData
->uCallbackMessage
;
871 if (iconData
->uFlags
& NIF_ICON
)
873 BOOL hasSharedIcon
= notifyItem
->dwState
& NIS_SHAREDICON
;
876 INT iIcon
= FindExistingSharedIcon(iconData
->hIcon
);
879 notifyItem
->hIcon
= iconData
->hIcon
;
880 tbbi
.dwMask
|= TBIF_IMAGE
;
885 TRACE("Shared icon requested, but HICON not found!!! IGNORING!");
890 notifyItem
->hIcon
= iconData
->hIcon
;
891 tbbi
.dwMask
|= TBIF_IMAGE
;
892 tbbi
.iImage
= ImageList_ReplaceIcon(m_ImageList
, oldIconIndex
, notifyItem
->hIcon
);
896 if (iconData
->uFlags
& NIF_TIP
)
898 StringCchCopy(notifyItem
->szTip
, _countof(notifyItem
->szTip
), iconData
->szTip
);
901 if (iconData
->uFlags
& NIF_INFO
)
903 // NOTE: In Vista+, the uTimeout value is disregarded, and the accessibility settings are used always.
904 StringCchCopy(notifyItem
->szInfo
, _countof(notifyItem
->szInfo
), iconData
->szInfo
);
905 StringCchCopy(notifyItem
->szInfoTitle
, _countof(notifyItem
->szInfoTitle
), iconData
->szInfoTitle
);
906 notifyItem
->dwInfoFlags
= iconData
->dwInfoFlags
;
907 notifyItem
->uTimeout
= iconData
->uTimeout
;
910 /* TODO: support VERSION_4 (NIF_GUID, NIF_REALTIME, NIF_SHOWTIP) */
912 SetButtonInfo(index
, &tbbi
);
914 if (iconData
->uFlags
& NIF_INFO
)
916 m_BalloonQueue
->UpdateInfo(notifyItem
);
922 BOOL
CNotifyToolbar::RemoveButton(_In_ CONST NOTIFYICONDATA
*iconData
)
924 InternalIconData
* notifyItem
;
926 TRACE("Removing icon %d from hWnd %08x", iconData
->uID
, iconData
->hWnd
);
928 int index
= FindItem(iconData
->hWnd
, iconData
->uID
, ¬ifyItem
);
931 TRACE("Icon %d from hWnd %08x ALREADY MISSING!", iconData
->uID
, iconData
->hWnd
);
936 if (!(notifyItem
->dwState
& NIS_HIDDEN
))
938 m_VisibleButtonCount
--;
941 if (!(notifyItem
->dwState
& NIS_SHAREDICON
))
944 GetButton(index
, &btn
);
945 int oldIconIndex
= btn
.iBitmap
;
946 ImageList_Remove(m_ImageList
, oldIconIndex
);
948 // Update other icons!
949 int count
= GetButtonCount();
950 for (int i
= 0; i
< count
; i
++)
955 if (btn
.iBitmap
> oldIconIndex
)
957 TBBUTTONINFO tbbi2
= { 0 };
958 tbbi2
.cbSize
= sizeof(tbbi2
);
959 tbbi2
.dwMask
= TBIF_BYINDEX
| TBIF_IMAGE
;
960 tbbi2
.iImage
= btn
.iBitmap
-1;
961 SetButtonInfo(i
, &tbbi2
);
966 m_BalloonQueue
->RemoveInfo(notifyItem
);
975 VOID
CNotifyToolbar::ResizeImagelist()
980 if (!ImageList_GetIconSize(m_ImageList
, &cx
, &cy
))
983 if (cx
== GetSystemMetrics(SM_CXSMICON
) && cy
== GetSystemMetrics(SM_CYSMICON
))
986 iml
= ImageList_Create(GetSystemMetrics(SM_CXSMICON
), GetSystemMetrics(SM_CYSMICON
), ILC_COLOR32
| ILC_MASK
, 0, 1000);
990 ImageList_Destroy(m_ImageList
);
992 SetImageList(m_ImageList
);
994 int count
= GetButtonCount();
995 for (int i
= 0; i
< count
; i
++)
997 InternalIconData
* data
= GetItemData(i
);
998 BOOL hasSharedIcon
= data
->dwState
& NIS_SHAREDICON
;
999 INT iIcon
= hasSharedIcon
? FindExistingSharedIcon(data
->hIcon
) : -1;
1001 iIcon
= ImageList_AddIcon(iml
, data
->hIcon
);
1002 TBBUTTONINFO tbbi
= { sizeof(tbbi
), TBIF_BYINDEX
| TBIF_IMAGE
, 0, iIcon
};
1003 SetButtonInfo(i
, &tbbi
);
1006 SetButtonSize(GetSystemMetrics(SM_CXSMICON
), GetSystemMetrics(SM_CYSMICON
));
1009 VOID
CNotifyToolbar::SendMouseEvent(IN WORD wIndex
, IN UINT uMsg
, IN WPARAM wParam
)
1011 static LPCWSTR eventNames
[] = {
1015 L
"WM_LBUTTONDBLCLK",
1018 L
"WM_RBUTTONDBLCLK",
1021 L
"WM_MBUTTONDBLCLK",
1028 InternalIconData
* notifyItem
= GetItemData(wIndex
);
1030 if (!::IsWindow(notifyItem
->hWnd
))
1032 // We detect and destroy icons with invalid handles only on mouse move over systray, same as MS does.
1033 // Alternatively we could search for them periodically (would waste more resources).
1034 TRACE("Destroying icon %d with invalid handle hWnd=%08x\n", notifyItem
->uID
, notifyItem
->hWnd
);
1036 RemoveButton(notifyItem
);
1038 /* Ask the parent to resize */
1039 NMHDR nmh
= {GetParent(), 0, NTNWM_REALIGN
};
1040 GetParent().SendMessage(WM_NOTIFY
, 0, (LPARAM
) &nmh
);
1045 if (uMsg
>= WM_MOUSEFIRST
&& uMsg
<= WM_MOUSELAST
)
1047 TRACE("Sending message %S from button %d to %p (msg=%x, w=%x, l=%x)...\n",
1048 eventNames
[uMsg
- WM_MOUSEFIRST
], wIndex
,
1049 notifyItem
->hWnd
, notifyItem
->uCallbackMessage
, notifyItem
->uID
, uMsg
);
1053 GetWindowThreadProcessId(notifyItem
->hWnd
, &pid
);
1055 if (pid
== GetCurrentProcessId() ||
1056 (uMsg
>= WM_MOUSEFIRST
&& uMsg
<= WM_MOUSELAST
))
1058 ::PostMessage(notifyItem
->hWnd
,
1059 notifyItem
->uCallbackMessage
,
1065 SendMessage(notifyItem
->hWnd
,
1066 notifyItem
->uCallbackMessage
,
1072 LRESULT
CNotifyToolbar::OnMouseEvent(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1074 POINT pt
= { GET_X_LPARAM(lParam
), GET_Y_LPARAM(lParam
) };
1076 INT iBtn
= HitTest(&pt
);
1080 SendMouseEvent(iBtn
, uMsg
, wParam
);
1087 static VOID
GetTooltipText(LPARAM data
, LPTSTR szTip
, DWORD cchTip
)
1089 InternalIconData
* notifyItem
= reinterpret_cast<InternalIconData
*>(data
);
1092 StringCchCopy(szTip
, cchTip
, notifyItem
->szTip
);
1096 StringCchCopy(szTip
, cchTip
, L
"");
1100 LRESULT
CNotifyToolbar::OnTooltipShow(INT uCode
, LPNMHDR hdr
, BOOL
& bHandled
)
1103 ::GetWindowRect(hdr
->hwndFrom
, &rcTip
);
1105 SIZE szTip
= { rcTip
.right
- rcTip
.left
, rcTip
.bottom
- rcTip
.top
};
1107 INT iBtn
= GetHotItem();
1111 MONITORINFO monInfo
= { 0 };
1112 HMONITOR hMon
= MonitorFromWindow(m_hWnd
, MONITOR_DEFAULTTONEAREST
);
1114 monInfo
.cbSize
= sizeof(monInfo
);
1117 GetMonitorInfo(hMon
, &monInfo
);
1119 ::GetWindowRect(GetDesktopWindow(), &monInfo
.rcMonitor
);
1121 GetItemRect(iBtn
, &rcItem
);
1123 POINT ptItem
= { rcItem
.left
, rcItem
.top
};
1124 SIZE szItem
= { rcItem
.right
- rcItem
.left
, rcItem
.bottom
- rcItem
.top
};
1125 ClientToScreen(&ptItem
);
1127 ptItem
.x
+= szItem
.cx
/ 2;
1128 ptItem
.y
-= szTip
.cy
;
1130 if (ptItem
.x
+ szTip
.cx
> monInfo
.rcMonitor
.right
)
1131 ptItem
.x
= monInfo
.rcMonitor
.right
- szTip
.cx
;
1133 if (ptItem
.y
+ szTip
.cy
> monInfo
.rcMonitor
.bottom
)
1134 ptItem
.y
= monInfo
.rcMonitor
.bottom
- szTip
.cy
;
1136 if (ptItem
.x
< monInfo
.rcMonitor
.left
)
1137 ptItem
.x
= monInfo
.rcMonitor
.left
;
1139 if (ptItem
.y
< monInfo
.rcMonitor
.top
)
1140 ptItem
.y
= monInfo
.rcMonitor
.top
;
1142 TRACE("ptItem { %d, %d }\n", ptItem
.x
, ptItem
.y
);
1144 ::SetWindowPos(hdr
->hwndFrom
, NULL
, ptItem
.x
, ptItem
.y
, 0, 0, SWP_NOSIZE
| SWP_NOZORDER
| SWP_NOACTIVATE
);
1153 void CNotifyToolbar::Initialize(HWND hWndParent
, CBalloonQueue
* queue
)
1155 m_BalloonQueue
= queue
;
1158 WS_CHILD
| WS_VISIBLE
| WS_CLIPCHILDREN
|
1159 TBSTYLE_FLAT
| TBSTYLE_TOOLTIPS
| TBSTYLE_WRAPABLE
| TBSTYLE_TRANSPARENT
|
1160 CCS_TOP
| CCS_NORESIZE
| CCS_NOPARENTALIGN
| CCS_NODIVIDER
;
1162 SubclassWindow(CToolbar::Create(hWndParent
, styles
));
1164 // Force the toolbar tooltips window to always show tooltips even if not foreground
1165 HWND tooltipsWnd
= (HWND
)SendMessageW(TB_GETTOOLTIPS
);
1168 ::SetWindowLong(tooltipsWnd
, GWL_STYLE
, ::GetWindowLong(tooltipsWnd
, GWL_STYLE
) | TTS_ALWAYSTIP
);
1171 SetWindowTheme(m_hWnd
, L
"TrayNotify", NULL
);
1173 m_ImageList
= ImageList_Create(GetSystemMetrics(SM_CXSMICON
), GetSystemMetrics(SM_CYSMICON
), ILC_COLOR32
| ILC_MASK
, 0, 1000);
1174 SetImageList(m_ImageList
);
1176 TBMETRICS tbm
= {sizeof(tbm
)};
1177 tbm
.dwMask
= TBMF_BARPAD
| TBMF_BUTTONSPACING
| TBMF_PAD
;
1182 tbm
.cxButtonSpacing
= 1;
1183 tbm
.cyButtonSpacing
= 1;
1186 SetButtonSize(GetSystemMetrics(SM_CXSMICON
), GetSystemMetrics(SM_CYSMICON
));
1192 const WCHAR szSysPagerWndClass
[] = L
"SysPager";
1194 CSysPagerWnd::CSysPagerWnd() {}
1195 CSysPagerWnd::~CSysPagerWnd() {}
1197 LRESULT
CSysPagerWnd::OnEraseBackground(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1199 HDC hdc
= (HDC
) wParam
;
1208 GetClientRect(&rect
);
1209 DrawThemeParentBackground(m_hWnd
, hdc
, &rect
);
1214 LRESULT
CSysPagerWnd::OnCreate(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1216 Toolbar
.Initialize(m_hWnd
, &m_BalloonQueue
);
1217 CIconWatcher::Initialize(m_hWnd
);
1219 HWND hWndTop
= GetAncestor(m_hWnd
, GA_ROOT
);
1221 m_Balloons
.Create(hWndTop
, TTS_NOPREFIX
| TTS_BALLOON
| TTS_CLOSE
);
1223 TOOLINFOW ti
= { 0 };
1224 ti
.cbSize
= TTTOOLINFOW_V1_SIZE
;
1225 ti
.uFlags
= TTF_TRACK
| TTF_IDISHWND
;
1226 ti
.uId
= reinterpret_cast<UINT_PTR
>(Toolbar
.m_hWnd
);
1231 BOOL ret
= m_Balloons
.AddTool(&ti
);
1234 WARN("AddTool failed, LastError=%d (probably meaningless unless non-zero)\n", GetLastError());
1237 m_BalloonQueue
.Init(m_hWnd
, &Toolbar
, &m_Balloons
);
1239 // Explicitly request running applications to re-register their systray icons
1240 ::SendNotifyMessageW(HWND_BROADCAST
,
1241 RegisterWindowMessageW(L
"TaskbarCreated"),
1247 LRESULT
CSysPagerWnd::OnDestroy(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1249 m_BalloonQueue
.Deinit();
1250 CIconWatcher::Uninitialize();
1254 BOOL
CSysPagerWnd::NotifyIcon(DWORD dwMessage
, _In_ CONST NOTIFYICONDATA
*iconData
)
1258 int VisibleButtonCount
= Toolbar
.GetVisibleButtonCount();
1260 TRACE("NotifyIcon received. Code=%d\n", dwMessage
);
1264 ret
= Toolbar
.AddButton(iconData
);
1267 (void)AddIconToWatcher(iconData
);
1271 ret
= Toolbar
.UpdateButton(iconData
);
1274 ret
= Toolbar
.RemoveButton(iconData
);
1277 (void)RemoveIconFromWatcher(iconData
);
1283 case NIM_SETVERSION
:
1284 ret
= Toolbar
.SwitchVersion(iconData
);
1286 TRACE("NotifyIcon received with unknown code %d.\n", dwMessage
);
1290 if (VisibleButtonCount
!= Toolbar
.GetVisibleButtonCount())
1292 /* Ask the parent to resize */
1293 NMHDR nmh
= {GetParent(), 0, NTNWM_REALIGN
};
1294 GetParent().SendMessage(WM_NOTIFY
, 0, (LPARAM
) &nmh
);
1300 void CSysPagerWnd::GetSize(IN BOOL IsHorizontal
, IN PSIZE size
)
1302 /* Get the ideal height or width */
1304 /* Unfortunately this doens't work correctly in ros */
1305 Toolbar
.GetIdealSize(!IsHorizontal
, size
);
1307 /* Make the reference dimension an exact multiple of the icon size */
1309 size
->cy
-= size
->cy
% GetSystemMetrics(SM_CYSMICON
);
1311 size
->cx
-= size
->cx
% GetSystemMetrics(SM_CXSMICON
);
1316 INT cyButton
= GetSystemMetrics(SM_CYSMICON
) + 2;
1317 INT cxButton
= GetSystemMetrics(SM_CXSMICON
) + 2;
1318 int VisibleButtonCount
= Toolbar
.GetVisibleButtonCount();
1322 rows
= max(size
->cy
/ cyButton
, 1);
1323 columns
= (VisibleButtonCount
+ rows
- 1) / rows
;
1327 columns
= max(size
->cx
/ cxButton
, 1);
1328 rows
= (VisibleButtonCount
+ columns
- 1) / columns
;
1330 size
->cx
= columns
* cxButton
;
1331 size
->cy
= rows
* cyButton
;
1335 LRESULT
CSysPagerWnd::OnGetInfoTip(INT uCode
, LPNMHDR hdr
, BOOL
& bHandled
)
1337 NMTBGETINFOTIPW
* nmtip
= (NMTBGETINFOTIPW
*) hdr
;
1338 GetTooltipText(nmtip
->lParam
, nmtip
->pszText
, nmtip
->cchTextMax
);
1342 LRESULT
CSysPagerWnd::OnCustomDraw(INT uCode
, LPNMHDR hdr
, BOOL
& bHandled
)
1344 NMCUSTOMDRAW
* cdraw
= (NMCUSTOMDRAW
*) hdr
;
1345 switch (cdraw
->dwDrawStage
)
1348 return CDRF_NOTIFYITEMDRAW
;
1350 case CDDS_ITEMPREPAINT
:
1351 return TBCDRF_NOBACKGROUND
| TBCDRF_NOEDGES
| TBCDRF_NOOFFSET
| TBCDRF_NOMARK
| TBCDRF_NOETCHEDEFFECT
;
1356 LRESULT
CSysPagerWnd::OnSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1360 szClient
.cx
= LOWORD(lParam
);
1361 szClient
.cy
= HIWORD(lParam
);
1363 Ret
= DefWindowProc(uMsg
, wParam
, lParam
);
1367 Toolbar
.SetWindowPos(NULL
, 0, 0, szClient
.cx
, szClient
.cy
, SWP_NOZORDER
);
1371 Toolbar
.GetClientRect(&rc
);
1373 SIZE szBar
= { rc
.right
- rc
.left
, rc
.bottom
- rc
.top
};
1375 INT xOff
= (szClient
.cx
- szBar
.cx
) / 2;
1376 INT yOff
= (szClient
.cy
- szBar
.cy
) / 2;
1378 Toolbar
.SetWindowPos(NULL
, xOff
, yOff
, szBar
.cx
, szBar
.cy
, SWP_NOZORDER
);
1383 LRESULT
CSysPagerWnd::OnCtxMenu(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1389 LRESULT
CSysPagerWnd::OnBalloonPop(UINT uCode
, LPNMHDR hdr
, BOOL
& bHandled
)
1391 m_BalloonQueue
.CloseCurrent();
1396 LRESULT
CSysPagerWnd::OnTimer(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1398 if (m_BalloonQueue
.OnTimer(wParam
))
1406 LRESULT
CSysPagerWnd::OnCopyData(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1408 PCOPYDATASTRUCT cpData
= (PCOPYDATASTRUCT
)lParam
;
1409 if (cpData
->dwData
== 1)
1411 /* A taskbar NotifyIcon notification */
1412 PTRAYNOTIFYDATAW pData
= (PTRAYNOTIFYDATAW
)cpData
->lpData
;
1413 if (pData
->dwSignature
== NI_NOTIFY_SIG
)
1414 return NotifyIcon(pData
->dwMessage
, &pData
->nid
);
1416 // TODO: Handle other types of taskbar notifications
1421 LRESULT
CSysPagerWnd::OnSettingChanged(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1423 if (wParam
== SPI_SETNONCLIENTMETRICS
)
1425 Toolbar
.ResizeImagelist();
1430 LRESULT
CSysPagerWnd::OnGetMinimumSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1432 GetSize((BOOL
)wParam
, (PSIZE
)lParam
);
1436 HRESULT
CSysPagerWnd::Initialize(IN HWND hWndParent
)
1438 /* Create the window. The tray window is going to move it to the correct
1439 position and resize it as needed. */
1440 DWORD dwStyle
= WS_CHILD
| WS_CLIPSIBLINGS
| WS_VISIBLE
;
1441 Create(hWndParent
, 0, NULL
, dwStyle
);
1445 SetWindowTheme(m_hWnd
, L
"TrayNotify", NULL
);
1450 HRESULT
CSysPagerWnd_CreateInstance(HWND hwndParent
, REFIID riid
, void **ppv
)
1452 return ShellObjectCreatorInit
<CSysPagerWnd
>(hwndParent
, riid
, ppv
);