Reactos
at master 748 lines 21 kB view raw
1/* 2 * ReactOS Explorer 3 * 4 * Copyright 2006 - 2007 Thomas Weidenmueller <w3seek@reactos.org> 5 * 6 * This library is free software; you can redistribute it and/or 7 * modify it under the terms of the GNU Lesser General Public 8 * License as published by the Free Software Foundation; either 9 * version 2.1 of the License, or (at your option) any later version. 10 * 11 * This library is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 * Lesser General Public License for more details. 15 * 16 * You should have received a copy of the GNU Lesser General Public 17 * License along with this library; if not, write to the Free Software 18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA 19 */ 20 21#include "precomp.h" 22 23#include <shdeprecated.h> 24 25/***************************************************************************** 26 ** ITrayBandSite ************************************************************ 27 *****************************************************************************/ 28 29// WARNING: Can't use ATL for this class due to our ATL not fully supporting the AGGREGATION functions needed for this class to be an "outer" class 30// it works just fine this way. 31class CTrayBandSite : 32 public ITrayBandSite, 33 public IBandSite, 34 public IBandSiteStreamCallback 35 /* TODO: IWinEventHandler */ 36{ 37 volatile LONG m_RefCount; 38 39 CComPtr<ITrayWindow> m_Tray; 40 41 CComPtr<IUnknown> m_Inner; 42 CComPtr<IBandSite> m_BandSite; 43 CComPtr<IDeskBand> m_TaskBand; 44 CComPtr<IWinEventHandler> m_WindowEventHandler; 45 CComPtr<IContextMenu> m_ContextMenu; 46 47 HWND m_Rebar; 48 49 union 50 { 51 DWORD dwFlags; 52 struct 53 { 54 DWORD Locked : 1; 55 }; 56 }; 57 58public: 59 STDMETHODIMP_(ULONG) 60 AddRef() override 61 { 62 return InterlockedIncrement(&m_RefCount); 63 } 64 65 STDMETHODIMP_(ULONG) 66 Release() override 67 { 68 ULONG Ret = InterlockedDecrement(&m_RefCount); 69 70 if (Ret == 0) 71 delete this; 72 73 return Ret; 74 } 75 76 STDMETHODIMP 77 QueryInterface(IN REFIID riid, OUT LPVOID *ppvObj) override 78 { 79 if (ppvObj == NULL) 80 return E_POINTER; 81 82 if (IsEqualIID(riid, IID_IUnknown) || IsEqualIID(riid, IID_IBandSiteHelper)) 83 { 84 // return IBandSiteStreamCallback's IUnknown 85 *ppvObj = static_cast<IBandSiteStreamCallback*>(this); 86 } 87 else if (IsEqualIID(riid, IID_IBandSite)) 88 { 89 *ppvObj = static_cast<IBandSite*>(this); 90 } 91 else if (IsEqualIID(riid, IID_IWinEventHandler)) 92 { 93 TRACE("ITaskBandSite: IWinEventHandler queried!\n"); 94 *ppvObj = NULL; 95 return E_NOINTERFACE; 96 } 97 else if (m_Inner != NULL) 98 { 99 return m_Inner->QueryInterface(riid, ppvObj); 100 } 101 else 102 { 103 *ppvObj = NULL; 104 return E_NOINTERFACE; 105 } 106 107 AddRef(); 108 return S_OK; 109 } 110 111public: 112 CTrayBandSite() : 113 m_RefCount(0), 114 m_Rebar(NULL) 115 { 116 } 117 118 virtual ~CTrayBandSite() { } 119 120 STDMETHODIMP 121 OnLoad( 122 IN OUT IStream *pStm, 123 IN REFIID riid, 124 OUT PVOID *pvObj) override 125 { 126 LARGE_INTEGER liPosZero; 127 ULARGE_INTEGER liCurrent; 128 CLSID clsid; 129 ULONG ulRead; 130 HRESULT hRet; 131 132 /* NOTE: Callback routine called by the shell while loading the task band 133 stream. We use it to intercept the default behavior when the task 134 band is loaded from the stream. 135 136 NOTE: riid always points to IID_IUnknown! This is because the shell hasn't 137 read anything from the stream and therefore doesn't know what CLSID 138 it's dealing with. We'll have to find it out ourselves by reading 139 the GUID from the stream. */ 140 141 /* Read the current position of the stream, we'll have to reset it everytime 142 we read a CLSID that's not the task band... */ 143 ZeroMemory(&liPosZero, sizeof(liPosZero)); 144 hRet = pStm->Seek(liPosZero, STREAM_SEEK_CUR, &liCurrent); 145 146 if (SUCCEEDED(hRet)) 147 { 148 /* Now let's read the CLSID from the stream and see if it's our task band */ 149 hRet = pStm->Read(&clsid, (ULONG)sizeof(clsid), &ulRead); 150 151 if (SUCCEEDED(hRet) && ulRead == sizeof(clsid)) 152 { 153 if (IsEqualGUID(clsid, CLSID_ITaskBand)) 154 { 155 ASSERT(m_TaskBand != NULL); 156 /* We're trying to load the task band! Let's create it... */ 157 158 hRet = m_TaskBand->QueryInterface( 159 riid, 160 pvObj); 161 if (SUCCEEDED(hRet)) 162 { 163 /* Load the stream */ 164 TRACE("IBandSiteStreamCallback::OnLoad intercepted the task band CLSID!\n"); 165 } 166 167 return hRet; 168 } 169 } 170 } 171 172 /* Reset the position and let the shell do all the work for us */ 173 hRet = pStm->Seek( 174 *(LARGE_INTEGER*) &liCurrent, 175 STREAM_SEEK_SET, 176 NULL); 177 if (SUCCEEDED(hRet)) 178 { 179 /* Let the shell handle everything else for us :) */ 180 hRet = OleLoadFromStream(pStm, 181 riid, 182 pvObj); 183 } 184 185 if (!SUCCEEDED(hRet)) 186 { 187 TRACE("IBandSiteStreamCallback::OnLoad(0x%p, 0x%p, 0x%p) returns 0x%x\n", pStm, riid, pvObj, hRet); 188 } 189 190 return hRet; 191 } 192 193 STDMETHODIMP 194 OnSave( 195 IN OUT IUnknown *pUnk, 196 IN OUT IStream *pStm) override 197 { 198 /* NOTE: Callback routine called by the shell while saving the task band 199 stream. We use it to intercept the default behavior when the task 200 band is saved to the stream */ 201 /* FIXME: Implement */ 202 TRACE("IBandSiteStreamCallback::OnSave(0x%p, 0x%p) returns E_NOTIMPL\n", pUnk, pStm); 203 return E_NOTIMPL; 204 } 205 206 STDMETHODIMP 207 IsTaskBand(IN IUnknown *punk) override 208 { 209 return IsSameObject(m_BandSite, punk); 210 } 211 212 STDMETHODIMP 213 ProcessMessage( 214 IN HWND hWnd, 215 IN UINT uMsg, 216 IN WPARAM wParam, 217 IN LPARAM lParam, 218 OUT LRESULT *plResult) override 219 { 220 HRESULT hRet; 221 222 ASSERT(m_Rebar != NULL); 223 224 /* Custom task band behavior */ 225 switch (uMsg) 226 { 227 case WM_NOTIFY: 228 { 229 const NMHDR *nmh = (const NMHDR *) lParam; 230 231 if (nmh->hwndFrom == m_Rebar) 232 { 233 switch (nmh->code) 234 { 235 case NM_NCHITTEST: 236 { 237 LPNMMOUSE nmm = (LPNMMOUSE) lParam; 238 239 if (nmm->dwHitInfo == RBHT_CLIENT || nmm->dwHitInfo == RBHT_NOWHERE || 240 nmm->dwItemSpec == (DWORD_PTR) -1) 241 { 242 /* Make the rebar control appear transparent so the user 243 can drag the tray window */ 244 *plResult = HTTRANSPARENT; 245 } 246 return S_OK; 247 } 248 249 case RBN_MINMAX: 250 /* Deny if an Administrator disabled this "feature" */ 251 *plResult = (SHRestricted(REST_NOMOVINGBAND) != 0); 252 return S_OK; 253 } 254 } 255 256 //TRACE("ITrayBandSite::ProcessMessage: WM_NOTIFY for 0x%p, From: 0x%p, Code: NM_FIRST-%u...\n", hWnd, nmh->hwndFrom, NM_FIRST - nmh->code); 257 break; 258 } 259 } 260 261 /* Forward to the shell's IWinEventHandler interface to get the default shell behavior! */ 262 if (!m_WindowEventHandler) 263 return E_FAIL; 264 265 /*TRACE("Calling IWinEventHandler::ProcessMessage(0x%p, 0x%x, 0x%p, 0x%p, 0x%p) hWndRebar=0x%p\n", hWnd, uMsg, wParam, lParam, plResult, hWndRebar);*/ 266 hRet = m_WindowEventHandler->OnWinEvent(hWnd, uMsg, wParam, lParam, plResult); 267 268#if 0 269 if (FAILED(hRet)) 270 { 271 if (uMsg == WM_NOTIFY) 272 { 273 const NMHDR *nmh = (const NMHDR *) lParam; 274 ERR("ITrayBandSite->IWinEventHandler::ProcessMessage: WM_NOTIFY for 0x%p, From: 0x%p, Code: NM_FIRST-%u returned 0x%x\n", hWnd, nmh->hwndFrom, NM_FIRST - nmh->code, hRet); 275 } 276 else 277 { 278 ERR("ITrayBandSite->IWinEventHandler::ProcessMessage(0x%p,0x%x,0x%p,0x%p,0x%p->0x%p) returned: 0x%x\n", hWnd, uMsg, wParam, lParam, plResult, *plResult, hRet); 279 } 280 } 281#endif 282 283 return hRet; 284 } 285 286 STDMETHODIMP 287 AddContextMenus( 288 IN HMENU hmenu, 289 IN UINT indexMenu, 290 IN UINT idCmdFirst, 291 IN UINT idCmdLast, 292 IN UINT uFlags, 293 OUT IContextMenu **ppcm) override 294 { 295 HRESULT hRet; 296 297 if (m_ContextMenu == NULL) 298 { 299 /* Cache the context menu so we don't need to CoCreateInstance all the time... */ 300 hRet = _CBandSiteMenu_CreateInstance(IID_PPV_ARG(IContextMenu, &m_ContextMenu)); 301 if (FAILED_UNEXPECTEDLY(hRet)) 302 return hRet; 303 304 hRet = IUnknown_SetOwner(m_ContextMenu, (IBandSite*)this); 305 if (FAILED_UNEXPECTEDLY(hRet)) 306 return hRet; 307 } 308 309 if (ppcm != NULL) 310 { 311 *ppcm = m_ContextMenu; 312 (*ppcm)->AddRef(); 313 } 314 315 /* Add the menu items */ 316 hRet = m_ContextMenu->QueryContextMenu(hmenu, indexMenu, idCmdFirst, idCmdLast, uFlags); 317 if (FAILED_UNEXPECTEDLY(hRet)) 318 return hRet; 319 320 return S_OK; 321 } 322 323 STDMETHODIMP 324 Lock(IN BOOL bLock) override 325 { 326 BOOL bPrevLocked = Locked; 327 BANDSITEINFO bsi; 328 HRESULT hRet; 329 330 ASSERT(m_BandSite != NULL); 331 332 if (bPrevLocked != bLock) 333 { 334 Locked = bLock; 335 336 bsi.dwMask = BSIM_STYLE; 337 bsi.dwStyle = (Locked ? BSIS_LOCKED | BSIS_NOGRIPPER : BSIS_AUTOGRIPPER); 338 339 hRet = m_BandSite->SetBandSiteInfo(&bsi); 340 if (SUCCEEDED(hRet)) 341 { 342 hRet = Update(); 343 } 344 345 return hRet; 346 } 347 348 return S_FALSE; 349 } 350 351 /*******************************************************************/ 352 353 STDMETHODIMP 354 AddBand(IN IUnknown *punk) override 355 { 356 /* Send the DBID_DELAYINIT command to initialize the band to be added */ 357 /* FIXME: Should be delayed */ 358 IUnknown_Exec(punk, IID_IDeskBand, DBID_DELAYINIT, 0, NULL, NULL); 359 360 HRESULT hr = m_BandSite->AddBand(punk); 361 if (FAILED_UNEXPECTEDLY(hr)) 362 return hr; 363 364 VARIANT vThemeName; 365 V_VT(&vThemeName) = VT_BSTR; 366 V_BSTR(&vThemeName) = SysAllocString(L"TaskBar"); 367 IUnknown_Exec(punk, 368 IID_IDeskBand, 369 DBID_SETWINDOWTHEME, 370 0, 371 &vThemeName, 372 NULL); 373 374 SysFreeString(V_BSTR(&vThemeName)); 375 376 return S_OK; 377 } 378 379 STDMETHODIMP 380 EnumBands( 381 IN UINT uBand, 382 OUT DWORD *pdwBandID) override 383 { 384 return m_BandSite->EnumBands(uBand, pdwBandID); 385 } 386 387 STDMETHODIMP 388 QueryBand( 389 IN DWORD dwBandID, 390 OUT IDeskBand **ppstb, 391 OUT DWORD *pdwState, 392 OUT LPWSTR pszName, 393 IN int cchName) override 394 { 395 HRESULT hRet; 396 IDeskBand *pstb = NULL; 397 398 hRet = m_BandSite->QueryBand( 399 dwBandID, 400 &pstb, 401 pdwState, 402 pszName, 403 cchName); 404 405 if (SUCCEEDED(hRet)) 406 { 407 hRet = IsSameObject(pstb, m_TaskBand); 408 if (hRet == S_OK) 409 { 410 /* Add the BSSF_UNDELETEABLE flag to pdwState because the task bar band shouldn't be deletable */ 411 if (pdwState != NULL) 412 *pdwState |= BSSF_UNDELETEABLE; 413 } 414 else if (!SUCCEEDED(hRet)) 415 { 416 pstb->Release(); 417 pstb = NULL; 418 } 419 420 if (ppstb != NULL) 421 *ppstb = pstb; 422 } 423 else if (ppstb != NULL) 424 *ppstb = NULL; 425 426 return hRet; 427 } 428 429 STDMETHODIMP 430 SetBandState( 431 IN DWORD dwBandID, 432 IN DWORD dwMask, 433 IN DWORD dwState) override 434 { 435 return m_BandSite->SetBandState(dwBandID, dwMask, dwState); 436 } 437 438 STDMETHODIMP 439 RemoveBand(IN DWORD dwBandID) override 440 { 441 return m_BandSite->RemoveBand(dwBandID); 442 } 443 444 STDMETHODIMP 445 GetBandObject( 446 IN DWORD dwBandID, 447 IN REFIID riid, 448 OUT VOID **ppv) override 449 { 450 return m_BandSite->GetBandObject(dwBandID, riid, ppv); 451 } 452 453 STDMETHODIMP 454 SetBandSiteInfo(IN const BANDSITEINFO *pbsinfo) override 455 { 456 return m_BandSite->SetBandSiteInfo(pbsinfo); 457 } 458 459 STDMETHODIMP 460 GetBandSiteInfo(IN OUT BANDSITEINFO *pbsinfo) override 461 { 462 return m_BandSite->GetBandSiteInfo(pbsinfo); 463 } 464 465 virtual BOOL HasTaskBand() 466 { 467 CComPtr<IPersist> pBand; 468 CLSID BandCLSID; 469 DWORD dwBandID; 470 UINT uBand = 0; 471 472 /* Enumerate all bands */ 473 while (SUCCEEDED(m_BandSite->EnumBands(uBand, &dwBandID))) 474 { 475 if (dwBandID && SUCCEEDED(m_BandSite->GetBandObject(dwBandID, IID_PPV_ARG(IPersist, &pBand)))) 476 { 477 if (SUCCEEDED(pBand->GetClassID(&BandCLSID))) 478 { 479 if (IsEqualGUID(BandCLSID, CLSID_ITaskBand)) 480 { 481 return TRUE; 482 } 483 } 484 } 485 uBand++; 486 } 487 488 return FALSE; 489 } 490 491 virtual HRESULT Update() 492 { 493 return IUnknown_Exec(m_Inner, 494 IID_IDeskBand, 495 DBID_BANDINFOCHANGED, 496 0, 497 NULL, 498 NULL); 499 } 500 501 virtual VOID BroadcastOleCommandExec(REFGUID pguidCmdGroup, 502 DWORD nCmdID, 503 DWORD nCmdExecOpt, 504 VARIANTARG *pvaIn, 505 VARIANTARG *pvaOut) 506 { 507 IOleCommandTarget *pOct; 508 DWORD dwBandID; 509 UINT uBand = 0; 510 511 /* Enumerate all bands */ 512 while (SUCCEEDED(m_BandSite->EnumBands(uBand, &dwBandID))) 513 { 514 if (SUCCEEDED(m_BandSite->GetBandObject(dwBandID, IID_PPV_ARG(IOleCommandTarget, &pOct)))) 515 { 516 /* Execute the command */ 517 pOct->Exec( 518 &pguidCmdGroup, 519 nCmdID, 520 nCmdExecOpt, 521 pvaIn, 522 pvaOut); 523 524 pOct->Release(); 525 } 526 527 uBand++; 528 } 529 } 530 531 virtual HRESULT FinishInit() 532 { 533 /* Broadcast the DBID_FINISHINIT command */ 534 BroadcastOleCommandExec(IID_IDeskBand, DBID_FINISHINIT, 0, NULL, NULL); 535 536 return S_OK; 537 } 538 539 virtual HRESULT Show(IN BOOL bShow) 540 { 541 CComPtr<IDeskBarClient> pDbc; 542 HRESULT hRet; 543 544 hRet = m_BandSite->QueryInterface(IID_PPV_ARG(IDeskBarClient, &pDbc)); 545 if (SUCCEEDED(hRet)) 546 { 547 hRet = pDbc->UIActivateDBC(bShow ? DBC_SHOW : DBC_HIDE); 548 } 549 550 return hRet; 551 } 552 553 virtual HRESULT LoadFromStream(IN OUT IStream *pStm) 554 { 555 CComPtr<IPersistStream> pPStm; 556 HRESULT hRet; 557 558 ASSERT(m_BandSite != NULL); 559 560 /* We implement the undocumented COM interface IBandSiteStreamCallback 561 that the shell will query so that we can intercept and custom-load 562 the task band when it finds the task band's CLSID (which is internal). 563 This way we can prevent the shell from attempting to CoCreateInstance 564 the (internal) task band, resulting in a failure... */ 565 hRet = m_BandSite->QueryInterface(IID_PPV_ARG(IPersistStream, &pPStm)); 566 if (SUCCEEDED(hRet)) 567 { 568 hRet = pPStm->Load(pStm); 569 TRACE("->Load() returned 0x%x\n", hRet); 570 } 571 572 return hRet; 573 } 574 575 virtual IStream * GetUserBandsStream(IN DWORD grfMode) 576 { 577 HKEY hkStreams; 578 IStream *Stream = NULL; 579 580 if (RegCreateKeyW(hkExplorer, 581 L"Streams", 582 &hkStreams) == ERROR_SUCCESS) 583 { 584 Stream = SHOpenRegStreamW(hkStreams, 585 L"Desktop", 586 L"TaskbarWinXP", 587 grfMode); 588 589 RegCloseKey(hkStreams); 590 } 591 592 return Stream; 593 } 594 595 virtual IStream * GetDefaultBandsStream(IN DWORD grfMode) 596 { 597 HKEY hkStreams; 598 IStream *Stream = NULL; 599 600 if (RegCreateKeyW(HKEY_LOCAL_MACHINE, 601 L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Streams", 602 &hkStreams) == ERROR_SUCCESS) 603 { 604 Stream = SHOpenRegStreamW(hkStreams, 605 L"Desktop", 606 L"Default Taskbar", 607 grfMode); 608 609 RegCloseKey(hkStreams); 610 } 611 612 return Stream; 613 } 614 615 virtual HRESULT Load() 616 { 617 IStream *pStm; 618 HRESULT hRet; 619 620 /* Try to load the user's settings */ 621 pStm = GetUserBandsStream(STGM_READ); 622 if (pStm != NULL) 623 { 624 hRet = LoadFromStream(pStm); 625 626 TRACE("Loaded user bands settings: 0x%x\n", hRet); 627 pStm->Release(); 628 } 629 else 630 hRet = E_FAIL; 631 632 /* If the user's settings couldn't be loaded, try with 633 default settings (ie. when the user logs in for the 634 first time! */ 635 if (!SUCCEEDED(hRet)) 636 { 637 pStm = GetDefaultBandsStream(STGM_READ); 638 if (pStm != NULL) 639 { 640 hRet = LoadFromStream(pStm); 641 642 TRACE("Loaded default user bands settings: 0x%x\n", hRet); 643 pStm->Release(); 644 } 645 else 646 hRet = E_FAIL; 647 } 648 649 return hRet; 650 } 651 652 HRESULT _Init(IN ITrayWindow *tray, IN IDeskBand* pTaskBand) 653 { 654 CComPtr<IDeskBarClient> pDbc; 655 CComPtr<IDeskBand> pDb; 656 CComPtr<IOleWindow> pOw; 657 HRESULT hRet; 658 659 m_Tray = tray; 660 m_TaskBand = pTaskBand; 661 662 /* Create the RebarBandSite */ 663 hRet = _CBandSite_CreateInstance(static_cast<IBandSite*>(this), IID_PPV_ARG(IUnknown, &m_Inner)); 664 if (FAILED_UNEXPECTEDLY(hRet)) 665 return hRet; 666 667 hRet = m_Inner->QueryInterface(IID_PPV_ARG(IBandSite, &m_BandSite)); 668 if (FAILED_UNEXPECTEDLY(hRet)) 669 return hRet; 670 671 hRet = m_Inner->QueryInterface(IID_PPV_ARG(IWinEventHandler, &m_WindowEventHandler)); 672 if (FAILED_UNEXPECTEDLY(hRet)) 673 return hRet; 674 675 hRet = m_Inner->QueryInterface(IID_PPV_ARG(IDeskBarClient, &pDbc)); 676 if (FAILED_UNEXPECTEDLY(hRet)) 677 return hRet; 678 679 680 681 682 /* Crete the rebar in the tray */ 683 hRet = pDbc->SetDeskBarSite(tray); 684 if (FAILED_UNEXPECTEDLY(hRet)) 685 return hRet; 686 687 hRet = pDbc->GetWindow(&m_Rebar); 688 if (FAILED_UNEXPECTEDLY(hRet)) 689 return hRet; 690 691 SetWindowStyle(m_Rebar, RBS_BANDBORDERS, 0); 692 693 /* Set the Desk Bar mode to the current one */ 694 DWORD dwMode = 0; 695 /* FIXME: We need to set the mode (and update) whenever the user docks 696 the tray window to another monitor edge! */ 697 if (!m_Tray->IsHorizontal()) 698 dwMode = DBIF_VIEWMODE_VERTICAL; 699 700 hRet = pDbc->SetModeDBC(dwMode); 701 702 /* Load the saved state of the task band site */ 703 /* FIXME: We should delay loading shell extensions, also see DBID_DELAYINIT */ 704 Load(); 705 706 /* Add the task bar band if it hasn't been added while loading */ 707 if (!HasTaskBand()) 708 { 709 hRet = m_BandSite->AddBand(m_TaskBand); 710 if (FAILED_UNEXPECTEDLY(hRet)) 711 return hRet; 712 } 713 714 /* Should we send this after showing it? */ 715 Update(); 716 717 /* FIXME: When should we send this? Does anyone care anyway? */ 718 FinishInit(); 719 720 /* Activate the band site */ 721 Show(TRUE); 722 723 return S_OK; 724 } 725}; 726/*******************************************************************/ 727 728HRESULT CTrayBandSite_CreateInstance(IN ITrayWindow *tray, IN IDeskBand* pTaskBand, OUT ITrayBandSite** pBandSite) 729{ 730 HRESULT hr; 731 732 CTrayBandSite * tb = new CTrayBandSite(); 733 if (!tb) 734 return E_FAIL; 735 736 tb->AddRef(); 737 738 hr = tb->_Init(tray, pTaskBand); 739 if (FAILED_UNEXPECTEDLY(hr)) 740 { 741 tb->Release(); 742 return hr; 743 } 744 745 *pBandSite = tb; 746 747 return S_OK; 748}