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