xref: /reactos/base/shell/explorer/syspager.cpp (revision 12e94103)
1 /*
2  * ReactOS Explorer
3  *
4  * Copyright 2006 - 2007 Thomas Weidenmueller <w3seek@reactos.org>
5  * Copyright 2018 Ged Murphy <gedmurphy@reactos.org>
6  *
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.
11  *
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.
16  *
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
20  */
21 
22 #include "precomp.h"
23 
24 struct InternalIconData : NOTIFYICONDATA
25 {
26     // Must keep a separate copy since the original is unioned with uTimeout.
27     UINT uVersionCopy;
28 };
29 
30 struct IconWatcherData
31 {
32     HANDLE hProcess;
33     DWORD ProcessId;
34     NOTIFYICONDATA IconData;
35 
36     IconWatcherData(CONST NOTIFYICONDATA *iconData) :
37         hProcess(NULL), ProcessId(0)
38     {
39         IconData.cbSize = sizeof(NOTIFYICONDATA);
40         IconData.hWnd = iconData->hWnd;
41         IconData.uID = iconData->uID;
42         IconData.guidItem = iconData->guidItem;
43     }
44 
45     ~IconWatcherData()
46     {
47         if (hProcess)
48         {
49             CloseHandle(hProcess);
50         }
51     }
52 };
53 
54 class CIconWatcher
55 {
56     CAtlList<IconWatcherData *> m_WatcherList;
57     CRITICAL_SECTION m_ListLock;
58     HANDLE m_hWatcherThread;
59     HANDLE m_WakeUpEvent;
60     HWND m_hwndSysTray;
61     bool m_Loop;
62 
63 public:
64     CIconWatcher();
65 
66     virtual ~CIconWatcher();
67 
68     bool Initialize(_In_ HWND hWndParent);
69     void Uninitialize();
70 
71     bool AddIconToWatcher(_In_ CONST NOTIFYICONDATA *iconData);
72     bool RemoveIconFromWatcher(_In_ CONST NOTIFYICONDATA *iconData);
73 
74     IconWatcherData* GetListEntry(_In_opt_ CONST NOTIFYICONDATA *iconData, _In_opt_ HANDLE hProcess, _In_ bool Remove);
75 
76 private:
77 
78     static UINT WINAPI WatcherThread(_In_opt_ LPVOID lpParam);
79 };
80 
81 class CNotifyToolbar;
82 
83 class CBalloonQueue
84 {
85 public:
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;
91 
92 private:
93     struct Info
94     {
95         InternalIconData * pSource;
96         WCHAR szInfo[256];
97         WCHAR szInfoTitle[64];
98         WPARAM uIcon;
99         UINT uTimeout;
100 
101         Info(InternalIconData * source)
102         {
103             pSource = 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;
110         }
111     };
112 
113     HWND m_hwndParent;
114 
115     CTooltips * m_tooltips;
116 
117     CAtlList<Info> m_queue;
118 
119     CNotifyToolbar * m_toolbar;
120 
121     InternalIconData * m_current;
122     bool m_currentClosed;
123 
124     int m_timer;
125 
126 public:
127     CBalloonQueue();
128 
129     void Init(HWND hwndParent, CNotifyToolbar * toolbar, CTooltips * balloons);
130     void Deinit();
131 
132     bool OnTimer(int timerId);
133     void UpdateInfo(InternalIconData * notifyItem);
134     void RemoveInfo(InternalIconData * notifyItem);
135     void CloseCurrent();
136 
137 private:
138 
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);
143 };
144 
145 class CNotifyToolbar :
146     public CWindowImplBaseT< CToolbar<InternalIconData>, CControlWinTraits >
147 {
148     HIMAGELIST m_ImageList;
149     int m_VisibleButtonCount;
150 
151     CBalloonQueue * m_BalloonQueue;
152 
153 public:
154     CNotifyToolbar();
155     virtual ~CNotifyToolbar();
156 
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);
166 
167 private:
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);
171 
172 public:
173     BEGIN_MSG_MAP(CNotifyToolbar)
174         MESSAGE_RANGE_HANDLER(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseEvent)
175         NOTIFY_CODE_HANDLER(TTN_SHOW, OnTooltipShow)
176     END_MSG_MAP()
177 
178     void Initialize(HWND hWndParent, CBalloonQueue * queue);
179 };
180 
181 extern const WCHAR szSysPagerWndClass[];
182 
183 class CSysPagerWnd :
184     public CComCoClass<CSysPagerWnd>,
185     public CComObjectRootEx<CComMultiThreadModelNoCS>,
186     public CWindowImpl < CSysPagerWnd, CWindow, CControlWinTraits >,
187     public IOleWindow,
188     public CIconWatcher
189 {
190     CNotifyToolbar Toolbar;
191     CTooltips m_Balloons;
192     CBalloonQueue m_BalloonQueue;
193 
194 public:
195     CSysPagerWnd();
196     virtual ~CSysPagerWnd();
197 
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);
211 
212 public:
213 
214     HRESULT WINAPI GetWindow(HWND* phwnd)
215     {
216         if (!phwnd)
217             return E_INVALIDARG;
218         *phwnd = m_hWnd;
219         return S_OK;
220     }
221 
222     HRESULT WINAPI ContextSensitiveHelp(BOOL fEnterMode)
223     {
224         return E_NOTIMPL;
225     }
226 
227     DECLARE_NOT_AGGREGATABLE(CSysPagerWnd)
228 
229     DECLARE_PROTECT_FINAL_CONSTRUCT()
230     BEGIN_COM_MAP(CSysPagerWnd)
231         COM_INTERFACE_ENTRY_IID(IID_IOleWindow, IOleWindow)
232     END_COM_MAP()
233 
234     BOOL NotifyIcon(DWORD dwMessage, _In_ CONST NOTIFYICONDATA *iconData);
235     void GetSize(IN BOOL IsHorizontal, IN PSIZE size);
236 
237     DECLARE_WND_CLASS_EX(szSysPagerWndClass, CS_DBLCLKS, COLOR_3DFACE)
238 
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)
252     END_MSG_MAP()
253 
254     HRESULT Initialize(IN HWND hWndParent);
255 };
256 
257 CIconWatcher::CIconWatcher() :
258     m_hWatcherThread(NULL),
259     m_WakeUpEvent(NULL),
260     m_hwndSysTray(NULL),
261     m_Loop(false)
262 {
263 }
264 
265 CIconWatcher::~CIconWatcher()
266 {
267     Uninitialize();
268     DeleteCriticalSection(&m_ListLock);
269 
270     if (m_WakeUpEvent)
271         CloseHandle(m_WakeUpEvent);
272     if (m_hWatcherThread)
273         CloseHandle(m_hWatcherThread);
274 }
275 
276 bool CIconWatcher::Initialize(_In_ HWND hWndParent)
277 {
278     m_hwndSysTray = hWndParent;
279 
280     InitializeCriticalSection(&m_ListLock);
281     m_WakeUpEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
282     if (m_WakeUpEvent == NULL)
283         return false;
284 
285     m_hWatcherThread = (HANDLE)_beginthreadex(NULL,
286                                                 0,
287                                                 WatcherThread,
288                                                 (LPVOID)this,
289                                                 0,
290                                                 NULL);
291     if (m_hWatcherThread == NULL)
292         return false;
293 
294     return true;
295 }
296 
297 void CIconWatcher::Uninitialize()
298 {
299     m_Loop = false;
300     if (m_WakeUpEvent)
301         SetEvent(m_WakeUpEvent);
302 
303     EnterCriticalSection(&m_ListLock);
304 
305     POSITION Pos;
306     for (size_t i = 0; i < m_WatcherList.GetCount(); i++)
307     {
308         Pos = m_WatcherList.FindIndex(i);
309         if (Pos)
310         {
311             IconWatcherData *Icon;
312             Icon = m_WatcherList.GetAt(Pos);
313             delete Icon;
314         }
315     }
316     m_WatcherList.RemoveAll();
317 
318     LeaveCriticalSection(&m_ListLock);
319 }
320 
321 bool CIconWatcher::AddIconToWatcher(_In_ CONST NOTIFYICONDATA *iconData)
322 {
323     DWORD ProcessId;
324     (void)GetWindowThreadProcessId(iconData->hWnd, &ProcessId);
325 
326     HANDLE hProcess;
327     hProcess = OpenProcess(SYNCHRONIZE, FALSE, ProcessId);
328     if (hProcess == NULL)
329     {
330         return false;
331     }
332 
333     IconWatcherData *Icon = new IconWatcherData(iconData);
334     Icon->hProcess = hProcess;
335     Icon->ProcessId = ProcessId;
336 
337     bool Added = false;
338     EnterCriticalSection(&m_ListLock);
339 
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)
344     {
345         m_WatcherList.AddTail(Icon);
346         SetEvent(m_WakeUpEvent);
347         Added = true;
348     }
349 
350     LeaveCriticalSection(&m_ListLock);
351 
352     if (!Added)
353     {
354         delete Icon;
355     }
356 
357     return Added;
358 }
359 
360 bool CIconWatcher::RemoveIconFromWatcher(_In_ CONST NOTIFYICONDATA *iconData)
361 {
362     EnterCriticalSection(&m_ListLock);
363 
364     IconWatcherData *Icon;
365     Icon = GetListEntry(iconData, NULL, true);
366 
367     SetEvent(m_WakeUpEvent);
368     LeaveCriticalSection(&m_ListLock);
369 
370     delete Icon;
371     return true;
372 }
373 
374 IconWatcherData* CIconWatcher::GetListEntry(_In_opt_ CONST NOTIFYICONDATA *iconData, _In_opt_ HANDLE hProcess, _In_ bool Remove)
375 {
376     IconWatcherData *Entry = NULL;
377     POSITION NextPosition = m_WatcherList.GetHeadPosition();
378     POSITION Position;
379     do
380     {
381         Position = NextPosition;
382 
383         Entry = m_WatcherList.GetNext(NextPosition);
384         if (Entry)
385         {
386             if ((iconData && ((Entry->IconData.hWnd == iconData->hWnd) && (Entry->IconData.uID == iconData->uID))) ||
387                     (hProcess && (Entry->hProcess == hProcess)))
388             {
389                 if (Remove)
390                     m_WatcherList.RemoveAt(Position);
391                 break;
392             }
393         }
394         Entry = NULL;
395 
396     } while (NextPosition != NULL);
397 
398     return Entry;
399 }
400 
401 UINT WINAPI CIconWatcher::WatcherThread(_In_opt_ LPVOID lpParam)
402 {
403     CIconWatcher* This = reinterpret_cast<CIconWatcher *>(lpParam);
404     HANDLE *WatchList = NULL;
405 
406     This->m_Loop = true;
407     while (This->m_Loop)
408     {
409         EnterCriticalSection(&This->m_ListLock);
410 
411         DWORD Size;
412         Size = This->m_WatcherList.GetCount() + 1;
413         ASSERT(Size <= MAXIMUM_WAIT_OBJECTS);
414 
415         if (WatchList)
416             delete[] WatchList;
417         WatchList = new HANDLE[Size];
418         WatchList[0] = This->m_WakeUpEvent;
419 
420         POSITION Pos;
421         for (size_t i = 0; i < This->m_WatcherList.GetCount(); i++)
422         {
423             Pos = This->m_WatcherList.FindIndex(i);
424             if (Pos)
425             {
426                 IconWatcherData *Icon;
427                 Icon = This->m_WatcherList.GetAt(Pos);
428                 WatchList[i + 1] = Icon->hProcess;
429             }
430         }
431 
432         LeaveCriticalSection(&This->m_ListLock);
433 
434         DWORD Status;
435         Status = WaitForMultipleObjects(Size,
436                                         WatchList,
437                                         FALSE,
438                                         INFINITE);
439         if (Status == WAIT_OBJECT_0)
440         {
441             // We've been kicked, we have updates to our list (or we're exiting the thread)
442             if (This->m_Loop)
443                 TRACE("Updating watched icon list");
444         }
445         else if ((Status >= WAIT_OBJECT_0 + 1) && (Status < Size))
446         {
447             IconWatcherData *Icon;
448             Icon = This->GetListEntry(NULL, WatchList[Status], false);
449 
450             TRACE("Pid %lu owns a notification icon and has stopped without deleting it. We'll cleanup on its behalf", Icon->ProcessId);
451 
452             TRAYNOTIFYDATAW tnid = {0};
453             tnid.dwSignature = NI_NOTIFY_SIG;
454             tnid.dwMessage   = NIM_DELETE;
455             CopyMemory(&tnid.nid, &Icon->IconData, Icon->IconData.cbSize);
456 
457             COPYDATASTRUCT data;
458             data.dwData = 1;
459             data.cbData = sizeof(tnid);
460             data.lpData = &tnid;
461 
462             BOOL Success = ::SendMessage(This->m_hwndSysTray, WM_COPYDATA,
463                                          (WPARAM)&Icon->IconData, (LPARAM)&data);
464             if (!Success)
465             {
466                 // If we failed to handle the delete message, forcibly remove it
467                 This->RemoveIconFromWatcher(&Icon->IconData);
468             }
469         }
470         else
471         {
472             if (Status == WAIT_FAILED)
473             {
474                 Status = GetLastError();
475             }
476             ERR("Failed to wait on process handles : %lu\n", Status);
477             This->Uninitialize();
478         }
479     }
480 
481     if (WatchList)
482         delete[] WatchList;
483 
484     return 0;
485 }
486 
487 /*
488 * NotifyToolbar
489 */
490 
491 CBalloonQueue::CBalloonQueue() :
492     m_hwndParent(NULL),
493     m_tooltips(NULL),
494     m_toolbar(NULL),
495     m_current(NULL),
496     m_currentClosed(false),
497     m_timer(-1)
498 {
499 }
500 
501 void CBalloonQueue::Init(HWND hwndParent, CNotifyToolbar * toolbar, CTooltips * balloons)
502 {
503     m_hwndParent = hwndParent;
504     m_toolbar = toolbar;
505     m_tooltips = balloons;
506 }
507 
508 void CBalloonQueue::Deinit()
509 {
510     if (m_timer >= 0)
511     {
512         ::KillTimer(m_hwndParent, m_timer);
513     }
514 }
515 
516 bool CBalloonQueue::OnTimer(int timerId)
517 {
518     if (timerId != m_timer)
519         return false;
520 
521     ::KillTimer(m_hwndParent, m_timer);
522     m_timer = -1;
523 
524     if (m_current && !m_currentClosed)
525     {
526         Close(m_current, NIN_BALLOONTIMEOUT);
527     }
528     else
529     {
530         m_current = NULL;
531         m_currentClosed = false;
532         if (!m_queue.IsEmpty())
533         {
534             Info info = m_queue.RemoveHead();
535             Show(info);
536         }
537     }
538 
539     return true;
540 }
541 
542 void CBalloonQueue::UpdateInfo(InternalIconData * notifyItem)
543 {
544     size_t len = 0;
545     HRESULT hr = StringCchLength(notifyItem->szInfo, _countof(notifyItem->szInfo), &len);
546     if (SUCCEEDED(hr) && len > 0)
547     {
548         Info info(notifyItem);
549 
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()))
552         {
553             m_queue.AddTail(info);
554         }
555         else
556         {
557             Show(info);
558         }
559     }
560     else
561     {
562         Close(notifyItem, NIN_BALLOONHIDE);
563     }
564 }
565 
566 void CBalloonQueue::RemoveInfo(InternalIconData * notifyItem)
567 {
568     Close(notifyItem, NIN_BALLOONHIDE);
569 
570     POSITION position = m_queue.GetHeadPosition();
571     while(position != NULL)
572     {
573         Info& info = m_queue.GetNext(position);
574         if (info.pSource == notifyItem)
575         {
576             m_queue.RemoveAt(position);
577         }
578     }
579 }
580 
581 void CBalloonQueue::CloseCurrent()
582 {
583     if (m_current != NULL)
584     {
585         Close(m_current, NIN_BALLOONTIMEOUT);
586     }
587 }
588 
589 int CBalloonQueue::IndexOf(InternalIconData * pdata)
590 {
591     int count = m_toolbar->GetButtonCount();
592     for (int i = 0; i < count; i++)
593     {
594         if (m_toolbar->GetItemData(i) == pdata)
595             return i;
596     }
597     return -1;
598 }
599 
600 void CBalloonQueue::SetTimer(int length)
601 {
602     m_timer = ::SetTimer(m_hwndParent, BalloonsTimerId, length, NULL);
603 }
604 
605 void CBalloonQueue::Show(Info& info)
606 {
607     TRACE("ShowBalloonTip called for flags=%x text=%ws; title=%ws\n", info.uIcon, info.szInfo, info.szInfoTitle);
608 
609     // TODO: NIF_REALTIME, NIIF_NOSOUND, other Vista+ flags
610 
611     const int index = IndexOf(info.pSource);
612     RECT rc;
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;
617 
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));
622 
623     m_current = info.pSource;
624     int timeout = info.uTimeout;
625     if (timeout < MinTimeout) timeout = MinTimeout;
626     if (timeout > MaxTimeout) timeout = MaxTimeout;
627 
628     SetTimer(timeout);
629 
630     m_toolbar->SendNotifyCallback(m_current, NIN_BALLOONSHOW);
631 }
632 
633 void CBalloonQueue::Close(IN OUT InternalIconData * notifyItem, IN UINT uReason)
634 {
635     TRACE("HideBalloonTip called\n");
636 
637     if (m_current == notifyItem && !m_currentClosed)
638     {
639         m_toolbar->SendNotifyCallback(m_current, uReason);
640 
641         // Prevent Re-entry
642         m_currentClosed = true;
643         m_tooltips->TrackDeactivate();
644         SetTimer(CooldownBetweenBalloons);
645     }
646 }
647 
648 /*
649  * NotifyToolbar
650  */
651 
652 CNotifyToolbar::CNotifyToolbar() :
653     m_ImageList(NULL),
654     m_VisibleButtonCount(0),
655     m_BalloonQueue(NULL)
656 {
657 }
658 
659 CNotifyToolbar::~CNotifyToolbar()
660 {
661 }
662 
663 int CNotifyToolbar::GetVisibleButtonCount()
664 {
665     return m_VisibleButtonCount;
666 }
667 
668 int CNotifyToolbar::FindItem(IN HWND hWnd, IN UINT uID, InternalIconData ** pdata)
669 {
670     int count = GetButtonCount();
671 
672     for (int i = 0; i < count; i++)
673     {
674         InternalIconData * data = GetItemData(i);
675 
676         if (data->hWnd == hWnd &&
677             data->uID == uID)
678         {
679             if (pdata)
680                 *pdata = data;
681             return i;
682         }
683     }
684 
685     return -1;
686 }
687 
688 int CNotifyToolbar::FindExistingSharedIcon(HICON handle)
689 {
690     int count = GetButtonCount();
691     for (int i = 0; i < count; i++)
692     {
693         InternalIconData * data = GetItemData(i);
694         if (data->hIcon == handle)
695         {
696             TBBUTTON btn;
697             GetButton(i, &btn);
698             return btn.iBitmap;
699         }
700     }
701 
702     return -1;
703 }
704 
705 BOOL CNotifyToolbar::AddButton(_In_ CONST NOTIFYICONDATA *iconData)
706 {
707     TBBUTTON tbBtn = { 0 };
708     InternalIconData * notifyItem;
709     WCHAR text[] = L"";
710 
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" : "");
717 
718     int index = FindItem(iconData->hWnd, iconData->uID, &notifyItem);
719     if (index >= 0)
720     {
721         TRACE("Icon %d from hWnd %08x ALREADY EXISTS!", iconData->uID, iconData->hWnd);
722         return FALSE;
723     }
724 
725     notifyItem = new InternalIconData();
726     ZeroMemory(notifyItem, sizeof(*notifyItem));
727 
728     notifyItem->hWnd = iconData->hWnd;
729     notifyItem->uID = iconData->uID;
730 
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();
736 
737     if (iconData->uFlags & NIF_STATE)
738     {
739         notifyItem->dwState = iconData->dwState & iconData->dwStateMask;
740     }
741 
742     if (iconData->uFlags & NIF_MESSAGE)
743     {
744         notifyItem->uCallbackMessage = iconData->uCallbackMessage;
745     }
746 
747     if (iconData->uFlags & NIF_ICON)
748     {
749         notifyItem->hIcon = iconData->hIcon;
750         BOOL hasSharedIcon = notifyItem->dwState & NIS_SHAREDICON;
751         if (hasSharedIcon)
752         {
753             INT iIcon = FindExistingSharedIcon(notifyItem->hIcon);
754             if (iIcon < 0)
755             {
756                 notifyItem->hIcon = NULL;
757                 TRACE("Shared icon requested, but HICON not found!!!");
758             }
759             tbBtn.iBitmap = iIcon;
760         }
761         else
762         {
763             tbBtn.iBitmap = ImageList_AddIcon(m_ImageList, notifyItem->hIcon);
764         }
765     }
766 
767     if (iconData->uFlags & NIF_TIP)
768     {
769         StringCchCopy(notifyItem->szTip, _countof(notifyItem->szTip), iconData->szTip);
770     }
771 
772     if (iconData->uFlags & NIF_INFO)
773     {
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;
779     }
780 
781     if (notifyItem->dwState & NIS_HIDDEN)
782     {
783         tbBtn.fsState |= TBSTATE_HIDDEN;
784     }
785     else
786     {
787         m_VisibleButtonCount++;
788     }
789 
790     /* TODO: support VERSION_4 (NIF_GUID, NIF_REALTIME, NIF_SHOWTIP) */
791 
792     CToolbar::AddButton(&tbBtn);
793     SetButtonSize(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
794 
795     if (iconData->uFlags & NIF_INFO)
796     {
797         m_BalloonQueue->UpdateInfo(notifyItem);
798     }
799 
800     return TRUE;
801 }
802 
803 BOOL CNotifyToolbar::SwitchVersion(_In_ CONST NOTIFYICONDATA *iconData)
804 {
805     InternalIconData * notifyItem;
806     int index = FindItem(iconData->hWnd, iconData->uID, &notifyItem);
807     if (index < 0)
808     {
809         WARN("Icon %d from hWnd %08x DOES NOT EXIST!", iconData->uID, iconData->hWnd);
810         return FALSE;
811     }
812 
813     if (iconData->uVersion != 0 && iconData->uVersion != NOTIFYICON_VERSION)
814     {
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);
816         return FALSE;
817     }
818 
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;
822 
823     return TRUE;
824 }
825 
826 BOOL CNotifyToolbar::UpdateButton(_In_ CONST NOTIFYICONDATA *iconData)
827 {
828     InternalIconData * notifyItem;
829     TBBUTTONINFO tbbi = { 0 };
830 
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" : "");
837 
838     int index = FindItem(iconData->hWnd, iconData->uID, &notifyItem);
839     if (index < 0)
840     {
841         WARN("Icon %d from hWnd %08x DOES NOT EXIST!", iconData->uID, iconData->hWnd);
842         return AddButton(iconData);
843     }
844 
845     TBBUTTON btn;
846     GetButton(index, &btn);
847     int oldIconIndex = btn.iBitmap;
848 
849     tbbi.cbSize = sizeof(tbbi);
850     tbbi.dwMask = TBIF_BYINDEX | TBIF_COMMAND;
851     tbbi.idCommand = index;
852 
853     if (iconData->uFlags & NIF_STATE)
854     {
855         if (iconData->dwStateMask & NIS_HIDDEN &&
856             (notifyItem->dwState & NIS_HIDDEN) != (iconData->dwState & NIS_HIDDEN))
857         {
858             tbbi.dwMask |= TBIF_STATE;
859             if (iconData->dwState & NIS_HIDDEN)
860             {
861                 tbbi.fsState |= TBSTATE_HIDDEN;
862                 m_VisibleButtonCount--;
863             }
864             else
865             {
866                 tbbi.fsState &= ~TBSTATE_HIDDEN;
867                 m_VisibleButtonCount++;
868             }
869         }
870 
871         notifyItem->dwState &= ~iconData->dwStateMask;
872         notifyItem->dwState |= (iconData->dwState & iconData->dwStateMask);
873     }
874 
875     if (iconData->uFlags & NIF_MESSAGE)
876     {
877         notifyItem->uCallbackMessage = iconData->uCallbackMessage;
878     }
879 
880     if (iconData->uFlags & NIF_ICON)
881     {
882         BOOL hasSharedIcon = notifyItem->dwState & NIS_SHAREDICON;
883         if (hasSharedIcon)
884         {
885             INT iIcon = FindExistingSharedIcon(iconData->hIcon);
886             if (iIcon >= 0)
887             {
888                 notifyItem->hIcon = iconData->hIcon;
889                 tbbi.dwMask |= TBIF_IMAGE;
890                 tbbi.iImage = iIcon;
891             }
892             else
893             {
894                 TRACE("Shared icon requested, but HICON not found!!! IGNORING!");
895             }
896         }
897         else
898         {
899             notifyItem->hIcon = iconData->hIcon;
900             tbbi.dwMask |= TBIF_IMAGE;
901             tbbi.iImage = ImageList_ReplaceIcon(m_ImageList, oldIconIndex, notifyItem->hIcon);
902         }
903     }
904 
905     if (iconData->uFlags & NIF_TIP)
906     {
907         StringCchCopy(notifyItem->szTip, _countof(notifyItem->szTip), iconData->szTip);
908     }
909 
910     if (iconData->uFlags & NIF_INFO)
911     {
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;
917     }
918 
919     /* TODO: support VERSION_4 (NIF_GUID, NIF_REALTIME, NIF_SHOWTIP) */
920 
921     SetButtonInfo(index, &tbbi);
922 
923     if (iconData->uFlags & NIF_INFO)
924     {
925         m_BalloonQueue->UpdateInfo(notifyItem);
926     }
927 
928     return TRUE;
929 }
930 
931 BOOL CNotifyToolbar::RemoveButton(_In_ CONST NOTIFYICONDATA *iconData)
932 {
933     InternalIconData * notifyItem;
934 
935     TRACE("Removing icon %d from hWnd %08x", iconData->uID, iconData->hWnd);
936 
937     int index = FindItem(iconData->hWnd, iconData->uID, &notifyItem);
938     if (index < 0)
939     {
940         TRACE("Icon %d from hWnd %08x ALREADY MISSING!", iconData->uID, iconData->hWnd);
941 
942         return FALSE;
943     }
944 
945     if (!(notifyItem->dwState & NIS_HIDDEN))
946     {
947         m_VisibleButtonCount--;
948     }
949 
950     if (!(notifyItem->dwState & NIS_SHAREDICON))
951     {
952         TBBUTTON btn;
953         GetButton(index, &btn);
954         int oldIconIndex = btn.iBitmap;
955         ImageList_Remove(m_ImageList, oldIconIndex);
956 
957         // Update other icons!
958         int count = GetButtonCount();
959         for (int i = 0; i < count; i++)
960         {
961             TBBUTTON btn;
962             GetButton(i, &btn);
963 
964             if (btn.iBitmap > oldIconIndex)
965             {
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);
971             }
972         }
973     }
974 
975     m_BalloonQueue->RemoveInfo(notifyItem);
976 
977     DeleteButton(index);
978 
979     delete notifyItem;
980 
981     return TRUE;
982 }
983 
984 VOID CNotifyToolbar::ResizeImagelist()
985 {
986     int cx, cy;
987     HIMAGELIST iml;
988 
989     if (!ImageList_GetIconSize(m_ImageList, &cx, &cy))
990         return;
991 
992     if (cx == GetSystemMetrics(SM_CXSMICON) && cy == GetSystemMetrics(SM_CYSMICON))
993         return;
994 
995     iml = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR32 | ILC_MASK, 0, 1000);
996     if (!iml)
997         return;
998 
999     ImageList_Destroy(m_ImageList);
1000     m_ImageList = iml;
1001     SetImageList(m_ImageList);
1002 
1003     int count = GetButtonCount();
1004     for (int i = 0; i < count; i++)
1005     {
1006         InternalIconData * data = GetItemData(i);
1007         BOOL hasSharedIcon = data->dwState & NIS_SHAREDICON;
1008         INT iIcon = hasSharedIcon ? FindExistingSharedIcon(data->hIcon) : -1;
1009         if (iIcon < 0)
1010             iIcon = ImageList_AddIcon(iml, data->hIcon);
1011         TBBUTTONINFO tbbi = { sizeof(tbbi), TBIF_BYINDEX | TBIF_IMAGE, 0, iIcon};
1012         SetButtonInfo(i, &tbbi);
1013     }
1014 
1015     SetButtonSize(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
1016 }
1017 
1018 bool CNotifyToolbar::SendNotifyCallback(InternalIconData* notifyItem, UINT uMsg)
1019 {
1020     if (!::IsWindow(notifyItem->hWnd))
1021     {
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);
1025 
1026         RemoveButton(notifyItem);
1027 
1028         /* Ask the parent to resize */
1029         NMHDR nmh = {GetParent(), 0, NTNWM_REALIGN};
1030         GetParent().SendMessage(WM_NOTIFY, 0, (LPARAM) &nmh);
1031 
1032         return true;
1033     }
1034 
1035     DWORD pid;
1036     GetWindowThreadProcessId(notifyItem->hWnd, &pid);
1037 
1038     if (pid == GetCurrentProcessId() ||
1039         (uMsg >= WM_MOUSEFIRST && uMsg <= WM_MOUSELAST))
1040     {
1041         ::PostMessage(notifyItem->hWnd,
1042                       notifyItem->uCallbackMessage,
1043                       notifyItem->uID,
1044                       uMsg);
1045     }
1046     else
1047     {
1048         SendMessage(notifyItem->hWnd,
1049                     notifyItem->uCallbackMessage,
1050                     notifyItem->uID,
1051                     uMsg);
1052     }
1053     return false;
1054 }
1055 
1056 VOID CNotifyToolbar::SendMouseEvent(IN WORD wIndex, IN UINT uMsg, IN WPARAM wParam)
1057 {
1058     static LPCWSTR eventNames [] = {
1059         L"WM_MOUSEMOVE",
1060         L"WM_LBUTTONDOWN",
1061         L"WM_LBUTTONUP",
1062         L"WM_LBUTTONDBLCLK",
1063         L"WM_RBUTTONDOWN",
1064         L"WM_RBUTTONUP",
1065         L"WM_RBUTTONDBLCLK",
1066         L"WM_MBUTTONDOWN",
1067         L"WM_MBUTTONUP",
1068         L"WM_MBUTTONDBLCLK",
1069         L"WM_MOUSEWHEEL",
1070         L"WM_XBUTTONDOWN",
1071         L"WM_XBUTTONUP",
1072         L"WM_XBUTTONDBLCLK"
1073     };
1074 
1075     InternalIconData * notifyItem = GetItemData(wIndex);
1076 
1077     if (uMsg >= WM_MOUSEFIRST && uMsg <= WM_MOUSELAST)
1078     {
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);
1082     }
1083 
1084     SendNotifyCallback(notifyItem, uMsg);
1085 }
1086 
1087 LRESULT CNotifyToolbar::OnMouseEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1088 {
1089     POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
1090 
1091     INT iBtn = HitTest(&pt);
1092 
1093     if (iBtn >= 0)
1094     {
1095         SendMouseEvent(iBtn, uMsg, wParam);
1096     }
1097 
1098     bHandled = FALSE;
1099     return FALSE;
1100 }
1101 
1102 static VOID GetTooltipText(LPARAM data, LPTSTR szTip, DWORD cchTip)
1103 {
1104     InternalIconData * notifyItem = reinterpret_cast<InternalIconData *>(data);
1105     if (notifyItem)
1106     {
1107         StringCchCopy(szTip, cchTip, notifyItem->szTip);
1108     }
1109     else
1110     {
1111         StringCchCopy(szTip, cchTip, L"");
1112     }
1113 }
1114 
1115 LRESULT CNotifyToolbar::OnTooltipShow(INT uCode, LPNMHDR hdr, BOOL& bHandled)
1116 {
1117     RECT rcTip, rcItem;
1118     ::GetWindowRect(hdr->hwndFrom, &rcTip);
1119 
1120     SIZE szTip = { rcTip.right - rcTip.left, rcTip.bottom - rcTip.top };
1121 
1122     INT iBtn = GetHotItem();
1123 
1124     if (iBtn >= 0)
1125     {
1126         MONITORINFO monInfo = { 0 };
1127         HMONITOR hMon = MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTONEAREST);
1128 
1129         monInfo.cbSize = sizeof(monInfo);
1130 
1131         if (hMon)
1132             GetMonitorInfo(hMon, &monInfo);
1133         else
1134             ::GetWindowRect(GetDesktopWindow(), &monInfo.rcMonitor);
1135 
1136         GetItemRect(iBtn, &rcItem);
1137 
1138         POINT ptItem = { rcItem.left, rcItem.top };
1139         SIZE szItem = { rcItem.right - rcItem.left, rcItem.bottom - rcItem.top };
1140         ClientToScreen(&ptItem);
1141 
1142         ptItem.x += szItem.cx / 2;
1143         ptItem.y -= szTip.cy;
1144 
1145         if (ptItem.x + szTip.cx > monInfo.rcMonitor.right)
1146             ptItem.x = monInfo.rcMonitor.right - szTip.cx;
1147 
1148         if (ptItem.y + szTip.cy > monInfo.rcMonitor.bottom)
1149             ptItem.y = monInfo.rcMonitor.bottom - szTip.cy;
1150 
1151         if (ptItem.x < monInfo.rcMonitor.left)
1152             ptItem.x = monInfo.rcMonitor.left;
1153 
1154         if (ptItem.y < monInfo.rcMonitor.top)
1155             ptItem.y = monInfo.rcMonitor.top;
1156 
1157         TRACE("ptItem { %d, %d }\n", ptItem.x, ptItem.y);
1158 
1159         ::SetWindowPos(hdr->hwndFrom, NULL, ptItem.x, ptItem.y, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
1160 
1161         return TRUE;
1162     }
1163 
1164     bHandled = FALSE;
1165     return 0;
1166 }
1167 
1168 void CNotifyToolbar::Initialize(HWND hWndParent, CBalloonQueue * queue)
1169 {
1170     m_BalloonQueue = queue;
1171 
1172     DWORD styles =
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;
1176 
1177     SubclassWindow(CToolbar::Create(hWndParent, styles));
1178 
1179     // Force the toolbar tooltips window to always show tooltips even if not foreground
1180     HWND tooltipsWnd = (HWND)SendMessageW(TB_GETTOOLTIPS);
1181     if (tooltipsWnd)
1182     {
1183         ::SetWindowLong(tooltipsWnd, GWL_STYLE, ::GetWindowLong(tooltipsWnd, GWL_STYLE) | TTS_ALWAYSTIP);
1184     }
1185 
1186     SetWindowTheme(m_hWnd, L"TrayNotify", NULL);
1187 
1188     m_ImageList = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR32 | ILC_MASK, 0, 1000);
1189     SetImageList(m_ImageList);
1190 
1191     TBMETRICS tbm = {sizeof(tbm)};
1192     tbm.dwMask = TBMF_BARPAD | TBMF_BUTTONSPACING | TBMF_PAD;
1193     tbm.cxPad = 1;
1194     tbm.cyPad = 1;
1195     tbm.cxBarPad = 1;
1196     tbm.cyBarPad = 1;
1197     tbm.cxButtonSpacing = 1;
1198     tbm.cyButtonSpacing = 1;
1199     SetMetrics(&tbm);
1200 
1201     SetButtonSize(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
1202 }
1203 
1204 /*
1205  * SysPagerWnd
1206  */
1207 const WCHAR szSysPagerWndClass[] = L"SysPager";
1208 
1209 CSysPagerWnd::CSysPagerWnd() {}
1210 
1211 CSysPagerWnd::~CSysPagerWnd() {}
1212 
1213 LRESULT CSysPagerWnd::OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1214 {
1215     HDC hdc = (HDC) wParam;
1216 
1217     if (!IsAppThemed())
1218     {
1219         bHandled = FALSE;
1220         return 0;
1221     }
1222 
1223     RECT rect;
1224     GetClientRect(&rect);
1225     DrawThemeParentBackground(m_hWnd, hdc, &rect);
1226 
1227     return TRUE;
1228 }
1229 
1230 LRESULT CSysPagerWnd::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1231 {
1232     Toolbar.Initialize(m_hWnd, &m_BalloonQueue);
1233     CIconWatcher::Initialize(m_hWnd);
1234 
1235     HWND hWndTop = GetAncestor(m_hWnd, GA_ROOT);
1236 
1237     m_Balloons.Create(hWndTop, TTS_NOPREFIX | TTS_BALLOON | TTS_CLOSE);
1238 
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);
1243     ti.hwnd = m_hWnd;
1244     ti.lpszText = NULL;
1245     ti.lParam = NULL;
1246 
1247     BOOL ret = m_Balloons.AddTool(&ti);
1248     if (!ret)
1249     {
1250         WARN("AddTool failed, LastError=%d (probably meaningless unless non-zero)\n", GetLastError());
1251     }
1252 
1253     m_BalloonQueue.Init(m_hWnd, &Toolbar, &m_Balloons);
1254 
1255     // Explicitly request running applications to re-register their systray icons
1256     ::SendNotifyMessageW(HWND_BROADCAST,
1257                             RegisterWindowMessageW(L"TaskbarCreated"),
1258                             0, 0);
1259 
1260     return TRUE;
1261 }
1262 
1263 LRESULT CSysPagerWnd::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1264 {
1265     m_BalloonQueue.Deinit();
1266     CIconWatcher::Uninitialize();
1267     return TRUE;
1268 }
1269 
1270 BOOL CSysPagerWnd::NotifyIcon(DWORD dwMessage, _In_ CONST NOTIFYICONDATA *iconData)
1271 {
1272     BOOL ret = FALSE;
1273 
1274     int VisibleButtonCount = Toolbar.GetVisibleButtonCount();
1275 
1276     TRACE("NotifyIcon received. Code=%d\n", dwMessage);
1277     switch (dwMessage)
1278     {
1279     case NIM_ADD:
1280         ret = Toolbar.AddButton(iconData);
1281         if (ret == TRUE)
1282         {
1283             (void)AddIconToWatcher(iconData);
1284         }
1285         break;
1286     case NIM_MODIFY:
1287         ret = Toolbar.UpdateButton(iconData);
1288         break;
1289     case NIM_DELETE:
1290         ret = Toolbar.RemoveButton(iconData);
1291         if (ret == TRUE)
1292         {
1293             (void)RemoveIconFromWatcher(iconData);
1294         }
1295         break;
1296     case NIM_SETFOCUS:
1297         Toolbar.SetFocus();
1298         ret = TRUE;
1299     case NIM_SETVERSION:
1300         ret = Toolbar.SwitchVersion(iconData);
1301     default:
1302         TRACE("NotifyIcon received with unknown code %d.\n", dwMessage);
1303         return FALSE;
1304     }
1305 
1306     if (VisibleButtonCount != Toolbar.GetVisibleButtonCount())
1307     {
1308         /* Ask the parent to resize */
1309         NMHDR nmh = {GetParent(), 0, NTNWM_REALIGN};
1310         GetParent().SendMessage(WM_NOTIFY, 0, (LPARAM) &nmh);
1311     }
1312 
1313     return ret;
1314 }
1315 
1316 void CSysPagerWnd::GetSize(IN BOOL IsHorizontal, IN PSIZE size)
1317 {
1318     /* Get the ideal height or width */
1319 #if 0
1320     /* Unfortunately this doens't work correctly in ros */
1321     Toolbar.GetIdealSize(!IsHorizontal, size);
1322 
1323     /* Make the reference dimension an exact multiple of the icon size */
1324     if (IsHorizontal)
1325         size->cy -= size->cy % GetSystemMetrics(SM_CYSMICON);
1326     else
1327         size->cx -= size->cx % GetSystemMetrics(SM_CXSMICON);
1328 
1329 #else
1330     INT rows = 0;
1331     INT columns = 0;
1332     INT cyButton = GetSystemMetrics(SM_CYSMICON) + 2;
1333     INT cxButton = GetSystemMetrics(SM_CXSMICON) + 2;
1334     int VisibleButtonCount = Toolbar.GetVisibleButtonCount();
1335 
1336     if (IsHorizontal)
1337     {
1338         rows = max(size->cy / cyButton, 1);
1339         columns = (VisibleButtonCount + rows - 1) / rows;
1340     }
1341     else
1342     {
1343         columns = max(size->cx / cxButton, 1);
1344         rows = (VisibleButtonCount + columns - 1) / columns;
1345     }
1346     size->cx = columns * cxButton;
1347     size->cy = rows * cyButton;
1348 #endif
1349 }
1350 
1351 LRESULT CSysPagerWnd::OnGetInfoTip(INT uCode, LPNMHDR hdr, BOOL& bHandled)
1352 {
1353     NMTBGETINFOTIPW * nmtip = (NMTBGETINFOTIPW *) hdr;
1354     GetTooltipText(nmtip->lParam, nmtip->pszText, nmtip->cchTextMax);
1355     return TRUE;
1356 }
1357 
1358 LRESULT CSysPagerWnd::OnCustomDraw(INT uCode, LPNMHDR hdr, BOOL& bHandled)
1359 {
1360     NMCUSTOMDRAW * cdraw = (NMCUSTOMDRAW *) hdr;
1361     switch (cdraw->dwDrawStage)
1362     {
1363     case CDDS_PREPAINT:
1364         return CDRF_NOTIFYITEMDRAW;
1365 
1366     case CDDS_ITEMPREPAINT:
1367         return TBCDRF_NOBACKGROUND | TBCDRF_NOEDGES | TBCDRF_NOOFFSET | TBCDRF_NOMARK | TBCDRF_NOETCHEDEFFECT;
1368     }
1369     return TRUE;
1370 }
1371 
1372 LRESULT CSysPagerWnd::OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1373 {
1374     LRESULT Ret = TRUE;
1375     SIZE szClient;
1376     szClient.cx = LOWORD(lParam);
1377     szClient.cy = HIWORD(lParam);
1378 
1379     Ret = DefWindowProc(uMsg, wParam, lParam);
1380 
1381     if (Toolbar)
1382     {
1383         Toolbar.SetWindowPos(NULL, 0, 0, szClient.cx, szClient.cy, SWP_NOZORDER);
1384         Toolbar.AutoSize();
1385 
1386         RECT rc;
1387         Toolbar.GetClientRect(&rc);
1388 
1389         SIZE szBar = { rc.right - rc.left, rc.bottom - rc.top };
1390 
1391         INT xOff = (szClient.cx - szBar.cx) / 2;
1392         INT yOff = (szClient.cy - szBar.cy) / 2;
1393 
1394         Toolbar.SetWindowPos(NULL, xOff, yOff, szBar.cx, szBar.cy, SWP_NOZORDER);
1395     }
1396     return Ret;
1397 }
1398 
1399 LRESULT CSysPagerWnd::OnCtxMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1400 {
1401     bHandled = TRUE;
1402     return 0;
1403 }
1404 
1405 LRESULT CSysPagerWnd::OnBalloonPop(UINT uCode, LPNMHDR hdr , BOOL& bHandled)
1406 {
1407     m_BalloonQueue.CloseCurrent();
1408     bHandled = TRUE;
1409     return 0;
1410 }
1411 
1412 LRESULT CSysPagerWnd::OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1413 {
1414     if (m_BalloonQueue.OnTimer(wParam))
1415     {
1416         bHandled = TRUE;
1417     }
1418 
1419     return 0;
1420 }
1421 
1422 LRESULT CSysPagerWnd::OnCopyData(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1423 {
1424     PCOPYDATASTRUCT cpData = (PCOPYDATASTRUCT)lParam;
1425     if (cpData->dwData == 1)
1426     {
1427         /* A taskbar NotifyIcon notification */
1428         PTRAYNOTIFYDATAW pData = (PTRAYNOTIFYDATAW)cpData->lpData;
1429         if (pData->dwSignature == NI_NOTIFY_SIG)
1430             return NotifyIcon(pData->dwMessage, &pData->nid);
1431     }
1432     // TODO: Handle other types of taskbar notifications
1433 
1434     return FALSE;
1435 }
1436 
1437 LRESULT CSysPagerWnd::OnSettingChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1438 {
1439     if (wParam == SPI_SETNONCLIENTMETRICS)
1440     {
1441         Toolbar.ResizeImagelist();
1442     }
1443     return 0;
1444 }
1445 
1446 LRESULT CSysPagerWnd::OnGetMinimumSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1447 {
1448     GetSize((BOOL)wParam, (PSIZE)lParam);
1449     return 0;
1450 }
1451 
1452 HRESULT CSysPagerWnd::Initialize(IN HWND hWndParent)
1453 {
1454     /* Create the window. The tray window is going to move it to the correct
1455         position and resize it as needed. */
1456     DWORD dwStyle = WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE;
1457     Create(hWndParent, 0, NULL, dwStyle);
1458     if (!m_hWnd)
1459         return E_FAIL;
1460 
1461     SetWindowTheme(m_hWnd, L"TrayNotify", NULL);
1462 
1463     return S_OK;
1464 }
1465 
1466 HRESULT CSysPagerWnd_CreateInstance(HWND hwndParent, REFIID riid, void **ppv)
1467 {
1468     return ShellObjectCreatorInit<CSysPagerWnd>(hwndParent, riid, ppv);
1469 }
1470