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, ¬ifyItem);
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, ¬ifyItem);
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, ¬ifyItem);
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, ¬ifyItem);
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