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 // Data comes from shell32/systray.cpp -> TrayNotifyCDS_Dummy
25 typedef struct _SYS_PAGER_COPY_DATA
29 NOTIFYICONDATA nicon_data
;
30 } SYS_PAGER_COPY_DATA
, *PSYS_PAGER_COPY_DATA
;
32 struct InternalIconData
: NOTIFYICONDATA
34 // Must keep a separate copy since the original is unioned with uTimeout.
38 struct IconWatcherData
42 NOTIFYICONDATA IconData
;
44 IconWatcherData(CONST NOTIFYICONDATA
*iconData
) :
45 hProcess(NULL
), ProcessId(0)
47 IconData
.cbSize
= sizeof(NOTIFYICONDATA
);
48 IconData
.hWnd
= iconData
->hWnd
;
49 IconData
.uID
= iconData
->uID
;
50 IconData
.guidItem
= iconData
->guidItem
;
57 CloseHandle(hProcess
);
64 CAtlList
<IconWatcherData
*> m_WatcherList
;
65 CRITICAL_SECTION m_ListLock
;
66 HANDLE m_hWatcherThread
;
74 virtual ~CIconWatcher();
76 bool Initialize(_In_ HWND hWndParent
);
79 bool AddIconToWatcher(_In_ CONST NOTIFYICONDATA
*iconData
);
80 bool RemoveIconFromWatcher(_In_ CONST NOTIFYICONDATA
*iconData
);
82 IconWatcherData
* GetListEntry(_In_opt_ CONST NOTIFYICONDATA
*iconData
, _In_opt_ HANDLE hProcess
, _In_
bool Remove
);
86 static UINT WINAPI
WatcherThread(_In_opt_ LPVOID lpParam
);
92 static const int TimerInterval
= 2000;
93 static const int BalloonsTimerId
= 1;
94 static const int MinTimeout
= 10000;
95 static const int MaxTimeout
= 30000;
96 static const int CooldownBetweenBalloons
= 2000;
101 InternalIconData
* pSource
;
103 WCHAR szInfoTitle
[64];
107 Info(InternalIconData
* source
)
110 StringCchCopy(szInfo
, _countof(szInfo
), source
->szInfo
);
111 StringCchCopy(szInfoTitle
, _countof(szInfoTitle
), source
->szInfoTitle
);
112 uIcon
= source
->dwInfoFlags
& NIIF_ICON_MASK
;
113 if (source
->dwInfoFlags
== NIIF_USER
)
114 uIcon
= reinterpret_cast<WPARAM
>(source
->hIcon
);
115 uTimeout
= source
->uTimeout
;
121 CTooltips
* m_tooltips
;
123 CAtlList
<Info
> m_queue
;
125 CToolbar
<InternalIconData
> * m_toolbar
;
127 InternalIconData
* m_current
;
128 bool m_currentClosed
;
135 void Init(HWND hwndParent
, CToolbar
<InternalIconData
> * toolbar
, CTooltips
* balloons
);
138 bool OnTimer(int timerId
);
139 void UpdateInfo(InternalIconData
* notifyItem
);
140 void RemoveInfo(InternalIconData
* notifyItem
);
145 int IndexOf(InternalIconData
* pdata
);
146 void SetTimer(int length
);
147 void Show(Info
& info
);
148 void Close(IN OUT InternalIconData
* notifyItem
);
151 class CNotifyToolbar
:
152 public CWindowImplBaseT
< CToolbar
<InternalIconData
>, CControlWinTraits
>
154 HIMAGELIST m_ImageList
;
155 int m_VisibleButtonCount
;
157 CBalloonQueue
* m_BalloonQueue
;
161 virtual ~CNotifyToolbar();
163 int GetVisibleButtonCount();
164 int FindItem(IN HWND hWnd
, IN UINT uID
, InternalIconData
** pdata
);
165 int FindExistingSharedIcon(HICON handle
);
166 BOOL
AddButton(IN CONST NOTIFYICONDATA
*iconData
);
167 BOOL
SwitchVersion(IN CONST NOTIFYICONDATA
*iconData
);
168 BOOL
UpdateButton(IN CONST NOTIFYICONDATA
*iconData
);
169 BOOL
RemoveButton(IN CONST NOTIFYICONDATA
*iconData
);
170 VOID
ResizeImagelist();
173 VOID
SendMouseEvent(IN WORD wIndex
, IN UINT uMsg
, IN WPARAM wParam
);
174 LRESULT
OnMouseEvent(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
175 LRESULT
OnTooltipShow(INT uCode
, LPNMHDR hdr
, BOOL
& bHandled
);
178 BEGIN_MSG_MAP(CNotifyToolbar
)
179 MESSAGE_RANGE_HANDLER(WM_MOUSEFIRST
, WM_MOUSELAST
, OnMouseEvent
)
180 NOTIFY_CODE_HANDLER(TTN_SHOW
, OnTooltipShow
)
183 void Initialize(HWND hWndParent
, CBalloonQueue
* queue
);
186 extern const WCHAR szSysPagerWndClass
[];
189 public CComCoClass
<CSysPagerWnd
>,
190 public CComObjectRootEx
<CComMultiThreadModelNoCS
>,
191 public CWindowImpl
< CSysPagerWnd
, CWindow
, CControlWinTraits
>,
195 CNotifyToolbar Toolbar
;
196 CTooltips m_Balloons
;
197 CBalloonQueue m_BalloonQueue
;
201 virtual ~CSysPagerWnd();
203 LRESULT
DrawBackground(HDC hdc
);
204 LRESULT
OnEraseBackground(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
205 LRESULT
OnCreate(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
206 LRESULT
OnDestroy(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
207 LRESULT
OnGetInfoTip(INT uCode
, LPNMHDR hdr
, BOOL
& bHandled
);
208 LRESULT
OnCustomDraw(INT uCode
, LPNMHDR hdr
, BOOL
& bHandled
);
209 LRESULT
OnSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
210 LRESULT
OnCtxMenu(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
211 LRESULT
OnBalloonPop(UINT uCode
, LPNMHDR hdr
, BOOL
& bHandled
);
212 LRESULT
OnTimer(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
213 LRESULT
OnCopyData(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
214 LRESULT
OnSettingChanged(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
215 LRESULT
OnGetMinimumSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
);
219 HRESULT WINAPI
GetWindow(HWND
* phwnd
)
227 HRESULT WINAPI
ContextSensitiveHelp(BOOL fEnterMode
)
232 DECLARE_NOT_AGGREGATABLE(CSysPagerWnd
)
234 DECLARE_PROTECT_FINAL_CONSTRUCT()
235 BEGIN_COM_MAP(CSysPagerWnd
)
236 COM_INTERFACE_ENTRY_IID(IID_IOleWindow
, IOleWindow
)
239 BOOL
NotifyIcon(DWORD notify_code
, _In_ CONST NOTIFYICONDATA
*iconData
);
240 void GetSize(IN BOOL IsHorizontal
, IN PSIZE size
);
242 DECLARE_WND_CLASS_EX(szSysPagerWndClass
, CS_DBLCLKS
, COLOR_3DFACE
)
244 BEGIN_MSG_MAP(CSysPagerWnd
)
245 MESSAGE_HANDLER(WM_CREATE
, OnCreate
)
246 MESSAGE_HANDLER(WM_DESTROY
, OnDestroy
)
247 MESSAGE_HANDLER(WM_ERASEBKGND
, OnEraseBackground
)
248 MESSAGE_HANDLER(WM_SIZE
, OnSize
)
249 MESSAGE_HANDLER(WM_CONTEXTMENU
, OnCtxMenu
)
250 MESSAGE_HANDLER(WM_TIMER
, OnTimer
)
251 MESSAGE_HANDLER(WM_COPYDATA
, OnCopyData
)
252 MESSAGE_HANDLER(WM_SETTINGCHANGE
, OnSettingChanged
)
253 MESSAGE_HANDLER(TNWM_GETMINIMUMSIZE
, OnGetMinimumSize
)
254 NOTIFY_CODE_HANDLER(TTN_POP
, OnBalloonPop
)
255 NOTIFY_CODE_HANDLER(TBN_GETINFOTIPW
, OnGetInfoTip
)
256 NOTIFY_CODE_HANDLER(NM_CUSTOMDRAW
, OnCustomDraw
)
259 HRESULT
Initialize(IN HWND hWndParent
);
262 CIconWatcher::CIconWatcher() :
263 m_hWatcherThread(NULL
),
270 CIconWatcher::~CIconWatcher()
273 DeleteCriticalSection(&m_ListLock
);
276 CloseHandle(m_WakeUpEvent
);
277 if (m_hWatcherThread
)
278 CloseHandle(m_hWatcherThread
);
281 bool CIconWatcher::Initialize(_In_ HWND hWndParent
)
283 m_hwndSysTray
= hWndParent
;
285 InitializeCriticalSection(&m_ListLock
);
286 m_WakeUpEvent
= CreateEventW(NULL
, FALSE
, FALSE
, NULL
);
287 if (m_WakeUpEvent
== NULL
)
290 m_hWatcherThread
= (HANDLE
)_beginthreadex(NULL
,
296 if (m_hWatcherThread
== NULL
)
302 void CIconWatcher::Uninitialize()
306 SetEvent(m_WakeUpEvent
);
308 EnterCriticalSection(&m_ListLock
);
311 for (size_t i
= 0; i
< m_WatcherList
.GetCount(); i
++)
313 Pos
= m_WatcherList
.FindIndex(i
);
316 IconWatcherData
*Icon
;
317 Icon
= m_WatcherList
.GetAt(Pos
);
321 m_WatcherList
.RemoveAll();
323 LeaveCriticalSection(&m_ListLock
);
326 bool CIconWatcher::AddIconToWatcher(_In_ CONST NOTIFYICONDATA
*iconData
)
329 (void)GetWindowThreadProcessId(iconData
->hWnd
, &ProcessId
);
332 hProcess
= OpenProcess(SYNCHRONIZE
, FALSE
, ProcessId
);
333 if (hProcess
== NULL
)
338 IconWatcherData
*Icon
= new IconWatcherData(iconData
);
339 Icon
->hProcess
= hProcess
;
343 EnterCriticalSection(&m_ListLock
);
345 // The likelyhood of someone having more than 64 icons in their tray is
346 // pretty slim. We could spin up a new thread for each multiple of 64, but
347 // it's not worth the effort, so we just won't bother watching those icons
348 if (m_WatcherList
.GetCount() < MAXIMUM_WAIT_OBJECTS
)
350 m_WatcherList
.AddTail(Icon
);
351 SetEvent(m_WakeUpEvent
);
355 LeaveCriticalSection(&m_ListLock
);
365 bool CIconWatcher::RemoveIconFromWatcher(_In_ CONST NOTIFYICONDATA
*iconData
)
367 EnterCriticalSection(&m_ListLock
);
369 IconWatcherData
*Icon
;
370 Icon
= GetListEntry(iconData
, NULL
, true);
372 SetEvent(m_WakeUpEvent
);
373 LeaveCriticalSection(&m_ListLock
);
379 IconWatcherData
* CIconWatcher::GetListEntry(_In_opt_ CONST NOTIFYICONDATA
*iconData
, _In_opt_ HANDLE hProcess
, _In_
bool Remove
)
381 IconWatcherData
*Entry
= NULL
;
382 POSITION NextPosition
= m_WatcherList
.GetHeadPosition();
386 Position
= NextPosition
;
388 Entry
= m_WatcherList
.GetNext(NextPosition
);
391 if ((iconData
&& ((Entry
->IconData
.hWnd
== iconData
->hWnd
) && (Entry
->IconData
.uID
== iconData
->uID
))) ||
392 (hProcess
&& (Entry
->hProcess
== hProcess
)))
395 m_WatcherList
.RemoveAt(Position
);
401 } while (NextPosition
!= NULL
);
406 UINT WINAPI
CIconWatcher::WatcherThread(_In_opt_ LPVOID lpParam
)
408 CIconWatcher
* This
= reinterpret_cast<CIconWatcher
*>(lpParam
);
409 HANDLE
*WatchList
= NULL
;
414 EnterCriticalSection(&This
->m_ListLock
);
417 Size
= This
->m_WatcherList
.GetCount() + 1;
418 ASSERT(Size
<= MAXIMUM_WAIT_OBJECTS
);
422 WatchList
= new HANDLE
[Size
];
423 WatchList
[0] = This
->m_WakeUpEvent
;
426 for (size_t i
= 0; i
< This
->m_WatcherList
.GetCount(); i
++)
428 Pos
= This
->m_WatcherList
.FindIndex(i
);
431 IconWatcherData
*Icon
;
432 Icon
= This
->m_WatcherList
.GetAt(Pos
);
433 WatchList
[i
+ 1] = Icon
->hProcess
;
437 LeaveCriticalSection(&This
->m_ListLock
);
440 Status
= WaitForMultipleObjects(Size
,
444 if (Status
== WAIT_OBJECT_0
)
446 // We've been kicked, we have updates to our list (or we're exiting the thread)
448 TRACE("Updating watched icon list");
450 else if ((Status
>= WAIT_OBJECT_0
+ 1) && (Status
< Size
))
452 IconWatcherData
*Icon
;
453 Icon
= This
->GetListEntry(NULL
, WatchList
[Status
], false);
455 TRACE("Pid %lu owns a notification icon and has stopped without deleting it. We'll cleanup on its behalf", Icon
->ProcessId
);
457 int len
= FIELD_OFFSET(SYS_PAGER_COPY_DATA
, nicon_data
) + Icon
->IconData
.cbSize
;
458 PSYS_PAGER_COPY_DATA pnotify_data
= (PSYS_PAGER_COPY_DATA
)new BYTE
[len
];
459 pnotify_data
->cookie
= 1;
460 pnotify_data
->notify_code
= NIM_DELETE
;
461 memcpy(&pnotify_data
->nicon_data
, &Icon
->IconData
, Icon
->IconData
.cbSize
);
466 data
.lpData
= pnotify_data
;
468 BOOL Success
= FALSE
;
469 ::SendMessage(This
->m_hwndSysTray
, WM_COPYDATA
, (WPARAM
)&Icon
->IconData
, (LPARAM
)&data
);
475 // If we failed to handle the delete message, forcibly remove it
476 This
->RemoveIconFromWatcher(&Icon
->IconData
);
481 if (Status
== WAIT_FAILED
)
483 Status
= GetLastError();
485 ERR("Failed to wait on process handles : %lu\n", Status
);
486 This
->Uninitialize();
500 CBalloonQueue::CBalloonQueue() :
505 m_currentClosed(false),
510 void CBalloonQueue::Init(HWND hwndParent
, CToolbar
<InternalIconData
> * toolbar
, CTooltips
* balloons
)
512 m_hwndParent
= hwndParent
;
514 m_tooltips
= balloons
;
517 void CBalloonQueue::Deinit()
521 ::KillTimer(m_hwndParent
, m_timer
);
525 bool CBalloonQueue::OnTimer(int timerId
)
527 if (timerId
!= m_timer
)
530 ::KillTimer(m_hwndParent
, m_timer
);
533 if (m_current
&& !m_currentClosed
)
540 m_currentClosed
= false;
541 if (!m_queue
.IsEmpty())
543 Info info
= m_queue
.RemoveHead();
551 void CBalloonQueue::UpdateInfo(InternalIconData
* notifyItem
)
554 HRESULT hr
= StringCchLength(notifyItem
->szInfo
, _countof(notifyItem
->szInfo
), &len
);
555 if (SUCCEEDED(hr
) && len
> 0)
557 Info
info(notifyItem
);
559 // If m_current == notifyItem, we want to replace the previous balloon even if there is a queue.
560 if (m_current
!= notifyItem
&& (m_current
!= NULL
|| !m_queue
.IsEmpty()))
562 m_queue
.AddTail(info
);
575 void CBalloonQueue::RemoveInfo(InternalIconData
* notifyItem
)
579 POSITION position
= m_queue
.GetHeadPosition();
580 while(position
!= NULL
)
582 Info
& info
= m_queue
.GetNext(position
);
583 if (info
.pSource
== notifyItem
)
585 m_queue
.RemoveAt(position
);
590 void CBalloonQueue::CloseCurrent()
592 if (m_current
!= NULL
)
596 int CBalloonQueue::IndexOf(InternalIconData
* pdata
)
598 int count
= m_toolbar
->GetButtonCount();
599 for (int i
= 0; i
< count
; i
++)
601 if (m_toolbar
->GetItemData(i
) == pdata
)
607 void CBalloonQueue::SetTimer(int length
)
609 m_timer
= ::SetTimer(m_hwndParent
, BalloonsTimerId
, length
, NULL
);
612 void CBalloonQueue::Show(Info
& info
)
614 TRACE("ShowBalloonTip called for flags=%x text=%ws; title=%ws\n", info
.uIcon
, info
.szInfo
, info
.szInfoTitle
);
616 // TODO: NIF_REALTIME, NIIF_NOSOUND, other Vista+ flags
618 const int index
= IndexOf(info
.pSource
);
620 m_toolbar
->GetItemRect(index
, &rc
);
621 m_toolbar
->ClientToScreen(&rc
);
622 const WORD x
= (rc
.left
+ rc
.right
) / 2;
623 const WORD y
= (rc
.top
+ rc
.bottom
) / 2;
625 m_tooltips
->SetTitle(info
.szInfoTitle
, info
.uIcon
);
626 m_tooltips
->TrackPosition(x
, y
);
627 m_tooltips
->UpdateTipText(m_hwndParent
, reinterpret_cast<LPARAM
>(m_toolbar
->m_hWnd
), info
.szInfo
);
628 m_tooltips
->TrackActivate(m_hwndParent
, reinterpret_cast<LPARAM
>(m_toolbar
->m_hWnd
));
630 m_current
= info
.pSource
;
631 int timeout
= info
.uTimeout
;
632 if (timeout
< MinTimeout
) timeout
= MinTimeout
;
633 if (timeout
> MaxTimeout
) timeout
= MaxTimeout
;
638 void CBalloonQueue::Close(IN OUT InternalIconData
* notifyItem
)
640 TRACE("HideBalloonTip called\n");
642 if (m_current
== notifyItem
&& !m_currentClosed
)
645 m_currentClosed
= true;
646 m_tooltips
->TrackDeactivate();
647 SetTimer(CooldownBetweenBalloons
);
655 CNotifyToolbar::CNotifyToolbar() :
657 m_VisibleButtonCount(0),
662 CNotifyToolbar::~CNotifyToolbar()
666 int CNotifyToolbar::GetVisibleButtonCount()
668 return m_VisibleButtonCount
;
671 int CNotifyToolbar::FindItem(IN HWND hWnd
, IN UINT uID
, InternalIconData
** pdata
)
673 int count
= GetButtonCount();
675 for (int i
= 0; i
< count
; i
++)
677 InternalIconData
* data
= GetItemData(i
);
679 if (data
->hWnd
== hWnd
&&
691 int CNotifyToolbar::FindExistingSharedIcon(HICON handle
)
693 int count
= GetButtonCount();
694 for (int i
= 0; i
< count
; i
++)
696 InternalIconData
* data
= GetItemData(i
);
697 if (data
->hIcon
== handle
)
708 BOOL
CNotifyToolbar::AddButton(_In_ CONST NOTIFYICONDATA
*iconData
)
711 InternalIconData
* notifyItem
;
714 TRACE("Adding icon %d from hWnd %08x flags%s%s state%s%s",
715 iconData
->uID
, iconData
->hWnd
,
716 (iconData
->uFlags
& NIF_ICON
) ? " ICON" : "",
717 (iconData
->uFlags
& NIF_STATE
) ? " STATE" : "",
718 (iconData
->dwState
& NIS_HIDDEN
) ? " HIDDEN" : "",
719 (iconData
->dwState
& NIS_SHAREDICON
) ? " SHARED" : "");
721 int index
= FindItem(iconData
->hWnd
, iconData
->uID
, ¬ifyItem
);
724 TRACE("Icon %d from hWnd %08x ALREADY EXISTS!", iconData
->uID
, iconData
->hWnd
);
728 notifyItem
= new InternalIconData();
729 ZeroMemory(notifyItem
, sizeof(*notifyItem
));
731 notifyItem
->hWnd
= iconData
->hWnd
;
732 notifyItem
->uID
= iconData
->uID
;
734 tbBtn
.fsState
= TBSTATE_ENABLED
;
735 tbBtn
.fsStyle
= BTNS_NOPREFIX
;
736 tbBtn
.dwData
= (DWORD_PTR
)notifyItem
;
737 tbBtn
.iString
= (INT_PTR
) text
;
738 tbBtn
.idCommand
= GetButtonCount();
740 if (iconData
->uFlags
& NIF_STATE
)
742 notifyItem
->dwState
= iconData
->dwState
& iconData
->dwStateMask
;
745 if (iconData
->uFlags
& NIF_MESSAGE
)
747 notifyItem
->uCallbackMessage
= iconData
->uCallbackMessage
;
750 if (iconData
->uFlags
& NIF_ICON
)
752 notifyItem
->hIcon
= iconData
->hIcon
;
753 BOOL hasSharedIcon
= notifyItem
->dwState
& NIS_SHAREDICON
;
756 INT iIcon
= FindExistingSharedIcon(notifyItem
->hIcon
);
759 notifyItem
->hIcon
= NULL
;
760 TRACE("Shared icon requested, but HICON not found!!!");
762 tbBtn
.iBitmap
= iIcon
;
766 tbBtn
.iBitmap
= ImageList_AddIcon(m_ImageList
, notifyItem
->hIcon
);
770 if (iconData
->uFlags
& NIF_TIP
)
772 StringCchCopy(notifyItem
->szTip
, _countof(notifyItem
->szTip
), iconData
->szTip
);
775 if (iconData
->uFlags
& NIF_INFO
)
777 // NOTE: In Vista+, the uTimeout value is disregarded, and the accessibility settings are used always.
778 StringCchCopy(notifyItem
->szInfo
, _countof(notifyItem
->szInfo
), iconData
->szInfo
);
779 StringCchCopy(notifyItem
->szInfoTitle
, _countof(notifyItem
->szInfoTitle
), iconData
->szInfoTitle
);
780 notifyItem
->dwInfoFlags
= iconData
->dwInfoFlags
;
781 notifyItem
->uTimeout
= iconData
->uTimeout
;
784 if (notifyItem
->dwState
& NIS_HIDDEN
)
786 tbBtn
.fsState
|= TBSTATE_HIDDEN
;
790 m_VisibleButtonCount
++;
793 /* TODO: support VERSION_4 (NIF_GUID, NIF_REALTIME, NIF_SHOWTIP) */
795 CToolbar::AddButton(&tbBtn
);
796 SetButtonSize(GetSystemMetrics(SM_CXSMICON
), GetSystemMetrics(SM_CYSMICON
));
798 if (iconData
->uFlags
& NIF_INFO
)
800 m_BalloonQueue
->UpdateInfo(notifyItem
);
806 BOOL
CNotifyToolbar::SwitchVersion(_In_ CONST NOTIFYICONDATA
*iconData
)
808 InternalIconData
* notifyItem
;
809 int index
= FindItem(iconData
->hWnd
, iconData
->uID
, ¬ifyItem
);
812 WARN("Icon %d from hWnd %08x DOES NOT EXIST!", iconData
->uID
, iconData
->hWnd
);
816 if (iconData
->uVersion
!= 0 && iconData
->uVersion
!= NOTIFYICON_VERSION
)
818 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
);
822 // We can not store the version in the uVersion field, because it's union'd with uTimeout,
823 // which we also need to keep track of.
824 notifyItem
->uVersionCopy
= iconData
->uVersion
;
829 BOOL
CNotifyToolbar::UpdateButton(_In_ CONST NOTIFYICONDATA
*iconData
)
831 InternalIconData
* notifyItem
;
832 TBBUTTONINFO tbbi
= { 0 };
834 TRACE("Updating icon %d from hWnd %08x flags%s%s state%s%s",
835 iconData
->uID
, iconData
->hWnd
,
836 (iconData
->uFlags
& NIF_ICON
) ? " ICON" : "",
837 (iconData
->uFlags
& NIF_STATE
) ? " STATE" : "",
838 (iconData
->dwState
& NIS_HIDDEN
) ? " HIDDEN" : "",
839 (iconData
->dwState
& NIS_SHAREDICON
) ? " SHARED" : "");
841 int index
= FindItem(iconData
->hWnd
, iconData
->uID
, ¬ifyItem
);
844 WARN("Icon %d from hWnd %08x DOES NOT EXIST!", iconData
->uID
, iconData
->hWnd
);
845 return AddButton(iconData
);
849 GetButton(index
, &btn
);
850 int oldIconIndex
= btn
.iBitmap
;
852 tbbi
.cbSize
= sizeof(tbbi
);
853 tbbi
.dwMask
= TBIF_BYINDEX
| TBIF_COMMAND
;
854 tbbi
.idCommand
= index
;
856 if (iconData
->uFlags
& NIF_STATE
)
858 if (iconData
->dwStateMask
& NIS_HIDDEN
&&
859 (notifyItem
->dwState
& NIS_HIDDEN
) != (iconData
->dwState
& NIS_HIDDEN
))
861 tbbi
.dwMask
|= TBIF_STATE
;
862 if (iconData
->dwState
& NIS_HIDDEN
)
864 tbbi
.fsState
|= TBSTATE_HIDDEN
;
865 m_VisibleButtonCount
--;
869 tbbi
.fsState
&= ~TBSTATE_HIDDEN
;
870 m_VisibleButtonCount
++;
874 notifyItem
->dwState
&= ~iconData
->dwStateMask
;
875 notifyItem
->dwState
|= (iconData
->dwState
& iconData
->dwStateMask
);
878 if (iconData
->uFlags
& NIF_MESSAGE
)
880 notifyItem
->uCallbackMessage
= iconData
->uCallbackMessage
;
883 if (iconData
->uFlags
& NIF_ICON
)
885 BOOL hasSharedIcon
= notifyItem
->dwState
& NIS_SHAREDICON
;
888 INT iIcon
= FindExistingSharedIcon(iconData
->hIcon
);
891 notifyItem
->hIcon
= iconData
->hIcon
;
892 tbbi
.dwMask
|= TBIF_IMAGE
;
897 TRACE("Shared icon requested, but HICON not found!!! IGNORING!");
902 notifyItem
->hIcon
= iconData
->hIcon
;
903 tbbi
.dwMask
|= TBIF_IMAGE
;
904 tbbi
.iImage
= ImageList_ReplaceIcon(m_ImageList
, oldIconIndex
, notifyItem
->hIcon
);
908 if (iconData
->uFlags
& NIF_TIP
)
910 StringCchCopy(notifyItem
->szTip
, _countof(notifyItem
->szTip
), iconData
->szTip
);
913 if (iconData
->uFlags
& NIF_INFO
)
915 // NOTE: In Vista+, the uTimeout value is disregarded, and the accessibility settings are used always.
916 StringCchCopy(notifyItem
->szInfo
, _countof(notifyItem
->szInfo
), iconData
->szInfo
);
917 StringCchCopy(notifyItem
->szInfoTitle
, _countof(notifyItem
->szInfoTitle
), iconData
->szInfoTitle
);
918 notifyItem
->dwInfoFlags
= iconData
->dwInfoFlags
;
919 notifyItem
->uTimeout
= iconData
->uTimeout
;
922 /* TODO: support VERSION_4 (NIF_GUID, NIF_REALTIME, NIF_SHOWTIP) */
924 SetButtonInfo(index
, &tbbi
);
926 if (iconData
->uFlags
& NIF_INFO
)
928 m_BalloonQueue
->UpdateInfo(notifyItem
);
934 BOOL
CNotifyToolbar::RemoveButton(_In_ CONST NOTIFYICONDATA
*iconData
)
936 InternalIconData
* notifyItem
;
938 TRACE("Removing icon %d from hWnd %08x", iconData
->uID
, iconData
->hWnd
);
940 int index
= FindItem(iconData
->hWnd
, iconData
->uID
, ¬ifyItem
);
943 TRACE("Icon %d from hWnd %08x ALREADY MISSING!", iconData
->uID
, iconData
->hWnd
);
948 if (!(notifyItem
->dwState
& NIS_HIDDEN
))
950 m_VisibleButtonCount
--;
953 if (!(notifyItem
->dwState
& NIS_SHAREDICON
))
956 GetButton(index
, &btn
);
957 int oldIconIndex
= btn
.iBitmap
;
958 ImageList_Remove(m_ImageList
, oldIconIndex
);
960 // Update other icons!
961 int count
= GetButtonCount();
962 for (int i
= 0; i
< count
; i
++)
967 if (btn
.iBitmap
> oldIconIndex
)
969 TBBUTTONINFO tbbi2
= { 0 };
970 tbbi2
.cbSize
= sizeof(tbbi2
);
971 tbbi2
.dwMask
= TBIF_BYINDEX
| TBIF_IMAGE
;
972 tbbi2
.iImage
= btn
.iBitmap
-1;
973 SetButtonInfo(i
, &tbbi2
);
978 m_BalloonQueue
->RemoveInfo(notifyItem
);
987 VOID
CNotifyToolbar::ResizeImagelist()
992 if (!ImageList_GetIconSize(m_ImageList
, &cx
, &cy
))
995 if (cx
== GetSystemMetrics(SM_CXSMICON
) && cy
== GetSystemMetrics(SM_CYSMICON
))
998 iml
= ImageList_Create(GetSystemMetrics(SM_CXSMICON
), GetSystemMetrics(SM_CYSMICON
), ILC_COLOR32
| ILC_MASK
, 0, 1000);
1002 ImageList_Destroy(m_ImageList
);
1004 SetImageList(m_ImageList
);
1006 int count
= GetButtonCount();
1007 for (int i
= 0; i
< count
; i
++)
1009 InternalIconData
* data
= GetItemData(i
);
1010 BOOL hasSharedIcon
= data
->dwState
& NIS_SHAREDICON
;
1011 INT iIcon
= hasSharedIcon
? FindExistingSharedIcon(data
->hIcon
) : -1;
1013 iIcon
= ImageList_AddIcon(iml
, data
->hIcon
);
1014 TBBUTTONINFO tbbi
= { sizeof(tbbi
), TBIF_BYINDEX
| TBIF_IMAGE
, 0, iIcon
};
1015 SetButtonInfo(i
, &tbbi
);
1018 SetButtonSize(GetSystemMetrics(SM_CXSMICON
), GetSystemMetrics(SM_CYSMICON
));
1021 VOID
CNotifyToolbar::SendMouseEvent(IN WORD wIndex
, IN UINT uMsg
, IN WPARAM wParam
)
1023 static LPCWSTR eventNames
[] = {
1027 L
"WM_LBUTTONDBLCLK",
1030 L
"WM_RBUTTONDBLCLK",
1033 L
"WM_MBUTTONDBLCLK",
1040 InternalIconData
* notifyItem
= GetItemData(wIndex
);
1042 if (!::IsWindow(notifyItem
->hWnd
))
1044 // We detect and destroy icons with invalid handles only on mouse move over systray, same as MS does.
1045 // Alternatively we could search for them periodically (would waste more resources).
1046 TRACE("Destroying icon %d with invalid handle hWnd=%08x\n", notifyItem
->uID
, notifyItem
->hWnd
);
1048 RemoveButton(notifyItem
);
1050 /* Ask the parent to resize */
1051 NMHDR nmh
= {GetParent(), 0, NTNWM_REALIGN
};
1052 GetParent().SendMessage(WM_NOTIFY
, 0, (LPARAM
) &nmh
);
1057 if (uMsg
>= WM_MOUSEFIRST
&& uMsg
<= WM_MOUSELAST
)
1059 TRACE("Sending message %S from button %d to %p (msg=%x, w=%x, l=%x)...\n",
1060 eventNames
[uMsg
- WM_MOUSEFIRST
], wIndex
,
1061 notifyItem
->hWnd
, notifyItem
->uCallbackMessage
, notifyItem
->uID
, uMsg
);
1065 GetWindowThreadProcessId(notifyItem
->hWnd
, &pid
);
1067 if (pid
== GetCurrentProcessId() ||
1068 (uMsg
>= WM_MOUSEFIRST
&& uMsg
<= WM_MOUSELAST
))
1070 ::PostMessage(notifyItem
->hWnd
,
1071 notifyItem
->uCallbackMessage
,
1077 SendMessage(notifyItem
->hWnd
,
1078 notifyItem
->uCallbackMessage
,
1084 LRESULT
CNotifyToolbar::OnMouseEvent(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1086 POINT pt
= { GET_X_LPARAM(lParam
), GET_Y_LPARAM(lParam
) };
1088 INT iBtn
= HitTest(&pt
);
1092 SendMouseEvent(iBtn
, uMsg
, wParam
);
1099 static VOID
GetTooltipText(LPARAM data
, LPTSTR szTip
, DWORD cchTip
)
1101 InternalIconData
* notifyItem
= reinterpret_cast<InternalIconData
*>(data
);
1104 StringCchCopy(szTip
, cchTip
, notifyItem
->szTip
);
1108 StringCchCopy(szTip
, cchTip
, L
"");
1112 LRESULT
CNotifyToolbar::OnTooltipShow(INT uCode
, LPNMHDR hdr
, BOOL
& bHandled
)
1115 ::GetWindowRect(hdr
->hwndFrom
, &rcTip
);
1117 SIZE szTip
= { rcTip
.right
- rcTip
.left
, rcTip
.bottom
- rcTip
.top
};
1119 INT iBtn
= GetHotItem();
1123 MONITORINFO monInfo
= { 0 };
1124 HMONITOR hMon
= MonitorFromWindow(m_hWnd
, MONITOR_DEFAULTTONEAREST
);
1126 monInfo
.cbSize
= sizeof(monInfo
);
1129 GetMonitorInfo(hMon
, &monInfo
);
1131 ::GetWindowRect(GetDesktopWindow(), &monInfo
.rcMonitor
);
1133 GetItemRect(iBtn
, &rcItem
);
1135 POINT ptItem
= { rcItem
.left
, rcItem
.top
};
1136 SIZE szItem
= { rcItem
.right
- rcItem
.left
, rcItem
.bottom
- rcItem
.top
};
1137 ClientToScreen(&ptItem
);
1139 ptItem
.x
+= szItem
.cx
/ 2;
1140 ptItem
.y
-= szTip
.cy
;
1142 if (ptItem
.x
+ szTip
.cx
> monInfo
.rcMonitor
.right
)
1143 ptItem
.x
= monInfo
.rcMonitor
.right
- szTip
.cx
;
1145 if (ptItem
.y
+ szTip
.cy
> monInfo
.rcMonitor
.bottom
)
1146 ptItem
.y
= monInfo
.rcMonitor
.bottom
- szTip
.cy
;
1148 if (ptItem
.x
< monInfo
.rcMonitor
.left
)
1149 ptItem
.x
= monInfo
.rcMonitor
.left
;
1151 if (ptItem
.y
< monInfo
.rcMonitor
.top
)
1152 ptItem
.y
= monInfo
.rcMonitor
.top
;
1154 TRACE("ptItem { %d, %d }\n", ptItem
.x
, ptItem
.y
);
1156 ::SetWindowPos(hdr
->hwndFrom
, NULL
, ptItem
.x
, ptItem
.y
, 0, 0, SWP_NOSIZE
| SWP_NOZORDER
| SWP_NOACTIVATE
);
1165 void CNotifyToolbar::Initialize(HWND hWndParent
, CBalloonQueue
* queue
)
1167 m_BalloonQueue
= queue
;
1170 WS_CHILD
| WS_VISIBLE
| WS_CLIPCHILDREN
|
1171 TBSTYLE_FLAT
| TBSTYLE_TOOLTIPS
| TBSTYLE_WRAPABLE
| TBSTYLE_TRANSPARENT
|
1172 CCS_TOP
| CCS_NORESIZE
| CCS_NOPARENTALIGN
| CCS_NODIVIDER
;
1174 SubclassWindow(CToolbar::Create(hWndParent
, styles
));
1176 // Force the toolbar tooltips window to always show tooltips even if not foreground
1177 HWND tooltipsWnd
= (HWND
)SendMessageW(TB_GETTOOLTIPS
);
1180 ::SetWindowLong(tooltipsWnd
, GWL_STYLE
, ::GetWindowLong(tooltipsWnd
, GWL_STYLE
) | TTS_ALWAYSTIP
);
1183 SetWindowTheme(m_hWnd
, L
"TrayNotify", NULL
);
1185 m_ImageList
= ImageList_Create(GetSystemMetrics(SM_CXSMICON
), GetSystemMetrics(SM_CYSMICON
), ILC_COLOR32
| ILC_MASK
, 0, 1000);
1186 SetImageList(m_ImageList
);
1188 TBMETRICS tbm
= {sizeof(tbm
)};
1189 tbm
.dwMask
= TBMF_BARPAD
| TBMF_BUTTONSPACING
| TBMF_PAD
;
1194 tbm
.cxButtonSpacing
= 1;
1195 tbm
.cyButtonSpacing
= 1;
1198 SetButtonSize(GetSystemMetrics(SM_CXSMICON
), GetSystemMetrics(SM_CYSMICON
));
1204 const WCHAR szSysPagerWndClass
[] = L
"SysPager";
1206 CSysPagerWnd::CSysPagerWnd() {}
1207 CSysPagerWnd::~CSysPagerWnd() {}
1209 LRESULT
CSysPagerWnd::OnEraseBackground(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1211 HDC hdc
= (HDC
) wParam
;
1220 GetClientRect(&rect
);
1221 DrawThemeParentBackground(m_hWnd
, hdc
, &rect
);
1226 LRESULT
CSysPagerWnd::OnCreate(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1228 Toolbar
.Initialize(m_hWnd
, &m_BalloonQueue
);
1229 CIconWatcher::Initialize(m_hWnd
);
1231 HWND hWndTop
= GetAncestor(m_hWnd
, GA_ROOT
);
1233 m_Balloons
.Create(hWndTop
, TTS_NOPREFIX
| TTS_BALLOON
| TTS_CLOSE
);
1235 TOOLINFOW ti
= { 0 };
1236 ti
.cbSize
= TTTOOLINFOW_V1_SIZE
;
1237 ti
.uFlags
= TTF_TRACK
| TTF_IDISHWND
;
1238 ti
.uId
= reinterpret_cast<UINT_PTR
>(Toolbar
.m_hWnd
);
1243 BOOL ret
= m_Balloons
.AddTool(&ti
);
1246 WARN("AddTool failed, LastError=%d (probably meaningless unless non-zero)\n", GetLastError());
1249 m_BalloonQueue
.Init(m_hWnd
, &Toolbar
, &m_Balloons
);
1251 // Explicitly request running applications to re-register their systray icons
1252 ::SendNotifyMessageW(HWND_BROADCAST
,
1253 RegisterWindowMessageW(L
"TaskbarCreated"),
1259 LRESULT
CSysPagerWnd::OnDestroy(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1261 m_BalloonQueue
.Deinit();
1262 CIconWatcher::Uninitialize();
1266 BOOL
CSysPagerWnd::NotifyIcon(DWORD notify_code
, _In_ CONST NOTIFYICONDATA
*iconData
)
1270 int VisibleButtonCount
= Toolbar
.GetVisibleButtonCount();
1272 TRACE("NotifyIcon received. Code=%d\n", notify_code
);
1273 switch (notify_code
)
1276 ret
= Toolbar
.AddButton(iconData
);
1279 (void)AddIconToWatcher(iconData
);
1283 ret
= Toolbar
.UpdateButton(iconData
);
1286 ret
= Toolbar
.RemoveButton(iconData
);
1289 (void)RemoveIconFromWatcher(iconData
);
1295 case NIM_SETVERSION
:
1296 ret
= Toolbar
.SwitchVersion(iconData
);
1298 TRACE("NotifyIcon received with unknown code %d.\n", notify_code
);
1302 if (VisibleButtonCount
!= Toolbar
.GetVisibleButtonCount())
1304 /* Ask the parent to resize */
1305 NMHDR nmh
= {GetParent(), 0, NTNWM_REALIGN
};
1306 GetParent().SendMessage(WM_NOTIFY
, 0, (LPARAM
) &nmh
);
1312 void CSysPagerWnd::GetSize(IN BOOL IsHorizontal
, IN PSIZE size
)
1314 /* Get the ideal height or width */
1316 /* Unfortunately this doens't work correctly in ros */
1317 Toolbar
.GetIdealSize(!IsHorizontal
, size
);
1319 /* Make the reference dimension an exact multiple of the icon size */
1321 size
->cy
-= size
->cy
% GetSystemMetrics(SM_CYSMICON
);
1323 size
->cx
-= size
->cx
% GetSystemMetrics(SM_CXSMICON
);
1328 INT cyButton
= GetSystemMetrics(SM_CYSMICON
) + 2;
1329 INT cxButton
= GetSystemMetrics(SM_CXSMICON
) + 2;
1330 int VisibleButtonCount
= Toolbar
.GetVisibleButtonCount();
1334 rows
= max(size
->cy
/ cyButton
, 1);
1335 columns
= (VisibleButtonCount
+ rows
- 1) / rows
;
1339 columns
= max(size
->cx
/ cxButton
, 1);
1340 rows
= (VisibleButtonCount
+ columns
- 1) / columns
;
1342 size
->cx
= columns
* cxButton
;
1343 size
->cy
= rows
* cyButton
;
1347 LRESULT
CSysPagerWnd::OnGetInfoTip(INT uCode
, LPNMHDR hdr
, BOOL
& bHandled
)
1349 NMTBGETINFOTIPW
* nmtip
= (NMTBGETINFOTIPW
*) hdr
;
1350 GetTooltipText(nmtip
->lParam
, nmtip
->pszText
, nmtip
->cchTextMax
);
1354 LRESULT
CSysPagerWnd::OnCustomDraw(INT uCode
, LPNMHDR hdr
, BOOL
& bHandled
)
1356 NMCUSTOMDRAW
* cdraw
= (NMCUSTOMDRAW
*) hdr
;
1357 switch (cdraw
->dwDrawStage
)
1360 return CDRF_NOTIFYITEMDRAW
;
1362 case CDDS_ITEMPREPAINT
:
1363 return TBCDRF_NOBACKGROUND
| TBCDRF_NOEDGES
| TBCDRF_NOOFFSET
| TBCDRF_NOMARK
| TBCDRF_NOETCHEDEFFECT
;
1368 LRESULT
CSysPagerWnd::OnSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1372 szClient
.cx
= LOWORD(lParam
);
1373 szClient
.cy
= HIWORD(lParam
);
1375 Ret
= DefWindowProc(uMsg
, wParam
, lParam
);
1379 Toolbar
.SetWindowPos(NULL
, 0, 0, szClient
.cx
, szClient
.cy
, SWP_NOZORDER
);
1383 Toolbar
.GetClientRect(&rc
);
1385 SIZE szBar
= { rc
.right
- rc
.left
, rc
.bottom
- rc
.top
};
1387 INT xOff
= (szClient
.cx
- szBar
.cx
) / 2;
1388 INT yOff
= (szClient
.cy
- szBar
.cy
) / 2;
1390 Toolbar
.SetWindowPos(NULL
, xOff
, yOff
, szBar
.cx
, szBar
.cy
, SWP_NOZORDER
);
1395 LRESULT
CSysPagerWnd::OnCtxMenu(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1401 LRESULT
CSysPagerWnd::OnBalloonPop(UINT uCode
, LPNMHDR hdr
, BOOL
& bHandled
)
1403 m_BalloonQueue
.CloseCurrent();
1408 LRESULT
CSysPagerWnd::OnTimer(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1410 if (m_BalloonQueue
.OnTimer(wParam
))
1418 LRESULT
CSysPagerWnd::OnCopyData(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1420 PCOPYDATASTRUCT cpData
= (PCOPYDATASTRUCT
)lParam
;
1421 if (cpData
->dwData
== 1)
1423 PSYS_PAGER_COPY_DATA pData
= (PSYS_PAGER_COPY_DATA
)cpData
->lpData
;
1424 return NotifyIcon(pData
->notify_code
, &pData
->nicon_data
);
1430 LRESULT
CSysPagerWnd::OnSettingChanged(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1432 if (wParam
== SPI_SETNONCLIENTMETRICS
)
1434 Toolbar
.ResizeImagelist();
1439 LRESULT
CSysPagerWnd::OnGetMinimumSize(UINT uMsg
, WPARAM wParam
, LPARAM lParam
, BOOL
& bHandled
)
1441 GetSize((BOOL
)wParam
, (PSIZE
)lParam
);
1445 HRESULT
CSysPagerWnd::Initialize(IN HWND hWndParent
)
1447 /* Create the window. The tray window is going to move it to the correct
1448 position and resize it as needed. */
1449 DWORD dwStyle
= WS_CHILD
| WS_CLIPSIBLINGS
| WS_VISIBLE
;
1450 Create(hWndParent
, 0, NULL
, dwStyle
);
1454 SetWindowTheme(m_hWnd
, L
"TrayNotify", NULL
);
1459 HRESULT
CSysPagerWnd_CreateInstance(HWND hwndParent
, REFIID riid
, void **ppv
)
1461 return ShellObjectCreatorInit
<CSysPagerWnd
>(hwndParent
, riid
, ppv
);