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