xref: /reactos/base/shell/explorer/syspager.cpp (revision 02e84521)
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     LRESULT OnCtxMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
169     VOID SendMouseEvent(IN WORD wIndex, IN UINT uMsg, IN WPARAM wParam);
170     LRESULT OnMouseEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
171     LRESULT OnTooltipShow(INT uCode, LPNMHDR hdr, BOOL& bHandled);
172 
173 public:
174     BEGIN_MSG_MAP(CNotifyToolbar)
175         MESSAGE_HANDLER(WM_CONTEXTMENU, OnCtxMenu)
176         MESSAGE_RANGE_HANDLER(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseEvent)
177         NOTIFY_CODE_HANDLER(TTN_SHOW, OnTooltipShow)
178     END_MSG_MAP()
179 
180     void Initialize(HWND hWndParent, CBalloonQueue * queue);
181 };
182 
183 extern const WCHAR szSysPagerWndClass[];
184 
185 class CSysPagerWnd :
186     public CComCoClass<CSysPagerWnd>,
187     public CComObjectRootEx<CComMultiThreadModelNoCS>,
188     public CWindowImpl < CSysPagerWnd, CWindow, CControlWinTraits >,
189     public IOleWindow,
190     public CIconWatcher
191 {
192     CNotifyToolbar Toolbar;
193     CTooltips m_Balloons;
194     CBalloonQueue m_BalloonQueue;
195 
196 public:
197     CSysPagerWnd();
198     virtual ~CSysPagerWnd();
199 
200     LRESULT DrawBackground(HDC hdc);
201     LRESULT OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
202     LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
203     LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
204     LRESULT OnGetInfoTip(INT uCode, LPNMHDR hdr, BOOL& bHandled);
205     LRESULT OnCustomDraw(INT uCode, LPNMHDR hdr, BOOL& bHandled);
206     LRESULT OnCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
207     LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
208     LRESULT OnCtxMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
209     LRESULT OnBalloonPop(UINT uCode, LPNMHDR hdr, BOOL& bHandled);
210     LRESULT OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
211     LRESULT OnCopyData(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
212     LRESULT OnSettingChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
213     LRESULT OnGetMinimumSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled);
214 
215 public:
216 
217     HRESULT WINAPI GetWindow(HWND* phwnd)
218     {
219         if (!phwnd)
220             return E_INVALIDARG;
221         *phwnd = m_hWnd;
222         return S_OK;
223     }
224 
225     HRESULT WINAPI ContextSensitiveHelp(BOOL fEnterMode)
226     {
227         return E_NOTIMPL;
228     }
229 
230     DECLARE_NOT_AGGREGATABLE(CSysPagerWnd)
231 
232     DECLARE_PROTECT_FINAL_CONSTRUCT()
233     BEGIN_COM_MAP(CSysPagerWnd)
234         COM_INTERFACE_ENTRY_IID(IID_IOleWindow, IOleWindow)
235     END_COM_MAP()
236 
237     BOOL NotifyIcon(DWORD dwMessage, _In_ CONST NOTIFYICONDATA *iconData);
238     void GetSize(IN BOOL IsHorizontal, IN PSIZE size);
239 
240     DECLARE_WND_CLASS_EX(szSysPagerWndClass, CS_DBLCLKS, COLOR_3DFACE)
241 
242     BEGIN_MSG_MAP(CSysPagerWnd)
243         MESSAGE_HANDLER(WM_CREATE, OnCreate)
244         MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
245         MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
246         MESSAGE_HANDLER(WM_COMMAND, OnCommand)
247         MESSAGE_HANDLER(WM_SIZE, OnSize)
248         MESSAGE_HANDLER(WM_CONTEXTMENU, OnCtxMenu)
249         MESSAGE_HANDLER(WM_TIMER, OnTimer)
250         MESSAGE_HANDLER(WM_COPYDATA, OnCopyData)
251         MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingChanged)
252         MESSAGE_HANDLER(TNWM_GETMINIMUMSIZE, OnGetMinimumSize)
253         NOTIFY_CODE_HANDLER(TTN_POP, OnBalloonPop)
254         NOTIFY_CODE_HANDLER(TBN_GETINFOTIPW, OnGetInfoTip)
255         NOTIFY_CODE_HANDLER(NM_CUSTOMDRAW, OnCustomDraw)
256     END_MSG_MAP()
257 
258     HRESULT Initialize(IN HWND hWndParent);
259 };
260 
261 CIconWatcher::CIconWatcher() :
262     m_hWatcherThread(NULL),
263     m_WakeUpEvent(NULL),
264     m_hwndSysTray(NULL),
265     m_Loop(false)
266 {
267 }
268 
269 CIconWatcher::~CIconWatcher()
270 {
271     Uninitialize();
272     DeleteCriticalSection(&m_ListLock);
273 
274     if (m_WakeUpEvent)
275         CloseHandle(m_WakeUpEvent);
276     if (m_hWatcherThread)
277         CloseHandle(m_hWatcherThread);
278 }
279 
280 bool CIconWatcher::Initialize(_In_ HWND hWndParent)
281 {
282     m_hwndSysTray = hWndParent;
283 
284     InitializeCriticalSection(&m_ListLock);
285     m_WakeUpEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
286     if (m_WakeUpEvent == NULL)
287         return false;
288 
289     m_hWatcherThread = (HANDLE)_beginthreadex(NULL,
290                                                 0,
291                                                 WatcherThread,
292                                                 (LPVOID)this,
293                                                 0,
294                                                 NULL);
295     if (m_hWatcherThread == NULL)
296         return false;
297 
298     return true;
299 }
300 
301 void CIconWatcher::Uninitialize()
302 {
303     m_Loop = false;
304     if (m_WakeUpEvent)
305         SetEvent(m_WakeUpEvent);
306 
307     EnterCriticalSection(&m_ListLock);
308 
309     POSITION Pos;
310     for (size_t i = 0; i < m_WatcherList.GetCount(); i++)
311     {
312         Pos = m_WatcherList.FindIndex(i);
313         if (Pos)
314         {
315             IconWatcherData *Icon;
316             Icon = m_WatcherList.GetAt(Pos);
317             delete Icon;
318         }
319     }
320     m_WatcherList.RemoveAll();
321 
322     LeaveCriticalSection(&m_ListLock);
323 }
324 
325 bool CIconWatcher::AddIconToWatcher(_In_ CONST NOTIFYICONDATA *iconData)
326 {
327     DWORD ProcessId;
328     (void)GetWindowThreadProcessId(iconData->hWnd, &ProcessId);
329 
330     HANDLE hProcess;
331     hProcess = OpenProcess(SYNCHRONIZE, FALSE, ProcessId);
332     if (hProcess == NULL)
333     {
334         return false;
335     }
336 
337     IconWatcherData *Icon = new IconWatcherData(iconData);
338     Icon->hProcess = hProcess;
339     Icon->ProcessId = ProcessId;
340 
341     bool Added = false;
342     EnterCriticalSection(&m_ListLock);
343 
344     // The likelyhood of someone having more than 64 icons in their tray is
345     // pretty slim. We could spin up a new thread for each multiple of 64, but
346     // it's not worth the effort, so we just won't bother watching those icons
347     if (m_WatcherList.GetCount() < MAXIMUM_WAIT_OBJECTS)
348     {
349         m_WatcherList.AddTail(Icon);
350         SetEvent(m_WakeUpEvent);
351         Added = true;
352     }
353 
354     LeaveCriticalSection(&m_ListLock);
355 
356     if (!Added)
357     {
358         delete Icon;
359     }
360 
361     return Added;
362 }
363 
364 bool CIconWatcher::RemoveIconFromWatcher(_In_ CONST NOTIFYICONDATA *iconData)
365 {
366     EnterCriticalSection(&m_ListLock);
367 
368     IconWatcherData *Icon;
369     Icon = GetListEntry(iconData, NULL, true);
370 
371     SetEvent(m_WakeUpEvent);
372     LeaveCriticalSection(&m_ListLock);
373 
374     delete Icon;
375     return true;
376 }
377 
378 IconWatcherData* CIconWatcher::GetListEntry(_In_opt_ CONST NOTIFYICONDATA *iconData, _In_opt_ HANDLE hProcess, _In_ bool Remove)
379 {
380     IconWatcherData *Entry = NULL;
381     POSITION NextPosition = m_WatcherList.GetHeadPosition();
382     POSITION Position;
383     do
384     {
385         Position = NextPosition;
386 
387         Entry = m_WatcherList.GetNext(NextPosition);
388         if (Entry)
389         {
390             if ((iconData && ((Entry->IconData.hWnd == iconData->hWnd) && (Entry->IconData.uID == iconData->uID))) ||
391                     (hProcess && (Entry->hProcess == hProcess)))
392             {
393                 if (Remove)
394                     m_WatcherList.RemoveAt(Position);
395                 break;
396             }
397         }
398         Entry = NULL;
399 
400     } while (NextPosition != NULL);
401 
402     return Entry;
403 }
404 
405 UINT WINAPI CIconWatcher::WatcherThread(_In_opt_ LPVOID lpParam)
406 {
407     CIconWatcher* This = reinterpret_cast<CIconWatcher *>(lpParam);
408     HANDLE *WatchList = NULL;
409 
410     This->m_Loop = true;
411     while (This->m_Loop)
412     {
413         EnterCriticalSection(&This->m_ListLock);
414 
415         DWORD Size;
416         Size = This->m_WatcherList.GetCount() + 1;
417         ASSERT(Size <= MAXIMUM_WAIT_OBJECTS);
418 
419         if (WatchList)
420             delete[] WatchList;
421         WatchList = new HANDLE[Size];
422         WatchList[0] = This->m_WakeUpEvent;
423 
424         POSITION Pos;
425         for (size_t i = 0; i < This->m_WatcherList.GetCount(); i++)
426         {
427             Pos = This->m_WatcherList.FindIndex(i);
428             if (Pos)
429             {
430                 IconWatcherData *Icon;
431                 Icon = This->m_WatcherList.GetAt(Pos);
432                 WatchList[i + 1] = Icon->hProcess;
433             }
434         }
435 
436         LeaveCriticalSection(&This->m_ListLock);
437 
438         DWORD Status;
439         Status = WaitForMultipleObjects(Size,
440                                         WatchList,
441                                         FALSE,
442                                         INFINITE);
443         if (Status == WAIT_OBJECT_0)
444         {
445             // We've been kicked, we have updates to our list (or we're exiting the thread)
446             if (This->m_Loop)
447                 TRACE("Updating watched icon list");
448         }
449         else if ((Status >= WAIT_OBJECT_0 + 1) && (Status < Size))
450         {
451             IconWatcherData *Icon;
452             Icon = This->GetListEntry(NULL, WatchList[Status], false);
453 
454             TRACE("Pid %lu owns a notification icon and has stopped without deleting it. We'll cleanup on its behalf", Icon->ProcessId);
455 
456             TRAYNOTIFYDATAW tnid = {0};
457             tnid.dwSignature = NI_NOTIFY_SIG;
458             tnid.dwMessage   = NIM_DELETE;
459             CopyMemory(&tnid.nid, &Icon->IconData, Icon->IconData.cbSize);
460 
461             COPYDATASTRUCT data;
462             data.dwData = 1;
463             data.cbData = sizeof(tnid);
464             data.lpData = &tnid;
465 
466             BOOL Success = ::SendMessage(This->m_hwndSysTray, WM_COPYDATA,
467                                          (WPARAM)&Icon->IconData, (LPARAM)&data);
468             if (!Success)
469             {
470                 // If we failed to handle the delete message, forcibly remove it
471                 This->RemoveIconFromWatcher(&Icon->IconData);
472             }
473         }
474         else
475         {
476             if (Status == WAIT_FAILED)
477             {
478                 Status = GetLastError();
479             }
480             ERR("Failed to wait on process handles : %lu\n", Status);
481             This->Uninitialize();
482         }
483     }
484 
485     if (WatchList)
486         delete[] WatchList;
487 
488     return 0;
489 }
490 
491 /*
492 * NotifyToolbar
493 */
494 
495 CBalloonQueue::CBalloonQueue() :
496     m_hwndParent(NULL),
497     m_tooltips(NULL),
498     m_toolbar(NULL),
499     m_current(NULL),
500     m_currentClosed(false),
501     m_timer(-1)
502 {
503 }
504 
505 void CBalloonQueue::Init(HWND hwndParent, CNotifyToolbar * toolbar, CTooltips * balloons)
506 {
507     m_hwndParent = hwndParent;
508     m_toolbar = toolbar;
509     m_tooltips = balloons;
510 }
511 
512 void CBalloonQueue::Deinit()
513 {
514     if (m_timer >= 0)
515     {
516         ::KillTimer(m_hwndParent, m_timer);
517     }
518 }
519 
520 bool CBalloonQueue::OnTimer(int timerId)
521 {
522     if (timerId != m_timer)
523         return false;
524 
525     ::KillTimer(m_hwndParent, m_timer);
526     m_timer = -1;
527 
528     if (m_current && !m_currentClosed)
529     {
530         Close(m_current, NIN_BALLOONTIMEOUT);
531     }
532     else
533     {
534         m_current = NULL;
535         m_currentClosed = false;
536         if (!m_queue.IsEmpty())
537         {
538             Info info = m_queue.RemoveHead();
539             Show(info);
540         }
541     }
542 
543     return true;
544 }
545 
546 void CBalloonQueue::UpdateInfo(InternalIconData * notifyItem)
547 {
548     size_t len = 0;
549     HRESULT hr = StringCchLength(notifyItem->szInfo, _countof(notifyItem->szInfo), &len);
550     if (SUCCEEDED(hr) && len > 0)
551     {
552         Info info(notifyItem);
553 
554         // If m_current == notifyItem, we want to replace the previous balloon even if there is a queue.
555         if (m_current != notifyItem && (m_current != NULL || !m_queue.IsEmpty()))
556         {
557             m_queue.AddTail(info);
558         }
559         else
560         {
561             Show(info);
562         }
563     }
564     else
565     {
566         Close(notifyItem, NIN_BALLOONHIDE);
567     }
568 }
569 
570 void CBalloonQueue::RemoveInfo(InternalIconData * notifyItem)
571 {
572     Close(notifyItem, NIN_BALLOONHIDE);
573 
574     POSITION position = m_queue.GetHeadPosition();
575     while(position != NULL)
576     {
577         Info& info = m_queue.GetNext(position);
578         if (info.pSource == notifyItem)
579         {
580             m_queue.RemoveAt(position);
581         }
582     }
583 }
584 
585 void CBalloonQueue::CloseCurrent()
586 {
587     if (m_current != NULL)
588     {
589         Close(m_current, NIN_BALLOONTIMEOUT);
590     }
591 }
592 
593 int CBalloonQueue::IndexOf(InternalIconData * pdata)
594 {
595     int count = m_toolbar->GetButtonCount();
596     for (int i = 0; i < count; i++)
597     {
598         if (m_toolbar->GetItemData(i) == pdata)
599             return i;
600     }
601     return -1;
602 }
603 
604 void CBalloonQueue::SetTimer(int length)
605 {
606     m_timer = ::SetTimer(m_hwndParent, BalloonsTimerId, length, NULL);
607 }
608 
609 void CBalloonQueue::Show(Info& info)
610 {
611     TRACE("ShowBalloonTip called for flags=%x text=%ws; title=%ws\n", info.uIcon, info.szInfo, info.szInfoTitle);
612 
613     // TODO: NIF_REALTIME, NIIF_NOSOUND, other Vista+ flags
614 
615     const int index = IndexOf(info.pSource);
616     RECT rc;
617     m_toolbar->GetItemRect(index, &rc);
618     m_toolbar->ClientToScreen(&rc);
619     const WORD x = (rc.left + rc.right) / 2;
620     const WORD y = (rc.top + rc.bottom) / 2;
621 
622     m_tooltips->SetTitle(info.szInfoTitle, info.uIcon);
623     m_tooltips->TrackPosition(x, y);
624     m_tooltips->UpdateTipText(m_hwndParent, reinterpret_cast<LPARAM>(m_toolbar->m_hWnd), info.szInfo);
625     m_tooltips->TrackActivate(m_hwndParent, reinterpret_cast<LPARAM>(m_toolbar->m_hWnd));
626 
627     m_current = info.pSource;
628     int timeout = info.uTimeout;
629     if (timeout < MinTimeout) timeout = MinTimeout;
630     if (timeout > MaxTimeout) timeout = MaxTimeout;
631 
632     SetTimer(timeout);
633 
634     m_toolbar->SendNotifyCallback(m_current, NIN_BALLOONSHOW);
635 }
636 
637 void CBalloonQueue::Close(IN OUT InternalIconData * notifyItem, IN UINT uReason)
638 {
639     TRACE("HideBalloonTip called\n");
640 
641     if (m_current == notifyItem && !m_currentClosed)
642     {
643         m_toolbar->SendNotifyCallback(m_current, uReason);
644 
645         // Prevent Re-entry
646         m_currentClosed = true;
647         m_tooltips->TrackDeactivate();
648         SetTimer(CooldownBetweenBalloons);
649     }
650 }
651 
652 /*
653  * NotifyToolbar
654  */
655 
656 CNotifyToolbar::CNotifyToolbar() :
657     m_ImageList(NULL),
658     m_VisibleButtonCount(0),
659     m_BalloonQueue(NULL)
660 {
661 }
662 
663 CNotifyToolbar::~CNotifyToolbar()
664 {
665 }
666 
667 int CNotifyToolbar::GetVisibleButtonCount()
668 {
669     return m_VisibleButtonCount;
670 }
671 
672 int CNotifyToolbar::FindItem(IN HWND hWnd, IN UINT uID, InternalIconData ** pdata)
673 {
674     int count = GetButtonCount();
675 
676     for (int i = 0; i < count; i++)
677     {
678         InternalIconData * data = GetItemData(i);
679 
680         if (data->hWnd == hWnd &&
681             data->uID == uID)
682         {
683             if (pdata)
684                 *pdata = data;
685             return i;
686         }
687     }
688 
689     return -1;
690 }
691 
692 int CNotifyToolbar::FindExistingSharedIcon(HICON handle)
693 {
694     int count = GetButtonCount();
695     for (int i = 0; i < count; i++)
696     {
697         InternalIconData * data = GetItemData(i);
698         if (data->hIcon == handle)
699         {
700             TBBUTTON btn;
701             GetButton(i, &btn);
702             return btn.iBitmap;
703         }
704     }
705 
706     return -1;
707 }
708 
709 BOOL CNotifyToolbar::AddButton(_In_ CONST NOTIFYICONDATA *iconData)
710 {
711     TBBUTTON tbBtn = { 0 };
712     InternalIconData * notifyItem;
713     WCHAR text[] = L"";
714 
715     TRACE("Adding icon %d from hWnd %08x flags%s%s state%s%s",
716         iconData->uID, iconData->hWnd,
717         (iconData->uFlags & NIF_ICON) ? " ICON" : "",
718         (iconData->uFlags & NIF_STATE) ? " STATE" : "",
719         (iconData->dwState & NIS_HIDDEN) ? " HIDDEN" : "",
720         (iconData->dwState & NIS_SHAREDICON) ? " SHARED" : "");
721 
722     int index = FindItem(iconData->hWnd, iconData->uID, &notifyItem);
723     if (index >= 0)
724     {
725         TRACE("Icon %d from hWnd %08x ALREADY EXISTS!", iconData->uID, iconData->hWnd);
726         return FALSE;
727     }
728 
729     notifyItem = new InternalIconData();
730     ZeroMemory(notifyItem, sizeof(*notifyItem));
731 
732     notifyItem->hWnd = iconData->hWnd;
733     notifyItem->uID = iconData->uID;
734 
735     tbBtn.fsState = TBSTATE_ENABLED;
736     tbBtn.fsStyle = BTNS_NOPREFIX;
737     tbBtn.dwData = (DWORD_PTR)notifyItem;
738     tbBtn.iString = (INT_PTR) text;
739     tbBtn.idCommand = GetButtonCount();
740 
741     if (iconData->uFlags & NIF_STATE)
742     {
743         notifyItem->dwState = iconData->dwState & iconData->dwStateMask;
744     }
745 
746     if (iconData->uFlags & NIF_MESSAGE)
747     {
748         notifyItem->uCallbackMessage = iconData->uCallbackMessage;
749     }
750 
751     if (iconData->uFlags & NIF_ICON)
752     {
753         notifyItem->hIcon = iconData->hIcon;
754         BOOL hasSharedIcon = notifyItem->dwState & NIS_SHAREDICON;
755         if (hasSharedIcon)
756         {
757             INT iIcon = FindExistingSharedIcon(notifyItem->hIcon);
758             if (iIcon < 0)
759             {
760                 notifyItem->hIcon = NULL;
761                 TRACE("Shared icon requested, but HICON not found!!!");
762             }
763             tbBtn.iBitmap = iIcon;
764         }
765         else
766         {
767             tbBtn.iBitmap = ImageList_AddIcon(m_ImageList, notifyItem->hIcon);
768         }
769     }
770 
771     if (iconData->uFlags & NIF_TIP)
772     {
773         StringCchCopy(notifyItem->szTip, _countof(notifyItem->szTip), iconData->szTip);
774     }
775 
776     if (iconData->uFlags & NIF_INFO)
777     {
778         // NOTE: In Vista+, the uTimeout value is disregarded, and the accessibility settings are used always.
779         StringCchCopy(notifyItem->szInfo, _countof(notifyItem->szInfo), iconData->szInfo);
780         StringCchCopy(notifyItem->szInfoTitle, _countof(notifyItem->szInfoTitle), iconData->szInfoTitle);
781         notifyItem->dwInfoFlags = iconData->dwInfoFlags;
782         notifyItem->uTimeout = iconData->uTimeout;
783     }
784 
785     if (notifyItem->dwState & NIS_HIDDEN)
786     {
787         tbBtn.fsState |= TBSTATE_HIDDEN;
788     }
789     else
790     {
791         m_VisibleButtonCount++;
792     }
793 
794     /* TODO: support VERSION_4 (NIF_GUID, NIF_REALTIME, NIF_SHOWTIP) */
795 
796     CToolbar::AddButton(&tbBtn);
797     SetButtonSize(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
798 
799     if (iconData->uFlags & NIF_INFO)
800     {
801         m_BalloonQueue->UpdateInfo(notifyItem);
802     }
803 
804     return TRUE;
805 }
806 
807 BOOL CNotifyToolbar::SwitchVersion(_In_ CONST NOTIFYICONDATA *iconData)
808 {
809     InternalIconData * notifyItem;
810     int index = FindItem(iconData->hWnd, iconData->uID, &notifyItem);
811     if (index < 0)
812     {
813         WARN("Icon %d from hWnd %08x DOES NOT EXIST!", iconData->uID, iconData->hWnd);
814         return FALSE;
815     }
816 
817     if (iconData->uVersion != 0 && iconData->uVersion != NOTIFYICON_VERSION)
818     {
819         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);
820         return FALSE;
821     }
822 
823     // We can not store the version in the uVersion field, because it's union'd with uTimeout,
824     // which we also need to keep track of.
825     notifyItem->uVersionCopy = iconData->uVersion;
826 
827     return TRUE;
828 }
829 
830 BOOL CNotifyToolbar::UpdateButton(_In_ CONST NOTIFYICONDATA *iconData)
831 {
832     InternalIconData * notifyItem;
833     TBBUTTONINFO tbbi = { 0 };
834 
835     TRACE("Updating icon %d from hWnd %08x flags%s%s state%s%s",
836         iconData->uID, iconData->hWnd,
837         (iconData->uFlags & NIF_ICON) ? " ICON" : "",
838         (iconData->uFlags & NIF_STATE) ? " STATE" : "",
839         (iconData->dwState & NIS_HIDDEN) ? " HIDDEN" : "",
840         (iconData->dwState & NIS_SHAREDICON) ? " SHARED" : "");
841 
842     int index = FindItem(iconData->hWnd, iconData->uID, &notifyItem);
843     if (index < 0)
844     {
845         WARN("Icon %d from hWnd %08x DOES NOT EXIST!", iconData->uID, iconData->hWnd);
846         return AddButton(iconData);
847     }
848 
849     TBBUTTON btn;
850     GetButton(index, &btn);
851     int oldIconIndex = btn.iBitmap;
852 
853     tbbi.cbSize = sizeof(tbbi);
854     tbbi.dwMask = TBIF_BYINDEX | TBIF_COMMAND;
855     tbbi.idCommand = index;
856 
857     if (iconData->uFlags & NIF_STATE)
858     {
859         if (iconData->dwStateMask & NIS_HIDDEN &&
860             (notifyItem->dwState & NIS_HIDDEN) != (iconData->dwState & NIS_HIDDEN))
861         {
862             tbbi.dwMask |= TBIF_STATE;
863             if (iconData->dwState & NIS_HIDDEN)
864             {
865                 tbbi.fsState |= TBSTATE_HIDDEN;
866                 m_VisibleButtonCount--;
867             }
868             else
869             {
870                 tbbi.fsState &= ~TBSTATE_HIDDEN;
871                 m_VisibleButtonCount++;
872             }
873         }
874 
875         notifyItem->dwState &= ~iconData->dwStateMask;
876         notifyItem->dwState |= (iconData->dwState & iconData->dwStateMask);
877     }
878 
879     if (iconData->uFlags & NIF_MESSAGE)
880     {
881         notifyItem->uCallbackMessage = iconData->uCallbackMessage;
882     }
883 
884     if (iconData->uFlags & NIF_ICON)
885     {
886         BOOL hasSharedIcon = notifyItem->dwState & NIS_SHAREDICON;
887         if (hasSharedIcon)
888         {
889             INT iIcon = FindExistingSharedIcon(iconData->hIcon);
890             if (iIcon >= 0)
891             {
892                 notifyItem->hIcon = iconData->hIcon;
893                 tbbi.dwMask |= TBIF_IMAGE;
894                 tbbi.iImage = iIcon;
895             }
896             else
897             {
898                 TRACE("Shared icon requested, but HICON not found!!! IGNORING!");
899             }
900         }
901         else
902         {
903             notifyItem->hIcon = iconData->hIcon;
904             tbbi.dwMask |= TBIF_IMAGE;
905             tbbi.iImage = ImageList_ReplaceIcon(m_ImageList, oldIconIndex, notifyItem->hIcon);
906         }
907     }
908 
909     if (iconData->uFlags & NIF_TIP)
910     {
911         StringCchCopy(notifyItem->szTip, _countof(notifyItem->szTip), iconData->szTip);
912     }
913 
914     if (iconData->uFlags & NIF_INFO)
915     {
916         // NOTE: In Vista+, the uTimeout value is disregarded, and the accessibility settings are used always.
917         StringCchCopy(notifyItem->szInfo, _countof(notifyItem->szInfo), iconData->szInfo);
918         StringCchCopy(notifyItem->szInfoTitle, _countof(notifyItem->szInfoTitle), iconData->szInfoTitle);
919         notifyItem->dwInfoFlags = iconData->dwInfoFlags;
920         notifyItem->uTimeout = iconData->uTimeout;
921     }
922 
923     /* TODO: support VERSION_4 (NIF_GUID, NIF_REALTIME, NIF_SHOWTIP) */
924 
925     SetButtonInfo(index, &tbbi);
926 
927     if (iconData->uFlags & NIF_INFO)
928     {
929         m_BalloonQueue->UpdateInfo(notifyItem);
930     }
931 
932     return TRUE;
933 }
934 
935 BOOL CNotifyToolbar::RemoveButton(_In_ CONST NOTIFYICONDATA *iconData)
936 {
937     InternalIconData * notifyItem;
938 
939     TRACE("Removing icon %d from hWnd %08x", iconData->uID, iconData->hWnd);
940 
941     int index = FindItem(iconData->hWnd, iconData->uID, &notifyItem);
942     if (index < 0)
943     {
944         TRACE("Icon %d from hWnd %08x ALREADY MISSING!", iconData->uID, iconData->hWnd);
945 
946         return FALSE;
947     }
948 
949     if (!(notifyItem->dwState & NIS_HIDDEN))
950     {
951         m_VisibleButtonCount--;
952     }
953 
954     if (!(notifyItem->dwState & NIS_SHAREDICON))
955     {
956         TBBUTTON btn;
957         GetButton(index, &btn);
958         int oldIconIndex = btn.iBitmap;
959         ImageList_Remove(m_ImageList, oldIconIndex);
960 
961         // Update other icons!
962         int count = GetButtonCount();
963         for (int i = 0; i < count; i++)
964         {
965             TBBUTTON btn;
966             GetButton(i, &btn);
967 
968             if (btn.iBitmap > oldIconIndex)
969             {
970                 TBBUTTONINFO tbbi2 = { 0 };
971                 tbbi2.cbSize = sizeof(tbbi2);
972                 tbbi2.dwMask = TBIF_BYINDEX | TBIF_IMAGE;
973                 tbbi2.iImage = btn.iBitmap-1;
974                 SetButtonInfo(i, &tbbi2);
975             }
976         }
977     }
978 
979     m_BalloonQueue->RemoveInfo(notifyItem);
980 
981     DeleteButton(index);
982 
983     delete notifyItem;
984 
985     return TRUE;
986 }
987 
988 VOID CNotifyToolbar::ResizeImagelist()
989 {
990     int cx, cy;
991     HIMAGELIST iml;
992 
993     if (!ImageList_GetIconSize(m_ImageList, &cx, &cy))
994         return;
995 
996     if (cx == GetSystemMetrics(SM_CXSMICON) && cy == GetSystemMetrics(SM_CYSMICON))
997         return;
998 
999     iml = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR32 | ILC_MASK, 0, 1000);
1000     if (!iml)
1001         return;
1002 
1003     ImageList_Destroy(m_ImageList);
1004     m_ImageList = iml;
1005     SetImageList(m_ImageList);
1006 
1007     int count = GetButtonCount();
1008     for (int i = 0; i < count; i++)
1009     {
1010         InternalIconData * data = GetItemData(i);
1011         BOOL hasSharedIcon = data->dwState & NIS_SHAREDICON;
1012         INT iIcon = hasSharedIcon ? FindExistingSharedIcon(data->hIcon) : -1;
1013         if (iIcon < 0)
1014             iIcon = ImageList_AddIcon(iml, data->hIcon);
1015         TBBUTTONINFO tbbi = { sizeof(tbbi), TBIF_BYINDEX | TBIF_IMAGE, 0, iIcon};
1016         SetButtonInfo(i, &tbbi);
1017     }
1018 
1019     SetButtonSize(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
1020 }
1021 
1022 LRESULT CNotifyToolbar::OnCtxMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1023 {
1024     bHandled = FALSE;
1025 
1026     /*
1027      * WM_CONTEXTMENU message can be generated either by the mouse,
1028      * in which case lParam encodes the mouse coordinates where the
1029      * user right-clicked the mouse, or can be generated by (Shift-)F10
1030      * keyboard press, in which case lParam equals -1.
1031      */
1032     INT iBtn = GetHotItem();
1033     if (iBtn < 0)
1034         return 0;
1035 
1036     InternalIconData* notifyItem = GetItemData(iBtn);
1037 
1038     if (!::IsWindow(notifyItem->hWnd))
1039         return 0;
1040 
1041     if (notifyItem->uVersionCopy >= NOTIFYICON_VERSION)
1042     {
1043         /* Transmit the WM_CONTEXTMENU message if the notification icon supports it */
1044         ::SendNotifyMessage(notifyItem->hWnd,
1045                             notifyItem->uCallbackMessage,
1046                             notifyItem->uID,
1047                             WM_CONTEXTMENU);
1048     }
1049     else if (lParam == -1)
1050     {
1051         /*
1052          * Otherwise, and only if the WM_CONTEXTMENU message was generated
1053          * from the keyboard, simulate right-click mouse messages. This is
1054          * not needed if the message came from the mouse because in this
1055          * case the right-click mouse messages were already sent together.
1056          */
1057         ::SendNotifyMessage(notifyItem->hWnd,
1058                             notifyItem->uCallbackMessage,
1059                             notifyItem->uID,
1060                             WM_RBUTTONDOWN);
1061         ::SendNotifyMessage(notifyItem->hWnd,
1062                             notifyItem->uCallbackMessage,
1063                             notifyItem->uID,
1064                             WM_RBUTTONUP);
1065     }
1066 
1067     return 0;
1068 }
1069 
1070 bool CNotifyToolbar::SendNotifyCallback(InternalIconData* notifyItem, UINT uMsg)
1071 {
1072     if (!::IsWindow(notifyItem->hWnd))
1073     {
1074         // We detect and destroy icons with invalid handles only on mouse move over systray, same as MS does.
1075         // Alternatively we could search for them periodically (would waste more resources).
1076         TRACE("Destroying icon %d with invalid handle hWnd=%08x\n", notifyItem->uID, notifyItem->hWnd);
1077 
1078         RemoveButton(notifyItem);
1079 
1080         /* Ask the parent to resize */
1081         NMHDR nmh = {GetParent(), 0, NTNWM_REALIGN};
1082         GetParent().SendMessage(WM_NOTIFY, 0, (LPARAM) &nmh);
1083 
1084         return true;
1085     }
1086 
1087     DWORD pid;
1088     GetWindowThreadProcessId(notifyItem->hWnd, &pid);
1089 
1090     if (pid == GetCurrentProcessId() ||
1091         (uMsg >= WM_MOUSEFIRST && uMsg <= WM_MOUSELAST))
1092     {
1093         ::PostMessage(notifyItem->hWnd,
1094                       notifyItem->uCallbackMessage,
1095                       notifyItem->uID,
1096                       uMsg);
1097     }
1098     else
1099     {
1100         ::SendMessage(notifyItem->hWnd,
1101                       notifyItem->uCallbackMessage,
1102                       notifyItem->uID,
1103                       uMsg);
1104     }
1105     return false;
1106 }
1107 
1108 VOID CNotifyToolbar::SendMouseEvent(IN WORD wIndex, IN UINT uMsg, IN WPARAM wParam)
1109 {
1110     static LPCWSTR eventNames [] = {
1111         L"WM_MOUSEMOVE",
1112         L"WM_LBUTTONDOWN",
1113         L"WM_LBUTTONUP",
1114         L"WM_LBUTTONDBLCLK",
1115         L"WM_RBUTTONDOWN",
1116         L"WM_RBUTTONUP",
1117         L"WM_RBUTTONDBLCLK",
1118         L"WM_MBUTTONDOWN",
1119         L"WM_MBUTTONUP",
1120         L"WM_MBUTTONDBLCLK",
1121         L"WM_MOUSEWHEEL",
1122         L"WM_XBUTTONDOWN",
1123         L"WM_XBUTTONUP",
1124         L"WM_XBUTTONDBLCLK"
1125     };
1126 
1127     InternalIconData * notifyItem = GetItemData(wIndex);
1128 
1129     if (uMsg >= WM_MOUSEFIRST && uMsg <= WM_MOUSELAST)
1130     {
1131         TRACE("Sending message %S from button %d to %p (msg=%x, w=%x, l=%x)...\n",
1132             eventNames[uMsg - WM_MOUSEFIRST], wIndex,
1133             notifyItem->hWnd, notifyItem->uCallbackMessage, notifyItem->uID, uMsg);
1134     }
1135 
1136     SendNotifyCallback(notifyItem, uMsg);
1137 }
1138 
1139 LRESULT CNotifyToolbar::OnMouseEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1140 {
1141     POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
1142     INT iBtn = HitTest(&pt);
1143 
1144     if (iBtn >= 0)
1145     {
1146         SendMouseEvent(iBtn, uMsg, wParam);
1147     }
1148 
1149     bHandled = FALSE;
1150     return FALSE;
1151 }
1152 
1153 static VOID GetTooltipText(LPARAM data, LPTSTR szTip, DWORD cchTip)
1154 {
1155     InternalIconData * notifyItem = reinterpret_cast<InternalIconData *>(data);
1156     if (notifyItem)
1157     {
1158         StringCchCopy(szTip, cchTip, notifyItem->szTip);
1159     }
1160     else
1161     {
1162         StringCchCopy(szTip, cchTip, L"");
1163     }
1164 }
1165 
1166 LRESULT CNotifyToolbar::OnTooltipShow(INT uCode, LPNMHDR hdr, BOOL& bHandled)
1167 {
1168     RECT rcTip, rcItem;
1169     ::GetWindowRect(hdr->hwndFrom, &rcTip);
1170 
1171     SIZE szTip = { rcTip.right - rcTip.left, rcTip.bottom - rcTip.top };
1172 
1173     INT iBtn = GetHotItem();
1174 
1175     if (iBtn >= 0)
1176     {
1177         MONITORINFO monInfo = { 0 };
1178         HMONITOR hMon = MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTONEAREST);
1179 
1180         monInfo.cbSize = sizeof(monInfo);
1181 
1182         if (hMon)
1183             GetMonitorInfo(hMon, &monInfo);
1184         else
1185             ::GetWindowRect(GetDesktopWindow(), &monInfo.rcMonitor);
1186 
1187         GetItemRect(iBtn, &rcItem);
1188 
1189         POINT ptItem = { rcItem.left, rcItem.top };
1190         SIZE szItem = { rcItem.right - rcItem.left, rcItem.bottom - rcItem.top };
1191         ClientToScreen(&ptItem);
1192 
1193         ptItem.x += szItem.cx / 2;
1194         ptItem.y -= szTip.cy;
1195 
1196         if (ptItem.x + szTip.cx > monInfo.rcMonitor.right)
1197             ptItem.x = monInfo.rcMonitor.right - szTip.cx;
1198 
1199         if (ptItem.y + szTip.cy > monInfo.rcMonitor.bottom)
1200             ptItem.y = monInfo.rcMonitor.bottom - szTip.cy;
1201 
1202         if (ptItem.x < monInfo.rcMonitor.left)
1203             ptItem.x = monInfo.rcMonitor.left;
1204 
1205         if (ptItem.y < monInfo.rcMonitor.top)
1206             ptItem.y = monInfo.rcMonitor.top;
1207 
1208         TRACE("ptItem { %d, %d }\n", ptItem.x, ptItem.y);
1209 
1210         ::SetWindowPos(hdr->hwndFrom, NULL, ptItem.x, ptItem.y, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
1211 
1212         return TRUE;
1213     }
1214 
1215     bHandled = FALSE;
1216     return 0;
1217 }
1218 
1219 void CNotifyToolbar::Initialize(HWND hWndParent, CBalloonQueue * queue)
1220 {
1221     m_BalloonQueue = queue;
1222 
1223     DWORD styles =
1224         WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN |
1225         TBSTYLE_FLAT | TBSTYLE_TOOLTIPS | TBSTYLE_WRAPABLE | TBSTYLE_TRANSPARENT |
1226         CCS_TOP | CCS_NORESIZE | CCS_NOPARENTALIGN | CCS_NODIVIDER;
1227 
1228     SubclassWindow(CToolbar::Create(hWndParent, styles));
1229 
1230     // Force the toolbar tooltips window to always show tooltips even if not foreground
1231     HWND tooltipsWnd = (HWND)SendMessageW(TB_GETTOOLTIPS);
1232     if (tooltipsWnd)
1233     {
1234         ::SetWindowLong(tooltipsWnd, GWL_STYLE, ::GetWindowLong(tooltipsWnd, GWL_STYLE) | TTS_ALWAYSTIP);
1235     }
1236 
1237     SetWindowTheme(m_hWnd, L"TrayNotify", NULL);
1238 
1239     m_ImageList = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR32 | ILC_MASK, 0, 1000);
1240     SetImageList(m_ImageList);
1241 
1242     TBMETRICS tbm = {sizeof(tbm)};
1243     tbm.dwMask = TBMF_BARPAD | TBMF_BUTTONSPACING | TBMF_PAD;
1244     tbm.cxPad = 1;
1245     tbm.cyPad = 1;
1246     tbm.cxBarPad = 1;
1247     tbm.cyBarPad = 1;
1248     tbm.cxButtonSpacing = 1;
1249     tbm.cyButtonSpacing = 1;
1250     SetMetrics(&tbm);
1251 
1252     SetButtonSize(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
1253 }
1254 
1255 /*
1256  * SysPagerWnd
1257  */
1258 const WCHAR szSysPagerWndClass[] = L"SysPager";
1259 
1260 CSysPagerWnd::CSysPagerWnd() {}
1261 
1262 CSysPagerWnd::~CSysPagerWnd() {}
1263 
1264 LRESULT CSysPagerWnd::OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1265 {
1266     HDC hdc = (HDC) wParam;
1267 
1268     if (!IsAppThemed())
1269     {
1270         bHandled = FALSE;
1271         return 0;
1272     }
1273 
1274     RECT rect;
1275     GetClientRect(&rect);
1276     DrawThemeParentBackground(m_hWnd, hdc, &rect);
1277 
1278     return TRUE;
1279 }
1280 
1281 LRESULT CSysPagerWnd::OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1282 {
1283     Toolbar.Initialize(m_hWnd, &m_BalloonQueue);
1284     CIconWatcher::Initialize(m_hWnd);
1285 
1286     HWND hWndTop = GetAncestor(m_hWnd, GA_ROOT);
1287 
1288     m_Balloons.Create(hWndTop, TTS_NOPREFIX | TTS_BALLOON | TTS_CLOSE);
1289 
1290     TOOLINFOW ti = { 0 };
1291     ti.cbSize = TTTOOLINFOW_V1_SIZE;
1292     ti.uFlags = TTF_TRACK | TTF_IDISHWND;
1293     ti.uId = reinterpret_cast<UINT_PTR>(Toolbar.m_hWnd);
1294     ti.hwnd = m_hWnd;
1295     ti.lpszText = NULL;
1296     ti.lParam = NULL;
1297 
1298     BOOL ret = m_Balloons.AddTool(&ti);
1299     if (!ret)
1300     {
1301         WARN("AddTool failed, LastError=%d (probably meaningless unless non-zero)\n", GetLastError());
1302     }
1303 
1304     m_BalloonQueue.Init(m_hWnd, &Toolbar, &m_Balloons);
1305 
1306     // Explicitly request running applications to re-register their systray icons
1307     ::SendNotifyMessageW(HWND_BROADCAST,
1308                             RegisterWindowMessageW(L"TaskbarCreated"),
1309                             0, 0);
1310 
1311     return TRUE;
1312 }
1313 
1314 LRESULT CSysPagerWnd::OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1315 {
1316     m_BalloonQueue.Deinit();
1317     CIconWatcher::Uninitialize();
1318     return TRUE;
1319 }
1320 
1321 BOOL CSysPagerWnd::NotifyIcon(DWORD dwMessage, _In_ CONST NOTIFYICONDATA *iconData)
1322 {
1323     BOOL ret = FALSE;
1324 
1325     int VisibleButtonCount = Toolbar.GetVisibleButtonCount();
1326 
1327     TRACE("NotifyIcon received. Code=%d\n", dwMessage);
1328     switch (dwMessage)
1329     {
1330     case NIM_ADD:
1331         ret = Toolbar.AddButton(iconData);
1332         if (ret == TRUE)
1333         {
1334             (void)AddIconToWatcher(iconData);
1335         }
1336         break;
1337 
1338     case NIM_MODIFY:
1339         ret = Toolbar.UpdateButton(iconData);
1340         break;
1341 
1342     case NIM_DELETE:
1343         ret = Toolbar.RemoveButton(iconData);
1344         if (ret == TRUE)
1345         {
1346             (void)RemoveIconFromWatcher(iconData);
1347         }
1348         break;
1349 
1350     case NIM_SETFOCUS:
1351         Toolbar.SetFocus();
1352         ret = TRUE;
1353         break;
1354 
1355     case NIM_SETVERSION:
1356         ret = Toolbar.SwitchVersion(iconData);
1357         break;
1358 
1359     default:
1360         TRACE("NotifyIcon received with unknown code %d.\n", dwMessage);
1361         return FALSE;
1362     }
1363 
1364     if (VisibleButtonCount != Toolbar.GetVisibleButtonCount())
1365     {
1366         /* Ask the parent to resize */
1367         NMHDR nmh = {GetParent(), 0, NTNWM_REALIGN};
1368         GetParent().SendMessage(WM_NOTIFY, 0, (LPARAM) &nmh);
1369     }
1370 
1371     return ret;
1372 }
1373 
1374 void CSysPagerWnd::GetSize(IN BOOL IsHorizontal, IN PSIZE size)
1375 {
1376     /* Get the ideal height or width */
1377 #if 0
1378     /* Unfortunately this doens't work correctly in ros */
1379     Toolbar.GetIdealSize(!IsHorizontal, size);
1380 
1381     /* Make the reference dimension an exact multiple of the icon size */
1382     if (IsHorizontal)
1383         size->cy -= size->cy % GetSystemMetrics(SM_CYSMICON);
1384     else
1385         size->cx -= size->cx % GetSystemMetrics(SM_CXSMICON);
1386 
1387 #else
1388     INT rows = 0;
1389     INT columns = 0;
1390     INT cyButton = GetSystemMetrics(SM_CYSMICON) + 2;
1391     INT cxButton = GetSystemMetrics(SM_CXSMICON) + 2;
1392     int VisibleButtonCount = Toolbar.GetVisibleButtonCount();
1393 
1394     if (IsHorizontal)
1395     {
1396         rows = max(size->cy / cyButton, 1);
1397         columns = (VisibleButtonCount + rows - 1) / rows;
1398     }
1399     else
1400     {
1401         columns = max(size->cx / cxButton, 1);
1402         rows = (VisibleButtonCount + columns - 1) / columns;
1403     }
1404     size->cx = columns * cxButton;
1405     size->cy = rows * cyButton;
1406 #endif
1407 }
1408 
1409 LRESULT CSysPagerWnd::OnGetInfoTip(INT uCode, LPNMHDR hdr, BOOL& bHandled)
1410 {
1411     NMTBGETINFOTIPW * nmtip = (NMTBGETINFOTIPW *) hdr;
1412     GetTooltipText(nmtip->lParam, nmtip->pszText, nmtip->cchTextMax);
1413     return TRUE;
1414 }
1415 
1416 LRESULT CSysPagerWnd::OnCustomDraw(INT uCode, LPNMHDR hdr, BOOL& bHandled)
1417 {
1418     NMCUSTOMDRAW * cdraw = (NMCUSTOMDRAW *) hdr;
1419     switch (cdraw->dwDrawStage)
1420     {
1421     case CDDS_PREPAINT:
1422         return CDRF_NOTIFYITEMDRAW;
1423 
1424     case CDDS_ITEMPREPAINT:
1425         return TBCDRF_NOBACKGROUND | TBCDRF_NOEDGES | TBCDRF_NOOFFSET | TBCDRF_NOMARK | TBCDRF_NOETCHEDEFFECT;
1426     }
1427     return TRUE;
1428 }
1429 
1430 LRESULT CSysPagerWnd::OnCommand(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1431 {
1432     bHandled = FALSE;
1433 
1434     /* Handles the BN_CLICKED notifications sent by the CNotifyToolbar member */
1435     if (HIWORD(wParam) != BN_CLICKED)
1436         return 0;
1437 
1438     INT iBtn = LOWORD(wParam);
1439     if (iBtn < 0)
1440         return 0;
1441 
1442     InternalIconData* notifyItem = Toolbar.GetItemData(iBtn);
1443 
1444     if (!::IsWindow(notifyItem->hWnd))
1445         return 0;
1446 
1447     // TODO: Improve keyboard handling by looking whether one presses
1448     // on ENTER, etc..., which roughly translates into "double-clicking".
1449 
1450     if (notifyItem->uVersionCopy >= NOTIFYICON_VERSION)
1451     {
1452         /* Use new-style notifications if the notification icon supports them */
1453         ::SendNotifyMessage(notifyItem->hWnd,
1454                             notifyItem->uCallbackMessage,
1455                             notifyItem->uID,
1456                             NIN_SELECT); // TODO: Distinguish with NIN_KEYSELECT
1457     }
1458     else if (lParam == -1)
1459     {
1460         /*
1461          * Otherwise, and only if the icon was selected via the keyboard,
1462          * simulate right-click mouse messages. This is not needed if the
1463          * selection was done by mouse because in this case the mouse
1464          * messages were already sent.
1465          */
1466         ::SendNotifyMessage(notifyItem->hWnd,
1467                             notifyItem->uCallbackMessage,
1468                             notifyItem->uID,
1469                             WM_LBUTTONDOWN); // TODO: Distinguish with double-click WM_LBUTTONDBLCLK
1470         ::SendNotifyMessage(notifyItem->hWnd,
1471                             notifyItem->uCallbackMessage,
1472                             notifyItem->uID,
1473                             WM_LBUTTONUP);
1474     }
1475 
1476     return 0;
1477 }
1478 
1479 LRESULT CSysPagerWnd::OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1480 {
1481     LRESULT Ret = TRUE;
1482     SIZE szClient;
1483     szClient.cx = LOWORD(lParam);
1484     szClient.cy = HIWORD(lParam);
1485 
1486     Ret = DefWindowProc(uMsg, wParam, lParam);
1487 
1488     if (Toolbar)
1489     {
1490         Toolbar.SetWindowPos(NULL, 0, 0, szClient.cx, szClient.cy, SWP_NOZORDER);
1491         Toolbar.AutoSize();
1492 
1493         RECT rc;
1494         Toolbar.GetClientRect(&rc);
1495 
1496         SIZE szBar = { rc.right - rc.left, rc.bottom - rc.top };
1497 
1498         INT xOff = (szClient.cx - szBar.cx) / 2;
1499         INT yOff = (szClient.cy - szBar.cy) / 2;
1500 
1501         Toolbar.SetWindowPos(NULL, xOff, yOff, szBar.cx, szBar.cy, SWP_NOZORDER);
1502     }
1503     return Ret;
1504 }
1505 
1506 LRESULT CSysPagerWnd::OnCtxMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1507 {
1508     bHandled = TRUE;
1509     return 0;
1510 }
1511 
1512 LRESULT CSysPagerWnd::OnBalloonPop(UINT uCode, LPNMHDR hdr , BOOL& bHandled)
1513 {
1514     m_BalloonQueue.CloseCurrent();
1515     bHandled = TRUE;
1516     return 0;
1517 }
1518 
1519 LRESULT CSysPagerWnd::OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1520 {
1521     if (m_BalloonQueue.OnTimer(wParam))
1522     {
1523         bHandled = TRUE;
1524     }
1525 
1526     return 0;
1527 }
1528 
1529 LRESULT CSysPagerWnd::OnCopyData(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1530 {
1531     PCOPYDATASTRUCT cpData = (PCOPYDATASTRUCT)lParam;
1532     if (cpData->dwData == 1)
1533     {
1534         /* A taskbar NotifyIcon notification */
1535         PTRAYNOTIFYDATAW pData = (PTRAYNOTIFYDATAW)cpData->lpData;
1536         if (pData->dwSignature == NI_NOTIFY_SIG)
1537             return NotifyIcon(pData->dwMessage, &pData->nid);
1538     }
1539     // TODO: Handle other types of taskbar notifications
1540 
1541     return FALSE;
1542 }
1543 
1544 LRESULT CSysPagerWnd::OnSettingChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1545 {
1546     if (wParam == SPI_SETNONCLIENTMETRICS)
1547     {
1548         Toolbar.ResizeImagelist();
1549     }
1550     return 0;
1551 }
1552 
1553 LRESULT CSysPagerWnd::OnGetMinimumSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1554 {
1555     GetSize((BOOL)wParam, (PSIZE)lParam);
1556     return 0;
1557 }
1558 
1559 HRESULT CSysPagerWnd::Initialize(IN HWND hWndParent)
1560 {
1561     /* Create the window. The tray window is going to move it to the correct
1562         position and resize it as needed. */
1563     DWORD dwStyle = WS_CHILD | WS_CLIPSIBLINGS | WS_VISIBLE;
1564     Create(hWndParent, 0, NULL, dwStyle);
1565     if (!m_hWnd)
1566         return E_FAIL;
1567 
1568     SetWindowTheme(m_hWnd, L"TrayNotify", NULL);
1569 
1570     return S_OK;
1571 }
1572 
1573 HRESULT CSysPagerWnd_CreateInstance(HWND hwndParent, REFIID riid, void **ppv)
1574 {
1575     return ShellObjectCreatorInit<CSysPagerWnd>(hwndParent, riid, ppv);
1576 }
1577