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