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