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