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