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