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