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