[EXPLORER]
[reactos.git] / base / shell / explorer / trayntfy.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 /*
25 * SysPagerWnd
26 */
27 static const WCHAR szSysPagerWndClass [] = L"SysPager";
28
29 // Data comes from shell32/systray.cpp -> TrayNotifyCDS_Dummy
30 typedef struct _SYS_PAGER_COPY_DATA
31 {
32 DWORD cookie;
33 DWORD notify_code;
34 NOTIFYICONDATA nicon_data;
35 } SYS_PAGER_COPY_DATA, *PSYS_PAGER_COPY_DATA;
36
37
38 struct IconWatcherData
39 {
40 HANDLE hProcess;
41 DWORD ProcessId;
42 NOTIFYICONDATA IconData;
43
44 IconWatcherData(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 m_hWatcherThread(NULL),
74 m_WakeUpEvent(NULL),
75 m_hwndSysTray(NULL),
76 m_Loop(false)
77 {
78 }
79
80 virtual ~CIconWatcher()
81 {
82 Uninitialize();
83 DeleteCriticalSection(&m_ListLock);
84
85 if (m_WakeUpEvent)
86 CloseHandle(m_WakeUpEvent);
87 if (m_hWatcherThread)
88 CloseHandle(m_hWatcherThread);
89 }
90
91 bool Initialize(_In_ HWND hWndParent)
92 {
93 m_hwndSysTray = hWndParent;
94
95 InitializeCriticalSection(&m_ListLock);
96 m_WakeUpEvent = CreateEventW(NULL, FALSE, FALSE, NULL);
97 if (m_WakeUpEvent == NULL)
98 return false;
99
100 m_hWatcherThread = (HANDLE)_beginthreadex(NULL,
101 0,
102 WatcherThread,
103 (LPVOID)this,
104 0,
105 NULL);
106 if (m_hWatcherThread == NULL)
107 return false;
108
109 return true;
110 }
111
112 void Uninitialize()
113 {
114 m_Loop = false;
115 if (m_WakeUpEvent)
116 SetEvent(m_WakeUpEvent);
117
118 EnterCriticalSection(&m_ListLock);
119
120 POSITION Pos;
121 for (size_t i = 0; i < m_WatcherList.GetCount(); i++)
122 {
123 Pos = m_WatcherList.FindIndex(i);
124 if (Pos)
125 {
126 IconWatcherData *Icon;
127 Icon = m_WatcherList.GetAt(Pos);
128 delete Icon;
129 }
130 }
131 m_WatcherList.RemoveAll();
132
133 LeaveCriticalSection(&m_ListLock);
134 }
135
136 bool AddIconToWatcher(_In_ NOTIFYICONDATA *iconData)
137 {
138 DWORD ProcessId;
139 (void)GetWindowThreadProcessId(iconData->hWnd, &ProcessId);
140
141 HANDLE hProcess;
142 hProcess = OpenProcess(SYNCHRONIZE, FALSE, ProcessId);
143 if (hProcess == NULL)
144 {
145 return false;
146 }
147
148 IconWatcherData *Icon = new IconWatcherData(iconData);
149 Icon->hProcess = hProcess;
150 Icon->ProcessId;
151
152 bool Added = false;
153 EnterCriticalSection(&m_ListLock);
154
155 // The likelyhood of someone having more than 64 icons in their tray is
156 // pretty slim. We could spin up a new thread for each multiple of 64, but
157 // it's not worth the effort, so we just won't bother watching those icons
158 if (m_WatcherList.GetCount() < MAXIMUM_WAIT_OBJECTS)
159 {
160 m_WatcherList.AddTail(Icon);
161 SetEvent(m_WakeUpEvent);
162 Added = true;
163 }
164
165 LeaveCriticalSection(&m_ListLock);
166
167 if (!Added)
168 {
169 delete Icon;
170 }
171
172 return Added;
173 }
174
175 bool RemoveIconFromWatcher(_In_ NOTIFYICONDATA *iconData)
176 {
177 EnterCriticalSection(&m_ListLock);
178
179 IconWatcherData *Icon;
180 Icon = GetListEntry(iconData, NULL, true);
181
182 SetEvent(m_WakeUpEvent);
183 LeaveCriticalSection(&m_ListLock);
184
185 delete Icon;
186 return true;
187 }
188
189 IconWatcherData* GetListEntry(_In_opt_ NOTIFYICONDATA *iconData, _In_opt_ HANDLE hProcess, _In_ bool Remove)
190 {
191 IconWatcherData *Entry = NULL;
192 POSITION NextPosition = m_WatcherList.GetHeadPosition();
193 POSITION Position;
194 do
195 {
196 Position = NextPosition;
197
198 Entry = m_WatcherList.GetNext(NextPosition);
199 if (Entry)
200 {
201 if ((iconData && ((Entry->IconData.hWnd == iconData->hWnd) && (Entry->IconData.uID == iconData->uID))) ||
202 (hProcess && (Entry->hProcess == hProcess)))
203 {
204 if (Remove)
205 m_WatcherList.RemoveAt(Position);
206 break;
207 }
208 }
209 Entry = NULL;
210
211 } while (NextPosition != NULL);
212
213 return Entry;
214 }
215
216 private:
217
218 static UINT WINAPI WatcherThread(_In_opt_ LPVOID lpParam)
219 {
220 CIconWatcher* This = reinterpret_cast<CIconWatcher *>(lpParam);
221 HANDLE *WatchList = NULL;
222
223 This->m_Loop = true;
224 while (This->m_Loop)
225 {
226 EnterCriticalSection(&This->m_ListLock);
227
228 DWORD Size;
229 Size = This->m_WatcherList.GetCount() + 1;
230 ASSERT(Size <= MAXIMUM_WAIT_OBJECTS);
231
232 if (WatchList)
233 delete WatchList;
234 WatchList = new HANDLE[Size];
235 WatchList[0] = This->m_WakeUpEvent;
236
237 POSITION Pos;
238 for (size_t i = 0; i < This->m_WatcherList.GetCount(); i++)
239 {
240 Pos = This->m_WatcherList.FindIndex(i);
241 if (Pos)
242 {
243 IconWatcherData *Icon;
244 Icon = This->m_WatcherList.GetAt(Pos);
245 WatchList[i + 1] = Icon->hProcess;
246 }
247 }
248
249 LeaveCriticalSection(&This->m_ListLock);
250
251 DWORD Status;
252 Status = WaitForMultipleObjects(Size,
253 WatchList,
254 FALSE,
255 INFINITE);
256 if (Status == WAIT_OBJECT_0)
257 {
258 // We've been kicked, we have updates to our list (or we're exiting the thread)
259 if (This->m_Loop)
260 TRACE("Updating watched icon list");
261 }
262 else if ((Status >= WAIT_OBJECT_0 + 1) && (Status < Size))
263 {
264 IconWatcherData *Icon;
265 Icon = This->GetListEntry(NULL, WatchList[Status], false);
266
267 TRACE("Pid %lu owns a notification icon and has stopped without deleting it. We'll cleanup on its behalf", Icon->ProcessId);
268
269 int len = FIELD_OFFSET(SYS_PAGER_COPY_DATA, nicon_data) + Icon->IconData.cbSize;
270 PSYS_PAGER_COPY_DATA pnotify_data = (PSYS_PAGER_COPY_DATA)new BYTE[len];
271 pnotify_data->cookie = 1;
272 pnotify_data->notify_code = NIM_DELETE;
273 memcpy(&pnotify_data->nicon_data, &Icon->IconData, Icon->IconData.cbSize);
274
275 COPYDATASTRUCT data;
276 data.dwData = 1;
277 data.cbData = len;
278 data.lpData = pnotify_data;
279
280 BOOL Success = FALSE;
281 HWND parentHWND = ::GetParent(GetParent(This->m_hwndSysTray));
282 if (parentHWND)
283 Success = ::SendMessage(parentHWND, WM_COPYDATA, (WPARAM)&Icon->IconData, (LPARAM)&data);
284
285 delete pnotify_data;
286
287 if (!Success)
288 {
289 // If we failed to handle the delete message, forcibly remove it
290 This->RemoveIconFromWatcher(&Icon->IconData);
291 }
292 }
293 else
294 {
295 if (Status == WAIT_FAILED)
296 {
297 Status = GetLastError();
298 }
299 ERR("Failed to wait on process handles : %lu\n", Status);
300 This->Uninitialize();
301 }
302 }
303
304 if (WatchList)
305 delete WatchList;
306
307 return 0;
308 }
309 };
310
311
312 class CNotifyToolbar :
313 public CWindowImplBaseT< CToolbar<NOTIFYICONDATA>, CControlWinTraits >
314 {
315 HIMAGELIST m_ImageList;
316 int m_VisibleButtonCount;
317
318 public:
319 CNotifyToolbar() :
320 m_ImageList(NULL),
321 m_VisibleButtonCount(0)
322 {
323 }
324
325 ~CNotifyToolbar()
326 {
327 }
328
329 int GetVisibleButtonCount()
330 {
331 return m_VisibleButtonCount;
332 }
333
334 int FindItem(IN HWND hWnd, IN UINT uID, NOTIFYICONDATA ** pdata)
335 {
336 int count = GetButtonCount();
337
338 for (int i = 0; i < count; i++)
339 {
340 NOTIFYICONDATA * data;
341
342 data = GetItemData(i);
343
344 if (data->hWnd == hWnd &&
345 data->uID == uID)
346 {
347 if (pdata)
348 *pdata = data;
349 return i;
350 }
351 }
352
353 return -1;
354 }
355
356 int FindExistingSharedIcon(HICON handle)
357 {
358 int count = GetButtonCount();
359 for (int i = 0; i < count; i++)
360 {
361 NOTIFYICONDATA * data = GetItemData(i);
362 if (data->hIcon == handle)
363 {
364 TBBUTTON btn;
365 GetButton(i, &btn);
366 return btn.iBitmap;
367 }
368 }
369
370 return -1;
371 }
372
373 BOOL AddButton(IN CONST NOTIFYICONDATA *iconData)
374 {
375 TBBUTTON tbBtn;
376 NOTIFYICONDATA * notifyItem;
377 WCHAR text[] = L"";
378
379 TRACE("Adding icon %d from hWnd %08x flags%s%s state%s%s",
380 iconData->uID, iconData->hWnd,
381 (iconData->uFlags & NIF_ICON) ? " ICON" : "",
382 (iconData->uFlags & NIF_STATE) ? " STATE" : "",
383 (iconData->dwState & NIS_HIDDEN) ? " HIDDEN" : "",
384 (iconData->dwState & NIS_SHAREDICON) ? " SHARED" : "");
385
386 int index = FindItem(iconData->hWnd, iconData->uID, &notifyItem);
387 if (index >= 0)
388 {
389 TRACE("Icon %d from hWnd %08x ALREADY EXISTS!", iconData->uID, iconData->hWnd);
390 return FALSE;
391 }
392
393 notifyItem = new NOTIFYICONDATA();
394 ZeroMemory(notifyItem, sizeof(*notifyItem));
395
396 notifyItem->hWnd = iconData->hWnd;
397 notifyItem->uID = iconData->uID;
398
399 tbBtn.fsState = TBSTATE_ENABLED;
400 tbBtn.fsStyle = BTNS_NOPREFIX;
401 tbBtn.dwData = (DWORD_PTR)notifyItem;
402 tbBtn.iString = (INT_PTR) text;
403 tbBtn.idCommand = GetButtonCount();
404
405 if (iconData->uFlags & NIF_STATE)
406 {
407 notifyItem->dwState = iconData->dwState & iconData->dwStateMask;
408 }
409
410 if (iconData->uFlags & NIF_MESSAGE)
411 {
412 notifyItem->uCallbackMessage = iconData->uCallbackMessage;
413 }
414
415 if (iconData->uFlags & NIF_ICON)
416 {
417 notifyItem->hIcon = iconData->hIcon;
418 BOOL hasSharedIcon = notifyItem->dwState & NIS_SHAREDICON;
419 if (hasSharedIcon)
420 {
421 INT iIcon = FindExistingSharedIcon(notifyItem->hIcon);
422 if (iIcon < 0)
423 {
424 notifyItem->hIcon = NULL;
425 TRACE("Shared icon requested, but HICON not found!!!");
426 }
427 tbBtn.iBitmap = iIcon;
428 }
429 else
430 {
431 tbBtn.iBitmap = ImageList_AddIcon(m_ImageList, notifyItem->hIcon);
432 }
433 }
434
435 if (iconData->uFlags & NIF_TIP)
436 {
437 StringCchCopy(notifyItem->szTip, _countof(notifyItem->szTip), iconData->szTip);
438 }
439
440 m_VisibleButtonCount++;
441 if (notifyItem->dwState & NIS_HIDDEN)
442 {
443 tbBtn.fsState |= TBSTATE_HIDDEN;
444 m_VisibleButtonCount--;
445 }
446
447 /* TODO: support NIF_INFO, NIF_GUID, NIF_REALTIME, NIF_SHOWTIP */
448
449 CToolbar::AddButton(&tbBtn);
450 SetButtonSize(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
451
452 return TRUE;
453 }
454
455 BOOL UpdateButton(IN CONST NOTIFYICONDATA *iconData)
456 {
457 NOTIFYICONDATA * notifyItem;
458 TBBUTTONINFO tbbi = { 0 };
459
460 TRACE("Updating icon %d from hWnd %08x flags%s%s state%s%s",
461 iconData->uID, iconData->hWnd,
462 (iconData->uFlags & NIF_ICON) ? " ICON" : "",
463 (iconData->uFlags & NIF_STATE) ? " STATE" : "",
464 (iconData->dwState & NIS_HIDDEN) ? " HIDDEN" : "",
465 (iconData->dwState & NIS_SHAREDICON) ? " SHARED" : "");
466
467 int index = FindItem(iconData->hWnd, iconData->uID, &notifyItem);
468 if (index < 0)
469 {
470 WARN("Icon %d from hWnd %08x DOES NOT EXIST!", iconData->uID, iconData->hWnd);
471 return AddButton(iconData);
472 }
473
474 TBBUTTON btn;
475 GetButton(index, &btn);
476 int oldIconIndex = btn.iBitmap;
477
478 tbbi.cbSize = sizeof(tbbi);
479 tbbi.dwMask = TBIF_BYINDEX | TBIF_COMMAND;
480 tbbi.idCommand = index;
481
482 if (iconData->uFlags & NIF_STATE)
483 {
484 if (iconData->dwStateMask & NIS_HIDDEN &&
485 (notifyItem->dwState & NIS_HIDDEN) != (iconData->dwState & NIS_HIDDEN))
486 {
487 tbbi.dwMask |= TBIF_STATE;
488 if (iconData->dwState & NIS_HIDDEN)
489 {
490 tbbi.fsState |= TBSTATE_HIDDEN;
491 m_VisibleButtonCount--;
492 }
493 else
494 {
495 tbbi.fsState &= ~TBSTATE_HIDDEN;
496 m_VisibleButtonCount++;
497 }
498 }
499
500 notifyItem->dwState &= ~iconData->dwStateMask;
501 notifyItem->dwState |= (iconData->dwState & iconData->dwStateMask);
502 }
503
504 if (iconData->uFlags & NIF_MESSAGE)
505 {
506 notifyItem->uCallbackMessage = iconData->uCallbackMessage;
507 }
508
509 if (iconData->uFlags & NIF_ICON)
510 {
511 BOOL hasSharedIcon = notifyItem->dwState & NIS_SHAREDICON;
512 if (hasSharedIcon)
513 {
514 INT iIcon = FindExistingSharedIcon(iconData->hIcon);
515 if (iIcon >= 0)
516 {
517 notifyItem->hIcon = iconData->hIcon;
518 tbbi.dwMask |= TBIF_IMAGE;
519 tbbi.iImage = iIcon;
520 }
521 else
522 {
523 TRACE("Shared icon requested, but HICON not found!!! IGNORING!");
524 }
525 }
526 else
527 {
528 notifyItem->hIcon = iconData->hIcon;
529 tbbi.dwMask |= TBIF_IMAGE;
530 tbbi.iImage = ImageList_ReplaceIcon(m_ImageList, oldIconIndex, notifyItem->hIcon);
531 }
532 }
533
534 if (iconData->uFlags & NIF_TIP)
535 {
536 StringCchCopy(notifyItem->szTip, _countof(notifyItem->szTip), iconData->szTip);
537 }
538
539 /* TODO: support NIF_INFO, NIF_GUID, NIF_REALTIME, NIF_SHOWTIP */
540
541 SetButtonInfo(index, &tbbi);
542
543 return TRUE;
544 }
545
546 BOOL RemoveButton(IN CONST NOTIFYICONDATA *iconData)
547 {
548 NOTIFYICONDATA * notifyItem;
549
550 TRACE("Removing icon %d from hWnd %08x", iconData->uID, iconData->hWnd);
551
552 int index = FindItem(iconData->hWnd, iconData->uID, &notifyItem);
553 if (index < 0)
554 {
555 TRACE("Icon %d from hWnd %08x ALREADY MISSING!", iconData->uID, iconData->hWnd);
556
557 return FALSE;
558 }
559
560 if (!(notifyItem->dwState & NIS_HIDDEN))
561 {
562 m_VisibleButtonCount--;
563 }
564
565 if (!(notifyItem->dwState & NIS_SHAREDICON))
566 {
567 TBBUTTON btn;
568 GetButton(index, &btn);
569 int oldIconIndex = btn.iBitmap;
570 ImageList_Remove(m_ImageList, oldIconIndex);
571
572 // Update other icons!
573 int count = GetButtonCount();
574 for (int i = 0; i < count; i++)
575 {
576 TBBUTTON btn;
577 GetButton(i, &btn);
578
579 if (btn.iBitmap > oldIconIndex)
580 {
581 TBBUTTONINFO tbbi2 = { 0 };
582 tbbi2.cbSize = sizeof(tbbi2);
583 tbbi2.dwMask = TBIF_BYINDEX | TBIF_IMAGE;
584 tbbi2.iImage = btn.iBitmap-1;
585 SetButtonInfo(i, &tbbi2);
586 }
587 }
588 }
589
590 delete notifyItem;
591 DeleteButton(index);
592
593 return TRUE;
594 }
595
596 VOID GetTooltipText(int index, LPTSTR szTip, DWORD cchTip)
597 {
598 NOTIFYICONDATA * notifyItem;
599 notifyItem = GetItemData(index);
600
601 if (notifyItem)
602 {
603 StringCchCopy(szTip, cchTip, notifyItem->szTip);
604 }
605 }
606
607 VOID ResizeImagelist()
608 {
609 int cx, cy;
610 HIMAGELIST iml;
611
612 if (!ImageList_GetIconSize(m_ImageList, &cx, &cy))
613 return;
614
615 if (cx == GetSystemMetrics(SM_CXSMICON) && cy == GetSystemMetrics(SM_CYSMICON))
616 return;
617
618 iml = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR32 | ILC_MASK, 0, 1000);
619 if (!iml)
620 return;
621
622 ImageList_Destroy(m_ImageList);
623 m_ImageList = iml;
624 SetImageList(m_ImageList);
625
626 int count = GetButtonCount();
627 for (int i = 0; i < count; i++)
628 {
629 NOTIFYICONDATA * data = GetItemData(i);
630 BOOL hasSharedIcon = data->dwState & NIS_SHAREDICON;
631 INT iIcon = hasSharedIcon ? FindExistingSharedIcon(data->hIcon) : -1;
632 if (iIcon < 0)
633 iIcon = ImageList_AddIcon(iml, data->hIcon);
634 TBBUTTONINFO tbbi = { sizeof(tbbi), TBIF_BYINDEX | TBIF_IMAGE, 0, iIcon};
635 SetButtonInfo(i, &tbbi);
636 }
637
638 SetButtonSize(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
639 }
640
641 private:
642
643 VOID SendMouseEvent(IN WORD wIndex, IN UINT uMsg, IN WPARAM wParam)
644 {
645 static LPCWSTR eventNames [] = {
646 L"WM_MOUSEMOVE",
647 L"WM_LBUTTONDOWN",
648 L"WM_LBUTTONUP",
649 L"WM_LBUTTONDBLCLK",
650 L"WM_RBUTTONDOWN",
651 L"WM_RBUTTONUP",
652 L"WM_RBUTTONDBLCLK",
653 L"WM_MBUTTONDOWN",
654 L"WM_MBUTTONUP",
655 L"WM_MBUTTONDBLCLK",
656 L"WM_MOUSEWHEEL",
657 L"WM_XBUTTONDOWN",
658 L"WM_XBUTTONUP",
659 L"WM_XBUTTONDBLCLK"
660 };
661
662 NOTIFYICONDATA * notifyItem = GetItemData(wIndex);
663
664 if (!::IsWindow(notifyItem->hWnd))
665 {
666 // We detect and destroy icons with invalid handles only on mouse move over systray, same as MS does.
667 // Alternatively we could search for them periodically (would waste more resources).
668 TRACE("Destroying icon %d with invalid handle hWnd=%08x\n", notifyItem->uID, notifyItem->hWnd);
669
670 RemoveButton(notifyItem);
671
672 HWND parentHWND = ::GetParent(::GetParent(GetParent()));
673 ::SendMessage(parentHWND, WM_SIZE, 0, 0);
674
675 return;
676 }
677
678 if (uMsg >= WM_MOUSEFIRST && uMsg <= WM_MOUSELAST)
679 {
680 TRACE("Sending message %S from button %d to %p (msg=%x, w=%x, l=%x)...\n",
681 eventNames[uMsg - WM_MOUSEFIRST], wIndex,
682 notifyItem->hWnd, notifyItem->uCallbackMessage, notifyItem->uID, uMsg);
683 }
684
685 DWORD pid;
686 GetWindowThreadProcessId(notifyItem->hWnd, &pid);
687
688 if (pid == GetCurrentProcessId() ||
689 (uMsg >= WM_MOUSEFIRST && uMsg <= WM_MOUSELAST))
690 {
691 ::PostMessage(notifyItem->hWnd,
692 notifyItem->uCallbackMessage,
693 notifyItem->uID,
694 uMsg);
695 }
696 else
697 {
698 SendMessage(notifyItem->hWnd,
699 notifyItem->uCallbackMessage,
700 notifyItem->uID,
701 uMsg);
702 }
703 }
704
705 LRESULT OnMouseEvent(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
706 {
707 POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };
708
709 INT iBtn = HitTest(&pt);
710
711 if (iBtn >= 0)
712 {
713 SendMouseEvent(iBtn, uMsg, wParam);
714 }
715
716 bHandled = FALSE;
717 return FALSE;
718 }
719
720 LRESULT OnTooltipShow(INT uCode, LPNMHDR hdr, BOOL& bHandled)
721 {
722 RECT rcTip, rcItem;
723 ::GetWindowRect(hdr->hwndFrom, &rcTip);
724
725 SIZE szTip = { rcTip.right - rcTip.left, rcTip.bottom - rcTip.top };
726
727 INT iBtn = GetHotItem();
728
729 if (iBtn >= 0)
730 {
731 MONITORINFO monInfo = { 0 };
732 HMONITOR hMon = MonitorFromWindow(m_hWnd, MONITOR_DEFAULTTONEAREST);
733
734 monInfo.cbSize = sizeof(monInfo);
735
736 if (hMon)
737 GetMonitorInfo(hMon, &monInfo);
738 else
739 ::GetWindowRect(GetDesktopWindow(), &monInfo.rcMonitor);
740
741 GetItemRect(iBtn, &rcItem);
742
743 POINT ptItem = { rcItem.left, rcItem.top };
744 SIZE szItem = { rcItem.right - rcItem.left, rcItem.bottom - rcItem.top };
745 ClientToScreen(&ptItem);
746
747 ptItem.x += szItem.cx / 2;
748 ptItem.y -= szTip.cy;
749
750 if (ptItem.x + szTip.cx > monInfo.rcMonitor.right)
751 ptItem.x = monInfo.rcMonitor.right - szTip.cx;
752
753 if (ptItem.y + szTip.cy > monInfo.rcMonitor.bottom)
754 ptItem.y = monInfo.rcMonitor.bottom - szTip.cy;
755
756 if (ptItem.x < monInfo.rcMonitor.left)
757 ptItem.x = monInfo.rcMonitor.left;
758
759 if (ptItem.y < monInfo.rcMonitor.top)
760 ptItem.y = monInfo.rcMonitor.top;
761
762 TRACE("ptItem { %d, %d }\n", ptItem.x, ptItem.y);
763
764 ::SetWindowPos(hdr->hwndFrom, NULL, ptItem.x, ptItem.y, 0, 0, SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
765
766 return TRUE;
767 }
768
769 bHandled = FALSE;
770 return 0;
771 }
772
773
774 public:
775 BEGIN_MSG_MAP(CNotifyToolbar)
776 MESSAGE_RANGE_HANDLER(WM_MOUSEFIRST, WM_MOUSELAST, OnMouseEvent)
777 NOTIFY_CODE_HANDLER(TTN_SHOW, OnTooltipShow)
778 END_MSG_MAP()
779
780 void Initialize(HWND hWndParent)
781 {
782 DWORD styles =
783 WS_CHILD | WS_VISIBLE | WS_CLIPCHILDREN |
784 TBSTYLE_FLAT | TBSTYLE_TOOLTIPS | TBSTYLE_WRAPABLE | TBSTYLE_TRANSPARENT |
785 CCS_TOP | CCS_NORESIZE | CCS_NOPARENTALIGN | CCS_NODIVIDER;
786
787 SubclassWindow(CToolbar::Create(hWndParent, styles));
788
789 SetWindowTheme(m_hWnd, L"TrayNotify", NULL);
790
791 m_ImageList = ImageList_Create(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON), ILC_COLOR32 | ILC_MASK, 0, 1000);
792 SetImageList(m_ImageList);
793
794 TBMETRICS tbm = {sizeof(tbm)};
795 tbm.dwMask = TBMF_BARPAD | TBMF_BUTTONSPACING | TBMF_PAD;
796 tbm.cxPad = 1;
797 tbm.cyPad = 1;
798 tbm.cxButtonSpacing = 1;
799 tbm.cyButtonSpacing = 1;
800 SetMetrics(&tbm);
801
802 SetButtonSize(GetSystemMetrics(SM_CXSMICON), GetSystemMetrics(SM_CYSMICON));
803 }
804 };
805
806 class CSysPagerWnd :
807 public CComObjectRootEx<CComMultiThreadModelNoCS>,
808 public CWindowImpl < CSysPagerWnd, CWindow, CControlWinTraits >,
809 public CIconWatcher
810 {
811 CNotifyToolbar Toolbar;
812
813 public:
814 CSysPagerWnd() {}
815 virtual ~CSysPagerWnd() {}
816
817 LRESULT DrawBackground(HDC hdc)
818 {
819 RECT rect;
820
821 GetClientRect(&rect);
822 DrawThemeParentBackground(m_hWnd, hdc, &rect);
823
824 return TRUE;
825 }
826
827 LRESULT OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
828 {
829 HDC hdc = (HDC) wParam;
830
831 if (!IsAppThemed())
832 {
833 bHandled = FALSE;
834 return 0;
835 }
836
837 return DrawBackground(hdc);
838 }
839
840 LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
841 {
842 Toolbar.Initialize(m_hWnd);
843 CIconWatcher::Initialize(m_hWnd);
844
845 // Explicitly request running applications to re-register their systray icons
846 ::SendNotifyMessageW(HWND_BROADCAST,
847 RegisterWindowMessageW(L"TaskbarCreated"),
848 0, 0);
849
850 return TRUE;
851 }
852
853 LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
854 {
855 CIconWatcher::Uninitialize();
856 return TRUE;
857 }
858
859 BOOL NotifyIconCmd(WPARAM wParam, LPARAM lParam)
860 {
861 PCOPYDATASTRUCT cpData = (PCOPYDATASTRUCT) lParam;
862 if (cpData->dwData == 1)
863 {
864 SYS_PAGER_COPY_DATA * data;
865 NOTIFYICONDATA *iconData;
866 BOOL ret = FALSE;
867
868 int VisibleButtonCount = Toolbar.GetVisibleButtonCount();
869
870 data = (PSYS_PAGER_COPY_DATA) cpData->lpData;
871 iconData = &data->nicon_data;
872
873 TRACE("NotifyIconCmd received. Code=%d\n", data->notify_code);
874 switch (data->notify_code)
875 {
876 case NIM_ADD:
877 ret = Toolbar.AddButton(iconData);
878 if (ret == TRUE)
879 {
880 (void)AddIconToWatcher(iconData);
881 }
882 break;
883 case NIM_MODIFY:
884 ret = Toolbar.UpdateButton(iconData);
885 break;
886 case NIM_DELETE:
887 ret = Toolbar.RemoveButton(iconData);
888 if (ret == TRUE)
889 {
890 (void)RemoveIconFromWatcher(iconData);
891 }
892 break;
893 default:
894 TRACE("NotifyIconCmd received with unknown code %d.\n", data->notify_code);
895 return FALSE;
896 }
897
898 if (VisibleButtonCount != Toolbar.GetVisibleButtonCount())
899 {
900 HWND parentHWND = ::GetParent(GetParent());
901 ::SendMessage(parentHWND, WM_SIZE, 0, 0);
902 }
903
904 return ret;
905 }
906
907 return TRUE;
908 }
909
910 void GetSize(IN BOOL IsHorizontal, IN PSIZE size)
911 {
912 /* Get the ideal height or width */
913 #if 0
914 /* Unfortunately this doens't work correctly in ros */
915 Toolbar.GetIdealSize(!IsHorizontal, size);
916
917 /* Make the reference dimension an exact multiple of the icon size */
918 if (IsHorizontal)
919 size->cy -= size->cy % GetSystemMetrics(SM_CYSMICON);
920 else
921 size->cx -= size->cx % GetSystemMetrics(SM_CXSMICON);
922
923 #else
924 INT rows = 0;
925 INT columns = 0;
926 INT cyButton = GetSystemMetrics(SM_CYSMICON) + 2;
927 INT cxButton = GetSystemMetrics(SM_CXSMICON) + 2;
928 int VisibleButtonCount = Toolbar.GetVisibleButtonCount();
929
930 if (IsHorizontal)
931 {
932 rows = max(size->cy / cyButton, 1);
933 columns = (VisibleButtonCount + rows - 1) / rows;
934 }
935 else
936 {
937 columns = max(size->cx / cxButton, 1);
938 rows = (VisibleButtonCount + columns - 1) / columns;
939 }
940 size->cx = columns * cxButton;
941 size->cy = rows * cyButton;
942 #endif
943 }
944
945 LRESULT OnGetInfoTip(INT uCode, LPNMHDR hdr, BOOL& bHandled)
946 {
947 NMTBGETINFOTIPW * nmtip = (NMTBGETINFOTIPW *) hdr;
948 Toolbar.GetTooltipText(nmtip->iItem, nmtip->pszText, nmtip->cchTextMax);
949 return TRUE;
950 }
951
952 LRESULT OnCustomDraw(INT uCode, LPNMHDR hdr, BOOL& bHandled)
953 {
954 NMCUSTOMDRAW * cdraw = (NMCUSTOMDRAW *) hdr;
955 switch (cdraw->dwDrawStage)
956 {
957 case CDDS_PREPAINT:
958 return CDRF_NOTIFYITEMDRAW;
959
960 case CDDS_ITEMPREPAINT:
961 return TBCDRF_NOBACKGROUND | TBCDRF_NOEDGES | TBCDRF_NOOFFSET | TBCDRF_NOMARK | TBCDRF_NOETCHEDEFFECT;
962 }
963 return TRUE;
964 }
965
966 LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
967 {
968 LRESULT Ret = TRUE;
969 SIZE szClient;
970 szClient.cx = LOWORD(lParam);
971 szClient.cy = HIWORD(lParam);
972
973 Ret = DefWindowProc(uMsg, wParam, lParam);
974
975 if (Toolbar)
976 {
977 Toolbar.SetWindowPos(NULL, 0, 0, szClient.cx, szClient.cy, SWP_NOZORDER);
978 Toolbar.AutoSize();
979
980 RECT rc;
981 Toolbar.GetClientRect(&rc);
982
983 SIZE szBar = { rc.right - rc.left, rc.bottom - rc.top };
984
985 INT xOff = (szClient.cx - szBar.cx) / 2;
986 INT yOff = (szClient.cy - szBar.cy) / 2;
987
988 Toolbar.SetWindowPos(NULL, xOff, yOff, szBar.cx, szBar.cy, SWP_NOZORDER);
989 }
990 return Ret;
991 }
992
993 LRESULT OnCtxMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
994 {
995 bHandled = TRUE;
996 return 0;
997 }
998
999 void ResizeImagelist()
1000 {
1001 Toolbar.ResizeImagelist();
1002 }
1003
1004 DECLARE_WND_CLASS_EX(szSysPagerWndClass, CS_DBLCLKS, COLOR_3DFACE)
1005
1006 BEGIN_MSG_MAP(CSysPagerWnd)
1007 MESSAGE_HANDLER(WM_CREATE, OnCreate)
1008 MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
1009 MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
1010 MESSAGE_HANDLER(WM_SIZE, OnSize)
1011 MESSAGE_HANDLER(WM_CONTEXTMENU, OnCtxMenu)
1012 NOTIFY_CODE_HANDLER(TBN_GETINFOTIPW, OnGetInfoTip)
1013 NOTIFY_CODE_HANDLER(NM_CUSTOMDRAW, OnCustomDraw)
1014 END_MSG_MAP()
1015
1016 HWND _Init(IN HWND hWndParent, IN BOOL bVisible)
1017 {
1018 DWORD dwStyle;
1019
1020 /* Create the window. The tray window is going to move it to the correct
1021 position and resize it as needed. */
1022 dwStyle = WS_CHILD | WS_CLIPSIBLINGS;
1023 if (bVisible)
1024 dwStyle |= WS_VISIBLE;
1025
1026 Create(hWndParent, 0, NULL, dwStyle);
1027
1028 if (!m_hWnd)
1029 {
1030 return NULL;
1031 }
1032
1033 SetWindowTheme(m_hWnd, L"TrayNotify", NULL);
1034
1035 return m_hWnd;
1036 }
1037 };
1038
1039 /*
1040 * TrayClockWnd
1041 */
1042
1043 static const WCHAR szTrayClockWndClass[] = L"TrayClockWClass";
1044
1045 #define ID_TRAYCLOCK_TIMER 0
1046 #define ID_TRAYCLOCK_TIMER_INIT 1
1047
1048 static const struct
1049 {
1050 BOOL IsTime;
1051 DWORD dwFormatFlags;
1052 LPCWSTR lpFormat;
1053 } ClockWndFormats [] = {
1054 { TRUE, 0, NULL },
1055 { FALSE, 0, L"dddd" },
1056 { FALSE, DATE_SHORTDATE, NULL }
1057 };
1058
1059 #define CLOCKWND_FORMAT_COUNT (_ARRAYSIZE(ClockWndFormats))
1060
1061 #define TRAY_CLOCK_WND_SPACING_X 0
1062 #define TRAY_CLOCK_WND_SPACING_Y 0
1063
1064 class CTrayClockWnd :
1065 public CComObjectRootEx<CComMultiThreadModelNoCS>,
1066 public CWindowImpl < CTrayClockWnd, CWindow, CControlWinTraits >
1067 {
1068 HWND hWndNotify;
1069 HFONT hFont;
1070 COLORREF textColor;
1071 RECT rcText;
1072 SYSTEMTIME LocalTime;
1073
1074 union
1075 {
1076 DWORD dwFlags;
1077 struct
1078 {
1079 DWORD IsTimerEnabled : 1;
1080 DWORD IsInitTimerEnabled : 1;
1081 DWORD LinesMeasured : 1;
1082 DWORD IsHorizontal : 1;
1083 };
1084 };
1085 DWORD LineSpacing;
1086 SIZE CurrentSize;
1087 WORD VisibleLines;
1088 SIZE LineSizes[CLOCKWND_FORMAT_COUNT];
1089 WCHAR szLines[CLOCKWND_FORMAT_COUNT][48];
1090
1091 public:
1092 CTrayClockWnd() :
1093 hWndNotify(NULL),
1094 hFont(NULL),
1095 dwFlags(0),
1096 LineSpacing(0),
1097 VisibleLines(0)
1098 {
1099 ZeroMemory(&textColor, sizeof(textColor));
1100 ZeroMemory(&rcText, sizeof(rcText));
1101 ZeroMemory(&LocalTime, sizeof(LocalTime));
1102 ZeroMemory(&CurrentSize, sizeof(CurrentSize));
1103 ZeroMemory(LineSizes, sizeof(LineSizes));
1104 ZeroMemory(szLines, sizeof(szLines));
1105 }
1106 virtual ~CTrayClockWnd() { }
1107
1108 LRESULT OnThemeChanged()
1109 {
1110 LOGFONTW clockFont;
1111 HTHEME clockTheme;
1112 HFONT hFont;
1113
1114 clockTheme = OpenThemeData(m_hWnd, L"Clock");
1115
1116 if (clockTheme)
1117 {
1118 GetThemeFont(clockTheme,
1119 NULL,
1120 CLP_TIME,
1121 0,
1122 TMT_FONT,
1123 &clockFont);
1124
1125 hFont = CreateFontIndirectW(&clockFont);
1126
1127 GetThemeColor(clockTheme,
1128 CLP_TIME,
1129 0,
1130 TMT_TEXTCOLOR,
1131 &textColor);
1132
1133 if (this->hFont != NULL)
1134 DeleteObject(this->hFont);
1135
1136 SetFont(hFont, FALSE);
1137 }
1138 else
1139 {
1140 /* We don't need to set a font here, our parent will use
1141 * WM_SETFONT to set the right one when themes are not enabled. */
1142 textColor = RGB(0, 0, 0);
1143 }
1144
1145 CloseThemeData(clockTheme);
1146
1147 return TRUE;
1148 }
1149
1150 LRESULT OnThemeChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1151 {
1152 return OnThemeChanged();
1153 }
1154
1155 BOOL MeasureLines()
1156 {
1157 HDC hDC;
1158 HFONT hPrevFont;
1159 UINT c, i;
1160 BOOL bRet = TRUE;
1161
1162 hDC = GetDC();
1163 if (hDC != NULL)
1164 {
1165 if (hFont)
1166 hPrevFont = (HFONT) SelectObject(hDC, hFont);
1167
1168 for (i = 0; i < CLOCKWND_FORMAT_COUNT && bRet; i++)
1169 {
1170 if (szLines[i][0] != L'\0' &&
1171 !GetTextExtentPointW(hDC, szLines[i], wcslen(szLines[i]),
1172 &LineSizes[i]))
1173 {
1174 bRet = FALSE;
1175 break;
1176 }
1177 }
1178
1179 if (hFont)
1180 SelectObject(hDC, hPrevFont);
1181
1182 ReleaseDC(hDC);
1183
1184 if (bRet)
1185 {
1186 LineSpacing = 0;
1187
1188 /* calculate the line spacing */
1189 for (i = 0, c = 0; i < CLOCKWND_FORMAT_COUNT; i++)
1190 {
1191 if (LineSizes[i].cx > 0)
1192 {
1193 LineSpacing += LineSizes[i].cy;
1194 c++;
1195 }
1196 }
1197
1198 if (c > 0)
1199 {
1200 /* We want a spacing of 1/2 line */
1201 LineSpacing = (LineSpacing / c) / 2;
1202 }
1203
1204 return TRUE;
1205 }
1206 }
1207
1208 return FALSE;
1209 }
1210
1211 WORD GetMinimumSize(IN BOOL Horizontal, IN OUT PSIZE pSize)
1212 {
1213 WORD iLinesVisible = 0;
1214 UINT i;
1215 SIZE szMax = { 0, 0 };
1216
1217 if (!LinesMeasured)
1218 LinesMeasured = MeasureLines();
1219
1220 if (!LinesMeasured)
1221 return 0;
1222
1223 for (i = 0; i < CLOCKWND_FORMAT_COUNT; i++)
1224 {
1225 if (LineSizes[i].cx != 0)
1226 {
1227 if (iLinesVisible > 0)
1228 {
1229 if (Horizontal)
1230 {
1231 if (szMax.cy + LineSizes[i].cy + (LONG) LineSpacing >
1232 pSize->cy - (2 * TRAY_CLOCK_WND_SPACING_Y))
1233 {
1234 break;
1235 }
1236 }
1237 else
1238 {
1239 if (LineSizes[i].cx > pSize->cx - (2 * TRAY_CLOCK_WND_SPACING_X))
1240 break;
1241 }
1242
1243 /* Add line spacing */
1244 szMax.cy += LineSpacing;
1245 }
1246
1247 iLinesVisible++;
1248
1249 /* Increase maximum rectangle */
1250 szMax.cy += LineSizes[i].cy;
1251 if (LineSizes[i].cx > szMax.cx - (2 * TRAY_CLOCK_WND_SPACING_X))
1252 szMax.cx = LineSizes[i].cx + (2 * TRAY_CLOCK_WND_SPACING_X);
1253 }
1254 }
1255
1256 szMax.cx += 2 * TRAY_CLOCK_WND_SPACING_X;
1257 szMax.cy += 2 * TRAY_CLOCK_WND_SPACING_Y;
1258
1259 *pSize = szMax;
1260
1261 return iLinesVisible;
1262 }
1263
1264
1265 VOID UpdateWnd()
1266 {
1267 SIZE szPrevCurrent;
1268 UINT BufSize, i;
1269 INT iRet;
1270 RECT rcClient;
1271
1272 ZeroMemory(LineSizes, sizeof(LineSizes));
1273
1274 szPrevCurrent = CurrentSize;
1275
1276 for (i = 0; i < CLOCKWND_FORMAT_COUNT; i++)
1277 {
1278 szLines[i][0] = L'\0';
1279 BufSize = _countof(szLines[0]);
1280
1281 if (ClockWndFormats[i].IsTime)
1282 {
1283 iRet = GetTimeFormat(LOCALE_USER_DEFAULT,
1284 g_TaskbarSettings.bShowSeconds ? ClockWndFormats[i].dwFormatFlags : TIME_NOSECONDS,
1285 &LocalTime,
1286 ClockWndFormats[i].lpFormat,
1287 szLines[i],
1288 BufSize);
1289 }
1290 else
1291 {
1292 iRet = GetDateFormat(LOCALE_USER_DEFAULT,
1293 ClockWndFormats[i].dwFormatFlags,
1294 &LocalTime,
1295 ClockWndFormats[i].lpFormat,
1296 szLines[i],
1297 BufSize);
1298 }
1299
1300 if (iRet != 0 && i == 0)
1301 {
1302 /* Set the window text to the time only */
1303 SetWindowText(szLines[i]);
1304 }
1305 }
1306
1307 LinesMeasured = MeasureLines();
1308
1309 if (LinesMeasured &&
1310 GetClientRect(&rcClient))
1311 {
1312 SIZE szWnd;
1313
1314 szWnd.cx = rcClient.right;
1315 szWnd.cy = rcClient.bottom;
1316
1317 VisibleLines = GetMinimumSize(IsHorizontal, &szWnd);
1318 CurrentSize = szWnd;
1319 }
1320
1321 if (IsWindowVisible())
1322 {
1323 InvalidateRect(NULL, TRUE);
1324
1325 if (hWndNotify != NULL &&
1326 (szPrevCurrent.cx != CurrentSize.cx ||
1327 szPrevCurrent.cy != CurrentSize.cy))
1328 {
1329 NMHDR nmh;
1330
1331 nmh.hwndFrom = m_hWnd;
1332 nmh.idFrom = GetWindowLongPtr(GWLP_ID);
1333 nmh.code = NTNWM_REALIGN;
1334
1335 SendMessage(hWndNotify,
1336 WM_NOTIFY,
1337 (WPARAM) nmh.idFrom,
1338 (LPARAM) &nmh);
1339 }
1340 }
1341 }
1342
1343 VOID Update()
1344 {
1345 GetLocalTime(&LocalTime);
1346 UpdateWnd();
1347 }
1348
1349 UINT CalculateDueTime()
1350 {
1351 UINT uiDueTime;
1352
1353 /* Calculate the due time */
1354 GetLocalTime(&LocalTime);
1355 uiDueTime = 1000 - (UINT) LocalTime.wMilliseconds;
1356 if (g_TaskbarSettings.bShowSeconds)
1357 uiDueTime += (UINT) LocalTime.wSecond * 100;
1358 else
1359 uiDueTime += (59 - (UINT) LocalTime.wSecond) * 1000;
1360
1361 if (uiDueTime < USER_TIMER_MINIMUM || uiDueTime > USER_TIMER_MAXIMUM)
1362 uiDueTime = 1000;
1363 else
1364 {
1365 /* Add an artificial delay of 0.05 seconds to make sure the timer
1366 doesn't fire too early*/
1367 uiDueTime += 50;
1368 }
1369
1370 return uiDueTime;
1371 }
1372
1373 BOOL ResetTime()
1374 {
1375 UINT uiDueTime;
1376 BOOL Ret;
1377
1378 /* Disable all timers */
1379 if (IsTimerEnabled)
1380 {
1381 KillTimer(ID_TRAYCLOCK_TIMER);
1382 IsTimerEnabled = FALSE;
1383 }
1384
1385 if (IsInitTimerEnabled)
1386 {
1387 KillTimer(ID_TRAYCLOCK_TIMER_INIT);
1388 }
1389
1390 uiDueTime = CalculateDueTime();
1391
1392 /* Set the new timer */
1393 Ret = SetTimer(ID_TRAYCLOCK_TIMER_INIT, uiDueTime, NULL) != 0;
1394 IsInitTimerEnabled = Ret;
1395
1396 /* Update the time */
1397 Update();
1398
1399 return Ret;
1400 }
1401
1402 VOID CalibrateTimer()
1403 {
1404 UINT uiDueTime;
1405 BOOL Ret;
1406 UINT uiWait1, uiWait2;
1407
1408 /* Kill the initialization timer */
1409 KillTimer(ID_TRAYCLOCK_TIMER_INIT);
1410 IsInitTimerEnabled = FALSE;
1411
1412 uiDueTime = CalculateDueTime();
1413
1414 if (g_TaskbarSettings.bShowSeconds)
1415 {
1416 uiWait1 = 1000 - 200;
1417 uiWait2 = 1000;
1418 }
1419 else
1420 {
1421 uiWait1 = 60 * 1000 - 200;
1422 uiWait2 = 60 * 1000;
1423 }
1424
1425 if (uiDueTime > uiWait1)
1426 {
1427 /* The update of the clock will be up to 200 ms late, but that's
1428 acceptable. We're going to setup a timer that fires depending
1429 uiWait2. */
1430 Ret = SetTimer(ID_TRAYCLOCK_TIMER, uiWait2, NULL) != 0;
1431 IsTimerEnabled = Ret;
1432
1433 /* Update the time */
1434 Update();
1435 }
1436 else
1437 {
1438 /* Recalibrate the timer and recalculate again when the current
1439 minute/second ends. */
1440 ResetTime();
1441 }
1442 }
1443
1444 LRESULT OnDestroy(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1445 {
1446 /* Disable all timers */
1447 if (IsTimerEnabled)
1448 {
1449 KillTimer(ID_TRAYCLOCK_TIMER);
1450 }
1451
1452 if (IsInitTimerEnabled)
1453 {
1454 KillTimer(ID_TRAYCLOCK_TIMER_INIT);
1455 }
1456
1457 return TRUE;
1458 }
1459
1460 LRESULT OnPaint(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1461 {
1462 RECT rcClient;
1463 HFONT hPrevFont;
1464 INT iPrevBkMode;
1465 UINT i, line;
1466
1467 PAINTSTRUCT ps;
1468 HDC hDC = (HDC) wParam;
1469
1470 if (wParam == 0)
1471 {
1472 hDC = BeginPaint(&ps);
1473 }
1474
1475 if (hDC == NULL)
1476 return FALSE;
1477
1478 if (LinesMeasured &&
1479 GetClientRect(&rcClient))
1480 {
1481 iPrevBkMode = SetBkMode(hDC, TRANSPARENT);
1482
1483 SetTextColor(hDC, textColor);
1484
1485 hPrevFont = (HFONT) SelectObject(hDC, hFont);
1486
1487 rcClient.left = (rcClient.right / 2) - (CurrentSize.cx / 2);
1488 rcClient.top = (rcClient.bottom / 2) - (CurrentSize.cy / 2);
1489 rcClient.right = rcClient.left + CurrentSize.cx;
1490 rcClient.bottom = rcClient.top + CurrentSize.cy;
1491
1492 for (i = 0, line = 0;
1493 i < CLOCKWND_FORMAT_COUNT && line < VisibleLines;
1494 i++)
1495 {
1496 if (LineSizes[i].cx != 0)
1497 {
1498 TextOut(hDC,
1499 rcClient.left + (CurrentSize.cx / 2) - (LineSizes[i].cx / 2) +
1500 TRAY_CLOCK_WND_SPACING_X,
1501 rcClient.top + TRAY_CLOCK_WND_SPACING_Y,
1502 szLines[i],
1503 wcslen(szLines[i]));
1504
1505 rcClient.top += LineSizes[i].cy + LineSpacing;
1506 line++;
1507 }
1508 }
1509
1510 SelectObject(hDC, hPrevFont);
1511
1512 SetBkMode(hDC, iPrevBkMode);
1513 }
1514
1515 if (wParam == 0)
1516 {
1517 EndPaint(&ps);
1518 }
1519
1520 return TRUE;
1521 }
1522
1523 VOID SetFont(IN HFONT hNewFont, IN BOOL bRedraw)
1524 {
1525 hFont = hNewFont;
1526 LinesMeasured = MeasureLines();
1527 if (bRedraw)
1528 {
1529 InvalidateRect(NULL, TRUE);
1530 }
1531 }
1532
1533 LRESULT DrawBackground(HDC hdc)
1534 {
1535 RECT rect;
1536
1537 GetClientRect(&rect);
1538 DrawThemeParentBackground(m_hWnd, hdc, &rect);
1539
1540 return TRUE;
1541 }
1542
1543 LRESULT OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1544 {
1545 HDC hdc = (HDC) wParam;
1546
1547 if (!IsAppThemed())
1548 {
1549 bHandled = FALSE;
1550 return 0;
1551 }
1552
1553 return DrawBackground(hdc);
1554 }
1555
1556 LRESULT OnTimer(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1557 {
1558 switch (wParam)
1559 {
1560 case ID_TRAYCLOCK_TIMER:
1561 Update();
1562 break;
1563
1564 case ID_TRAYCLOCK_TIMER_INIT:
1565 CalibrateTimer();
1566 break;
1567 }
1568 return TRUE;
1569 }
1570
1571 LRESULT OnGetMinimumSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1572 {
1573 IsHorizontal = (BOOL) wParam;
1574
1575 return (LRESULT) GetMinimumSize((BOOL) wParam, (PSIZE) lParam) != 0;
1576 }
1577
1578 LRESULT OnUpdateTime(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1579 {
1580 return (LRESULT) ResetTime();
1581 }
1582
1583 LRESULT OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1584 {
1585 return HTTRANSPARENT;
1586 }
1587
1588 LRESULT OnSetFont(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1589 {
1590 SetFont((HFONT) wParam, (BOOL) LOWORD(lParam));
1591 return TRUE;
1592 }
1593
1594 LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1595 {
1596 ResetTime();
1597 return TRUE;
1598 }
1599
1600 LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1601 {
1602 SIZE szClient;
1603
1604 szClient.cx = LOWORD(lParam);
1605 szClient.cy = HIWORD(lParam);
1606
1607 VisibleLines = GetMinimumSize(IsHorizontal, &szClient);
1608 CurrentSize = szClient;
1609
1610 InvalidateRect(NULL, TRUE);
1611 return TRUE;
1612 }
1613
1614 DECLARE_WND_CLASS_EX(szTrayClockWndClass, CS_DBLCLKS, COLOR_3DFACE)
1615
1616 BEGIN_MSG_MAP(CTrayClockWnd)
1617 MESSAGE_HANDLER(WM_CREATE, OnCreate)
1618 MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
1619 MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
1620 MESSAGE_HANDLER(WM_SIZE, OnSize)
1621 MESSAGE_HANDLER(WM_PAINT, OnPaint)
1622 MESSAGE_HANDLER(WM_PRINTCLIENT, OnPaint)
1623 MESSAGE_HANDLER(WM_THEMECHANGED, OnThemeChanged)
1624 MESSAGE_HANDLER(WM_TIMER, OnTimer)
1625 MESSAGE_HANDLER(WM_NCHITTEST, OnNcHitTest)
1626 MESSAGE_HANDLER(WM_SETFONT, OnSetFont)
1627 MESSAGE_HANDLER(TCWM_GETMINIMUMSIZE, OnGetMinimumSize)
1628 MESSAGE_HANDLER(TCWM_UPDATETIME, OnUpdateTime)
1629
1630 END_MSG_MAP()
1631
1632 HWND _Init(IN HWND hWndParent, IN BOOL bVisible)
1633 {
1634 IsHorizontal = TRUE;
1635
1636 hWndNotify = hWndParent;
1637
1638 /* Create the window. The tray window is going to move it to the correct
1639 position and resize it as needed. */
1640 DWORD dwStyle = WS_CHILD | WS_CLIPSIBLINGS;
1641 if (bVisible)
1642 dwStyle |= WS_VISIBLE;
1643
1644 Create(hWndParent, 0, NULL, dwStyle);
1645
1646 if (m_hWnd != NULL)
1647 SetWindowTheme(m_hWnd, L"TrayNotify", NULL);
1648
1649 return m_hWnd;
1650
1651 }
1652 };
1653
1654 /*
1655 * TrayNotifyWnd
1656 */
1657
1658 static const WCHAR szTrayNotifyWndClass [] = TEXT("TrayNotifyWnd");
1659
1660 #define TRAY_NOTIFY_WND_SPACING_X 1
1661 #define TRAY_NOTIFY_WND_SPACING_Y 1
1662
1663 class CTrayNotifyWnd :
1664 public CComObjectRootEx<CComMultiThreadModelNoCS>,
1665 public CWindowImpl < CTrayNotifyWnd, CWindow, CControlWinTraits >
1666 {
1667 HWND hWndNotify;
1668
1669 CSysPagerWnd * m_pager;
1670 CTrayClockWnd * m_clock;
1671
1672 CComPtr<ITrayWindow> TrayWindow;
1673
1674 HTHEME TrayTheme;
1675 SIZE szTrayClockMin;
1676 SIZE szTrayNotify;
1677 MARGINS ContentMargin;
1678 BOOL IsHorizontal;
1679
1680 public:
1681 CTrayNotifyWnd() :
1682 hWndNotify(NULL),
1683 m_pager(NULL),
1684 m_clock(NULL),
1685 TrayTheme(NULL),
1686 IsHorizontal(FALSE)
1687 {
1688 ZeroMemory(&szTrayClockMin, sizeof(szTrayClockMin));
1689 ZeroMemory(&szTrayNotify, sizeof(szTrayNotify));
1690 ZeroMemory(&ContentMargin, sizeof(ContentMargin));
1691 }
1692 virtual ~CTrayNotifyWnd() { }
1693
1694 LRESULT OnThemeChanged()
1695 {
1696 if (TrayTheme)
1697 CloseThemeData(TrayTheme);
1698
1699 if (IsThemeActive())
1700 TrayTheme = OpenThemeData(m_hWnd, L"TrayNotify");
1701 else
1702 TrayTheme = NULL;
1703
1704 if (TrayTheme)
1705 {
1706 SetWindowExStyle(m_hWnd, WS_EX_STATICEDGE, 0);
1707
1708 GetThemeMargins(TrayTheme,
1709 NULL,
1710 TNP_BACKGROUND,
1711 0,
1712 TMT_CONTENTMARGINS,
1713 NULL,
1714 &ContentMargin);
1715 }
1716 else
1717 {
1718 SetWindowExStyle(m_hWnd, WS_EX_STATICEDGE, WS_EX_STATICEDGE);
1719
1720 ContentMargin.cxLeftWidth = 2;
1721 ContentMargin.cxRightWidth = 2;
1722 ContentMargin.cyTopHeight = 2;
1723 ContentMargin.cyBottomHeight = 2;
1724 }
1725
1726 return TRUE;
1727 }
1728
1729 LRESULT OnThemeChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1730 {
1731 return OnThemeChanged();
1732 }
1733
1734 LRESULT OnCreate(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1735 {
1736 m_clock = new CTrayClockWnd();
1737 m_clock->_Init(m_hWnd, !g_TaskbarSettings.sr.HideClock);
1738
1739 m_pager = new CSysPagerWnd();
1740 m_pager->_Init(m_hWnd, !g_TaskbarSettings.sr.HideClock);
1741
1742 return TRUE;
1743 }
1744
1745 BOOL GetMinimumSize(IN OUT PSIZE pSize)
1746 {
1747 SIZE szClock = { 0, 0 };
1748 SIZE szTray = { 0, 0 };
1749
1750 if (!g_TaskbarSettings.sr.HideClock)
1751 {
1752 if (IsHorizontal)
1753 {
1754 szClock.cy = pSize->cy - 2 * TRAY_NOTIFY_WND_SPACING_Y;
1755 if (szClock.cy <= 0)
1756 goto NoClock;
1757 }
1758 else
1759 {
1760 szClock.cx = pSize->cx - 2 * TRAY_NOTIFY_WND_SPACING_X;
1761 if (szClock.cx <= 0)
1762 goto NoClock;
1763 }
1764
1765 m_clock->SendMessage(TCWM_GETMINIMUMSIZE, (WPARAM) IsHorizontal, (LPARAM) &szClock);
1766
1767 szTrayClockMin = szClock;
1768 }
1769 else
1770 NoClock:
1771 szTrayClockMin = szClock;
1772
1773 if (IsHorizontal)
1774 {
1775 szTray.cy = pSize->cy - 2 * TRAY_NOTIFY_WND_SPACING_Y;
1776 }
1777 else
1778 {
1779 szTray.cx = pSize->cx - 2 * TRAY_NOTIFY_WND_SPACING_X;
1780 }
1781
1782 m_pager->GetSize(IsHorizontal, &szTray);
1783
1784 szTrayNotify = szTray;
1785
1786 if (IsHorizontal)
1787 {
1788 pSize->cx = 2 * TRAY_NOTIFY_WND_SPACING_X;
1789
1790 if (!g_TaskbarSettings.sr.HideClock)
1791 pSize->cx += TRAY_NOTIFY_WND_SPACING_X + szTrayClockMin.cx;
1792
1793 pSize->cx += szTray.cx;
1794 }
1795 else
1796 {
1797 pSize->cy = 2 * TRAY_NOTIFY_WND_SPACING_Y;
1798
1799 if (!g_TaskbarSettings.sr.HideClock)
1800 pSize->cy += TRAY_NOTIFY_WND_SPACING_Y + szTrayClockMin.cy;
1801
1802 pSize->cy += szTray.cy;
1803 }
1804
1805 pSize->cy += ContentMargin.cyTopHeight + ContentMargin.cyBottomHeight;
1806 pSize->cx += ContentMargin.cxLeftWidth + ContentMargin.cxRightWidth;
1807
1808 return TRUE;
1809 }
1810
1811 VOID Size(IN const SIZE *pszClient)
1812 {
1813 if (!g_TaskbarSettings.sr.HideClock)
1814 {
1815 POINT ptClock;
1816 SIZE szClock;
1817
1818 if (IsHorizontal)
1819 {
1820 ptClock.x = pszClient->cx - szTrayClockMin.cx - ContentMargin.cxRightWidth;
1821 ptClock.y = ContentMargin.cyTopHeight;
1822 szClock.cx = szTrayClockMin.cx;
1823 szClock.cy = pszClient->cy - ContentMargin.cyTopHeight - ContentMargin.cyBottomHeight;
1824 }
1825 else
1826 {
1827 ptClock.x = ContentMargin.cxLeftWidth;
1828 ptClock.y = pszClient->cy - szTrayClockMin.cy;
1829 szClock.cx = pszClient->cx - ContentMargin.cxLeftWidth - ContentMargin.cxRightWidth;
1830 szClock.cy = szTrayClockMin.cy;
1831 }
1832
1833 m_clock->SetWindowPos(
1834 NULL,
1835 ptClock.x,
1836 ptClock.y,
1837 szClock.cx,
1838 szClock.cy,
1839 SWP_NOZORDER);
1840
1841 POINT ptPager;
1842
1843 if (IsHorizontal)
1844 {
1845 ptPager.x = ContentMargin.cxLeftWidth;
1846 ptPager.y = (pszClient->cy - szTrayNotify.cy)/2;
1847 }
1848 else
1849 {
1850 ptPager.x = (pszClient->cx - szTrayNotify.cx)/2;
1851 ptPager.y = ContentMargin.cyTopHeight;
1852 }
1853
1854 m_pager->SetWindowPos(
1855 NULL,
1856 ptPager.x,
1857 ptPager.y,
1858 szTrayNotify.cx,
1859 szTrayNotify.cy,
1860 SWP_NOZORDER);
1861 }
1862 }
1863
1864 LRESULT DrawBackground(HDC hdc)
1865 {
1866 HRESULT res;
1867 RECT rect;
1868
1869 GetClientRect(&rect);
1870
1871 if (TrayTheme)
1872 {
1873 if (IsThemeBackgroundPartiallyTransparent(TrayTheme, TNP_BACKGROUND, 0))
1874 {
1875 DrawThemeParentBackground(m_hWnd, hdc, &rect);
1876 }
1877
1878 res = DrawThemeBackground(TrayTheme, hdc, TNP_BACKGROUND, 0, &rect, 0);
1879 }
1880
1881 return res;
1882 }
1883
1884 LRESULT OnEraseBackground(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1885 {
1886 HDC hdc = (HDC) wParam;
1887
1888 if (!TrayTheme)
1889 {
1890 bHandled = FALSE;
1891 return 0;
1892 }
1893
1894 return DrawBackground(hdc);
1895 }
1896
1897 BOOL NotifyIconCmd(WPARAM wParam, LPARAM lParam)
1898 {
1899 if (m_pager)
1900 {
1901 return m_pager->NotifyIconCmd(wParam, lParam);
1902 }
1903
1904 return TRUE;
1905 }
1906
1907 BOOL GetClockRect(OUT PRECT rcClock)
1908 {
1909 if (!m_clock->IsWindowVisible())
1910 return FALSE;
1911
1912 return m_clock->GetWindowRect(rcClock);
1913 }
1914
1915 LRESULT OnGetMinimumSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1916 {
1917 BOOL Horizontal = (BOOL) wParam;
1918
1919 if (Horizontal != IsHorizontal)
1920 {
1921 IsHorizontal = Horizontal;
1922 if (IsHorizontal)
1923 SetWindowTheme(m_hWnd, L"TrayNotifyHoriz", NULL);
1924 else
1925 SetWindowTheme(m_hWnd, L"TrayNotifyVert", NULL);
1926 }
1927
1928 return (LRESULT) GetMinimumSize((PSIZE) lParam);
1929 }
1930
1931 LRESULT OnUpdateTime(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1932 {
1933 if (m_clock != NULL)
1934 {
1935 /* Forward the message to the tray clock window procedure */
1936 return m_clock->OnUpdateTime(uMsg, wParam, lParam, bHandled);
1937 }
1938 return FALSE;
1939 }
1940
1941 LRESULT OnSize(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1942 {
1943 SIZE szClient;
1944
1945 szClient.cx = LOWORD(lParam);
1946 szClient.cy = HIWORD(lParam);
1947
1948 Size(&szClient);
1949
1950 return TRUE;
1951 }
1952
1953 LRESULT OnNcHitTest(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1954 {
1955 return HTTRANSPARENT;
1956 }
1957
1958 LRESULT OnShowClock(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1959 {
1960 BOOL PrevHidden = g_TaskbarSettings.sr.HideClock;
1961 g_TaskbarSettings.sr.HideClock = (wParam == 0);
1962
1963 if (m_clock != NULL && PrevHidden != g_TaskbarSettings.sr.HideClock)
1964 {
1965 m_clock->ShowWindow(g_TaskbarSettings.sr.HideClock ? SW_HIDE : SW_SHOW);
1966 }
1967
1968 return (LRESULT) (!PrevHidden);
1969 }
1970
1971 LRESULT OnTaskbarSettingsChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1972 {
1973 TaskbarSettings* newSettings = (TaskbarSettings*)lParam;
1974 if (newSettings->bShowSeconds != g_TaskbarSettings.bShowSeconds)
1975 {
1976 g_TaskbarSettings.bShowSeconds = newSettings->bShowSeconds;
1977 /* TODO: Toggle showing seconds */
1978 }
1979
1980 if (newSettings->sr.HideClock != g_TaskbarSettings.sr.HideClock)
1981 {
1982 g_TaskbarSettings.sr.HideClock = newSettings->sr.HideClock;
1983 /* TODO: Toggle hiding the clock */
1984 }
1985
1986 return 0;
1987 }
1988
1989 LRESULT OnNotify(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
1990 {
1991 const NMHDR *nmh = (const NMHDR *) lParam;
1992
1993 if (nmh->hwndFrom == m_clock->m_hWnd)
1994 {
1995 /* Pass down notifications */
1996 return m_clock->SendMessage(WM_NOTIFY, wParam, lParam);
1997 }
1998
1999 return FALSE;
2000 }
2001
2002 LRESULT OnSetFont(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
2003 {
2004 if (m_clock != NULL)
2005 {
2006 m_clock->SendMessageW(WM_SETFONT, wParam, lParam);
2007 }
2008
2009 bHandled = FALSE;
2010 return FALSE;
2011 }
2012
2013 LRESULT OnCtxMenu(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
2014 {
2015 bHandled = TRUE;
2016 return 0;
2017 }
2018
2019 LRESULT OnSettingChanged(UINT uMsg, WPARAM wParam, LPARAM lParam, BOOL& bHandled)
2020 {
2021 if (wParam == SPI_SETNONCLIENTMETRICS)
2022 {
2023 m_pager->ResizeImagelist();
2024 }
2025 return 0;
2026 }
2027
2028 DECLARE_WND_CLASS_EX(szTrayNotifyWndClass, CS_DBLCLKS, COLOR_3DFACE)
2029
2030 BEGIN_MSG_MAP(CTrayNotifyWnd)
2031 MESSAGE_HANDLER(WM_CREATE, OnCreate)
2032 MESSAGE_HANDLER(WM_THEMECHANGED, OnThemeChanged)
2033 MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBackground)
2034 MESSAGE_HANDLER(WM_SETTINGCHANGE, OnSettingChanged)
2035 MESSAGE_HANDLER(WM_SIZE, OnSize)
2036 MESSAGE_HANDLER(WM_NCHITTEST, OnNcHitTest)
2037 MESSAGE_HANDLER(WM_NOTIFY, OnNotify)
2038 MESSAGE_HANDLER(WM_SETFONT, OnSetFont)
2039 MESSAGE_HANDLER(WM_CONTEXTMENU, OnCtxMenu) // FIXME: This handler is not necessary in Windows
2040 MESSAGE_HANDLER(TNWM_GETMINIMUMSIZE, OnGetMinimumSize)
2041 MESSAGE_HANDLER(TNWM_UPDATETIME, OnUpdateTime)
2042 MESSAGE_HANDLER(TNWM_SHOWCLOCK, OnShowClock)
2043 MESSAGE_HANDLER(TWM_SETTINGSCHANGED, OnTaskbarSettingsChanged)
2044 END_MSG_MAP()
2045
2046 HWND _Init(IN OUT ITrayWindow *TrayWindow)
2047 {
2048 HWND hWndTrayWindow;
2049
2050 hWndTrayWindow = TrayWindow->GetHWND();
2051 if (hWndTrayWindow == NULL)
2052 return NULL;
2053
2054 this->TrayWindow = TrayWindow;
2055 this->hWndNotify = hWndTrayWindow;
2056
2057 DWORD dwStyle = WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN;
2058 return Create(hWndTrayWindow, 0, NULL, dwStyle, WS_EX_STATICEDGE);
2059 }
2060 };
2061
2062 HWND CreateTrayNotifyWnd(IN OUT ITrayWindow *Tray, CTrayNotifyWnd** ppinstance)
2063 {
2064 CTrayNotifyWnd * pTrayNotify = new CTrayNotifyWnd();
2065 // TODO: Destroy after the window is destroyed
2066 *ppinstance = pTrayNotify;
2067
2068 return pTrayNotify->_Init(Tray);
2069 }
2070
2071 BOOL
2072 TrayNotify_NotifyIconCmd(CTrayNotifyWnd* pTrayNotify, WPARAM wParam, LPARAM lParam)
2073 {
2074 return pTrayNotify->NotifyIconCmd(wParam, lParam);
2075 }
2076
2077 BOOL
2078 TrayNotify_GetClockRect(CTrayNotifyWnd* pTrayNotify, OUT PRECT rcClock)
2079 {
2080 return pTrayNotify->GetClockRect(rcClock);
2081 }