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