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