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