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