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