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