1 /* 2 * PROJECT: NT Object Namespace shell extension 3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) 4 * PURPOSE: Folder view class header and implementation 5 * COPYRIGHT: Copyright 2015-2017 David Quintana <gigaherz@gmail.com> 6 */ 7 8 #pragma once 9 10 extern const GUID CLSID_NtObjectFolder; 11 12 class CFolderViewCB : 13 public CComObjectRootEx<CComMultiThreadModelNoCS>, 14 public IShellFolderViewCB 15 { 16 IShellView* m_View; 17 18 public: 19 20 CFolderViewCB() : m_View(NULL) {} 21 virtual ~CFolderViewCB() {} 22 23 virtual HRESULT STDMETHODCALLTYPE MessageSFVCB(UINT uMsg, WPARAM wParam, LPARAM lParam) 24 { 25 switch (uMsg) 26 { 27 case SFVM_DEFVIEWMODE: 28 { 29 FOLDERVIEWMODE* pViewMode = (FOLDERVIEWMODE*)lParam; 30 *pViewMode = FVM_DETAILS; 31 return S_OK; 32 } 33 34 case SFVM_COLUMNCLICK: 35 return S_FALSE; 36 37 case SFVM_BACKGROUNDENUM: 38 return S_OK; 39 } 40 41 DbgPrint("MessageSFVCB unimplemented %d %08x %08x\n", uMsg, wParam, lParam); 42 return E_NOTIMPL; 43 } 44 45 virtual HRESULT STDMETHODCALLTYPE Initialize(IShellView* psv) 46 { 47 m_View = psv; 48 return S_OK; 49 } 50 51 DECLARE_NOT_AGGREGATABLE(CFolderViewCB) 52 DECLARE_PROTECT_FINAL_CONSTRUCT() 53 54 BEGIN_COM_MAP(CFolderViewCB) 55 COM_INTERFACE_ENTRY_IID(IID_IShellFolderViewCB, IShellFolderViewCB) 56 END_COM_MAP() 57 }; 58 59 template<class TSelf, typename TItemId, class TExtractIcon> 60 class CCommonFolder : 61 public CComObjectRootEx<CComMultiThreadModelNoCS>, 62 public IShellFolder2, 63 public IPersistFolder2 64 { 65 protected: 66 WCHAR m_NtPath[MAX_PATH]; 67 68 LPITEMIDLIST m_shellPidl; 69 70 public: 71 72 CCommonFolder() : 73 m_shellPidl(NULL) 74 { 75 } 76 77 virtual ~CCommonFolder() 78 { 79 if (m_shellPidl) 80 ILFree(m_shellPidl); 81 } 82 83 // IShellFolder 84 virtual HRESULT STDMETHODCALLTYPE ParseDisplayName( 85 HWND hwndOwner, 86 LPBC pbcReserved, 87 LPOLESTR lpszDisplayName, 88 ULONG *pchEaten, 89 LPITEMIDLIST *ppidl, 90 ULONG *pdwAttributes) 91 { 92 if (!ppidl) 93 return E_POINTER; 94 95 if (pchEaten) 96 *pchEaten = 0; 97 98 if (pdwAttributes) 99 *pdwAttributes = 0; 100 101 TRACE("CCommonFolder::ParseDisplayName name=%S (ntPath=%S)\n", lpszDisplayName, m_NtPath); 102 103 const TItemId * info; 104 IEnumIDList * it; 105 HRESULT hr = EnumObjects(hwndOwner, SHCONTF_FOLDERS | SHCONTF_NONFOLDERS, &it); 106 if (FAILED(hr)) 107 return hr; 108 109 PWSTR end = StrChrW(lpszDisplayName, '\\'); 110 int length = end ? end - lpszDisplayName : wcslen(lpszDisplayName); 111 112 while (TRUE) 113 { 114 hr = it->Next(1, ppidl, NULL); 115 116 if (FAILED(hr)) 117 return hr; 118 119 if (hr != S_OK) 120 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); 121 122 hr = GetInfoFromPidl(*ppidl, &info); 123 if (FAILED_UNEXPECTEDLY(hr)) 124 return hr; 125 126 if (StrCmpNW(info->entryName, lpszDisplayName, length) == 0) 127 break; 128 } 129 130 // if has remaining path to parse (and the path didn't just terminate in a backslash) 131 if (end && wcslen(end) > 1) 132 { 133 CComPtr<IShellFolder> psfChild; 134 hr = BindToObject(*ppidl, pbcReserved, IID_PPV_ARG(IShellFolder, &psfChild)); 135 if (FAILED_UNEXPECTEDLY(hr)) 136 return hr; 137 138 LPITEMIDLIST child; 139 hr = psfChild->ParseDisplayName(hwndOwner, pbcReserved, end + 1, pchEaten, &child, pdwAttributes); 140 if (FAILED(hr)) 141 return hr; 142 143 LPITEMIDLIST old = *ppidl; 144 *ppidl = ILCombine(old, child); 145 ILFree(old); 146 147 // Count the path separator 148 if (pchEaten) 149 (*pchEaten) += 1; 150 } 151 else 152 { 153 if (pdwAttributes) 154 *pdwAttributes = ConvertAttributes(info, pdwAttributes); 155 } 156 157 if (pchEaten) 158 *pchEaten += wcslen(info->entryName); 159 160 return S_OK; 161 } 162 163 virtual HRESULT STDMETHODCALLTYPE EnumObjects( 164 HWND hwndOwner, 165 SHCONTF grfFlags, 166 IEnumIDList **ppenumIDList) PURE; 167 168 virtual HRESULT STDMETHODCALLTYPE BindToObject( 169 LPCITEMIDLIST pidl, 170 LPBC pbcReserved, 171 REFIID riid, 172 void **ppvOut) 173 { 174 const TItemId * info; 175 176 if (IsEqualIID(riid, IID_IShellFolder)) 177 { 178 HRESULT hr = GetInfoFromPidl(pidl, &info); 179 if (FAILED_UNEXPECTEDLY(hr)) 180 return hr; 181 182 WCHAR path[MAX_PATH]; 183 184 StringCbCopyW(path, sizeof(path), m_NtPath); 185 PathAppendW(path, info->entryName); 186 187 LPITEMIDLIST first = ILCloneFirst(pidl); 188 LPCITEMIDLIST rest = ILGetNext(pidl); 189 190 LPITEMIDLIST fullPidl = ILCombine(m_shellPidl, first); 191 192 CComPtr<IShellFolder> psfChild; 193 hr = InternalBindToObject(path, info, first, rest, fullPidl, pbcReserved, &psfChild); 194 195 ILFree(fullPidl); 196 ILFree(first); 197 198 if (FAILED(hr)) 199 return hr; 200 201 if (hr == S_FALSE) 202 return S_OK; 203 204 if (rest->mkid.cb > 0) 205 { 206 return psfChild->BindToObject(rest, pbcReserved, riid, ppvOut); 207 } 208 209 return psfChild->QueryInterface(riid, ppvOut); 210 } 211 212 return E_NOTIMPL; 213 } 214 215 protected: 216 virtual HRESULT STDMETHODCALLTYPE InternalBindToObject( 217 PWSTR path, 218 const TItemId * info, 219 LPITEMIDLIST first, 220 LPCITEMIDLIST rest, 221 LPITEMIDLIST fullPidl, 222 LPBC pbcReserved, 223 IShellFolder** ppsfChild) PURE; 224 225 virtual HRESULT STDMETHODCALLTYPE ResolveSymLink( 226 const TItemId * info, 227 LPITEMIDLIST * fullPidl) 228 { 229 return E_NOTIMPL; 230 } 231 232 public: 233 234 virtual HRESULT STDMETHODCALLTYPE BindToStorage( 235 LPCITEMIDLIST pidl, 236 LPBC pbcReserved, 237 REFIID riid, 238 void **ppvObj) 239 { 240 UNIMPLEMENTED; 241 return E_NOTIMPL; 242 } 243 244 virtual HRESULT STDMETHODCALLTYPE CompareIDs( 245 LPARAM lParam, 246 LPCITEMIDLIST pidl1, 247 LPCITEMIDLIST pidl2) 248 { 249 HRESULT hr; 250 251 TRACE("CompareIDs %d\n", lParam); 252 253 const TItemId * id1; 254 hr = GetInfoFromPidl(pidl1, &id1); 255 if (FAILED(hr)) 256 return E_INVALIDARG; 257 258 const TItemId * id2; 259 hr = GetInfoFromPidl(pidl2, &id2); 260 if (FAILED(hr)) 261 return E_INVALIDARG; 262 263 hr = CompareIDs(lParam, id1, id2); 264 if (hr != S_EQUAL) 265 return hr; 266 267 // The wollowing snipped is basically SHELL32_CompareChildren 268 269 PUIDLIST_RELATIVE rest1 = ILGetNext(pidl1); 270 PUIDLIST_RELATIVE rest2 = ILGetNext(pidl2); 271 272 bool isEmpty1 = (rest1->mkid.cb == 0); 273 bool isEmpty2 = (rest2->mkid.cb == 0); 274 275 if (isEmpty1 || isEmpty2) 276 return MAKE_COMPARE_HRESULT(isEmpty2 - isEmpty1); 277 278 LPCITEMIDLIST first1 = ILCloneFirst(pidl1); 279 if (!first1) 280 return E_OUTOFMEMORY; 281 282 CComPtr<IShellFolder> psfNext; 283 hr = BindToObject(first1, NULL, IID_PPV_ARG(IShellFolder, &psfNext)); 284 if (FAILED_UNEXPECTEDLY(hr)) 285 return hr; 286 287 return psfNext->CompareIDs(lParam, rest1, rest2); 288 } 289 290 protected: 291 virtual HRESULT STDMETHODCALLTYPE CompareName( 292 LPARAM lParam, 293 const TItemId * first, 294 const TItemId * second) 295 { 296 bool f1 = IsFolder(first); 297 bool f2 = IsFolder(second); 298 299 HRESULT hr = MAKE_COMPARE_HRESULT(f2 - f1); 300 if (hr != S_EQUAL) 301 return hr; 302 303 bool canonical = (lParam & 0xFFFF0000) == SHCIDS_CANONICALONLY; 304 if (canonical) 305 { 306 // Shortcut: avoid comparing contents if not necessary when the results are not for display. 307 hr = MAKE_COMPARE_HRESULT(second->entryNameLength - first->entryNameLength); 308 if (hr != S_EQUAL) 309 return hr; 310 311 int minlength = min(first->entryNameLength, second->entryNameLength); 312 if (minlength > 0) 313 { 314 hr = MAKE_COMPARE_HRESULT(memcmp(first->entryName, second->entryName, minlength)); 315 if (hr != S_EQUAL) 316 return hr; 317 } 318 319 return S_EQUAL; 320 } 321 322 int minlength = min(first->entryNameLength, second->entryNameLength); 323 if (minlength > 0) 324 { 325 hr = MAKE_COMPARE_HRESULT(StrCmpNW(first->entryName, second->entryName, minlength / sizeof(WCHAR))); 326 if (hr != S_EQUAL) 327 return hr; 328 } 329 330 return MAKE_COMPARE_HRESULT(second->entryNameLength - first->entryNameLength); 331 } 332 333 public: 334 virtual HRESULT STDMETHODCALLTYPE CreateViewObject( 335 HWND hwndOwner, 336 REFIID riid, 337 void **ppvOut) 338 { 339 if (!IsEqualIID(riid, IID_IShellView)) 340 return E_NOINTERFACE; 341 342 _CComObject<CFolderViewCB> *pcb; 343 344 HRESULT hr = _CComObject<CFolderViewCB>::CreateInstance(&pcb); 345 if (FAILED(hr)) 346 return hr; 347 348 pcb->AddRef(); 349 350 SFV_CREATE sfv; 351 sfv.cbSize = sizeof(sfv); 352 sfv.pshf = this; 353 sfv.psvOuter = NULL; 354 sfv.psfvcb = pcb; 355 356 IShellView* view; 357 358 hr = SHCreateShellFolderView(&sfv, &view); 359 if (FAILED(hr)) 360 return hr; 361 362 pcb->Initialize(view); 363 364 pcb->Release(); 365 366 *ppvOut = view; 367 368 return S_OK; 369 } 370 371 virtual HRESULT STDMETHODCALLTYPE GetAttributesOf( 372 UINT cidl, 373 PCUITEMID_CHILD_ARRAY apidl, 374 SFGAOF *rgfInOut) 375 { 376 const TItemId * info; 377 378 TRACE("GetAttributesOf %d\n", cidl); 379 380 if (cidl == 0) 381 { 382 *rgfInOut &= SFGAO_FOLDER | SFGAO_HASSUBFOLDER | SFGAO_BROWSABLE; 383 return S_OK; 384 } 385 386 for (int i = 0; i < (int)cidl; i++) 387 { 388 PCUITEMID_CHILD pidl = apidl[i]; 389 390 HRESULT hr = GetInfoFromPidl(pidl, &info); 391 if (FAILED_UNEXPECTEDLY(hr)) 392 return hr; 393 394 // Update attributes. 395 *rgfInOut = ConvertAttributes(info, rgfInOut); 396 } 397 398 return S_OK; 399 } 400 401 virtual HRESULT STDMETHODCALLTYPE GetUIObjectOf( 402 HWND hwndOwner, 403 UINT cidl, 404 PCUITEMID_CHILD_ARRAY apidl, 405 REFIID riid, 406 UINT *prgfInOut, 407 void **ppvOut) 408 { 409 DWORD res; 410 TRACE("GetUIObjectOf\n"); 411 412 if (IsEqualIID(riid, IID_IContextMenu) || 413 IsEqualIID(riid, IID_IContextMenu2) || 414 IsEqualIID(riid, IID_IContextMenu3)) 415 { 416 CComPtr<IContextMenu> pcm; 417 418 HKEY keys[1]; 419 420 LPITEMIDLIST parent = m_shellPidl; 421 422 CComPtr<IShellFolder> psfParent = this; 423 424 LPCITEMIDLIST child; 425 426 int nkeys = _countof(keys); 427 if (cidl == 1 && IsSymLink(apidl[0])) 428 { 429 const TItemId * info; 430 HRESULT hr = GetInfoFromPidl(apidl[0], &info); 431 if (FAILED(hr)) 432 return hr; 433 434 LPITEMIDLIST target; 435 hr = ResolveSymLink(info, &target); 436 if (FAILED(hr)) 437 return hr; 438 439 CComPtr<IShellFolder> psfTarget; 440 hr = ::SHBindToParent(target, IID_PPV_ARG(IShellFolder, &psfTarget), &child); 441 if (FAILED(hr)) 442 { 443 ILFree(target); 444 return hr; 445 } 446 447 parent = ILClone(target); 448 ILRemoveLastID(parent); 449 psfParent = psfTarget; 450 451 apidl = &child; 452 } 453 454 if (cidl == 1 && IsFolder(apidl[0])) 455 { 456 res = RegOpenKey(HKEY_CLASSES_ROOT, L"Folder", keys + 0); 457 if (!NT_SUCCESS(res)) 458 return HRESULT_FROM_NT(res); 459 } 460 else 461 { 462 nkeys = 0; 463 } 464 465 HRESULT hr = CDefFolderMenu_Create2(parent, hwndOwner, cidl, apidl, psfParent, DefCtxMenuCallback, nkeys, keys, &pcm); 466 if (FAILED_UNEXPECTEDLY(hr)) 467 return hr; 468 469 return pcm->QueryInterface(riid, ppvOut); 470 } 471 472 if (IsEqualIID(riid, IID_IExtractIconW)) 473 { 474 return ShellObjectCreatorInit<TExtractIcon>(m_NtPath, m_shellPidl, cidl, apidl, riid, ppvOut); 475 } 476 477 if (IsEqualIID(riid, IID_IDataObject)) 478 { 479 return CIDLData_CreateFromIDArray(m_shellPidl, cidl, apidl, (IDataObject**)ppvOut); 480 } 481 482 if (IsEqualIID(riid, IID_IQueryAssociations)) 483 { 484 if (cidl == 1 && IsFolder(apidl[0])) 485 { 486 CComPtr<IQueryAssociations> pqa; 487 HRESULT hr = AssocCreate(CLSID_QueryAssociations, IID_PPV_ARG(IQueryAssociations, &pqa)); 488 if (FAILED_UNEXPECTEDLY(hr)) 489 return hr; 490 491 hr = pqa->Init(ASSOCF_INIT_DEFAULTTOFOLDER, L"NTObjShEx.NTDirectory", NULL, hwndOwner); 492 if (FAILED_UNEXPECTEDLY(hr)) 493 return hr; 494 495 return pqa->QueryInterface(riid, ppvOut); 496 } 497 } 498 499 return E_NOTIMPL; 500 } 501 502 virtual HRESULT STDMETHODCALLTYPE GetDisplayNameOf( 503 LPCITEMIDLIST pidl, 504 SHGDNF uFlags, 505 STRRET *lpName) 506 { 507 const TItemId * info; 508 509 TRACE("GetDisplayNameOf %p\n", pidl); 510 511 HRESULT hr = GetInfoFromPidl(pidl, &info); 512 if (FAILED_UNEXPECTEDLY(hr)) 513 return hr; 514 515 if (GET_SHGDN_FOR(uFlags) & SHGDN_FOREDITING) 516 { 517 hr = MakeStrRetFromString(info->entryName, info->entryNameLength, lpName); 518 if (FAILED_UNEXPECTEDLY(hr)) 519 return hr; 520 } 521 522 WCHAR path[MAX_PATH] = { 0 }; 523 524 if (GET_SHGDN_FOR(uFlags) & SHGDN_FORPARSING) 525 { 526 if (GET_SHGDN_RELATION(uFlags) != SHGDN_INFOLDER) 527 { 528 hr = GetFullName(m_shellPidl, uFlags, path, _countof(path)); 529 if (FAILED_UNEXPECTEDLY(hr)) 530 return hr; 531 } 532 } 533 534 PathAppendW(path, info->entryName); 535 536 LPCITEMIDLIST pidlNext = ILGetNext(pidl); 537 if (pidlNext && pidlNext->mkid.cb > 0) 538 { 539 LPITEMIDLIST pidlFirst = ILCloneFirst(pidl); 540 541 CComPtr<IShellFolder> psfChild; 542 hr = BindToObject(pidlFirst, NULL, IID_PPV_ARG(IShellFolder, &psfChild)); 543 if (FAILED_UNEXPECTEDLY(hr)) 544 return hr; 545 546 WCHAR temp[MAX_PATH]; 547 STRRET childName; 548 549 hr = psfChild->GetDisplayNameOf(pidlNext, uFlags | SHGDN_INFOLDER, &childName); 550 if (FAILED_UNEXPECTEDLY(hr)) 551 return hr; 552 553 hr = StrRetToBufW(&childName, pidlNext, temp, _countof(temp)); 554 if (FAILED_UNEXPECTEDLY(hr)) 555 return hr; 556 557 PathAppendW(path, temp); 558 559 ILFree(pidlFirst); 560 } 561 562 hr = MakeStrRetFromString(path, lpName); 563 if (FAILED_UNEXPECTEDLY(hr)) 564 return hr; 565 566 return S_OK; 567 } 568 569 virtual HRESULT STDMETHODCALLTYPE SetNameOf( 570 HWND hwnd, 571 LPCITEMIDLIST pidl, 572 LPCOLESTR lpszName, 573 SHGDNF uFlags, 574 LPITEMIDLIST *ppidlOut) 575 { 576 UNIMPLEMENTED; 577 return E_NOTIMPL; 578 } 579 580 // IShellFolder2 581 virtual HRESULT STDMETHODCALLTYPE GetDefaultSearchGUID( 582 GUID *lpguid) 583 { 584 UNIMPLEMENTED; 585 return E_NOTIMPL; 586 } 587 588 virtual HRESULT STDMETHODCALLTYPE EnumSearches( 589 IEnumExtraSearch **ppenum) 590 { 591 UNIMPLEMENTED; 592 return E_NOTIMPL; 593 } 594 595 virtual HRESULT STDMETHODCALLTYPE GetDefaultColumn( 596 DWORD dwReserved, 597 ULONG *pSort, 598 ULONG *pDisplay) 599 { 600 if (pSort) 601 *pSort = 0; 602 if (pDisplay) 603 *pDisplay = 0; 604 return S_OK; 605 } 606 607 virtual HRESULT STDMETHODCALLTYPE GetDefaultColumnState( 608 UINT iColumn, 609 SHCOLSTATEF *pcsFlags) PURE; 610 611 virtual HRESULT STDMETHODCALLTYPE GetDetailsEx( 612 LPCITEMIDLIST pidl, 613 const SHCOLUMNID *pscid, 614 VARIANT *pv) PURE; 615 616 virtual HRESULT STDMETHODCALLTYPE GetDetailsOf( 617 LPCITEMIDLIST pidl, 618 UINT iColumn, 619 SHELLDETAILS *psd) PURE; 620 621 virtual HRESULT STDMETHODCALLTYPE MapColumnToSCID( 622 UINT iColumn, 623 SHCOLUMNID *pscid) PURE; 624 625 // IPersist 626 virtual HRESULT STDMETHODCALLTYPE GetClassID(CLSID *lpClassId) 627 { 628 if (!lpClassId) 629 return E_POINTER; 630 631 *lpClassId = CLSID_NtObjectFolder; 632 return S_OK; 633 } 634 635 // IPersistFolder 636 virtual HRESULT STDMETHODCALLTYPE Initialize(PCIDLIST_ABSOLUTE pidl) 637 { 638 m_shellPidl = ILClone(pidl); 639 640 StringCbCopyW(m_NtPath, sizeof(m_NtPath), L"\\"); 641 642 return S_OK; 643 } 644 645 // IPersistFolder2 646 virtual HRESULT STDMETHODCALLTYPE GetCurFolder(PIDLIST_ABSOLUTE * pidl) 647 { 648 if (pidl) 649 *pidl = ILClone(m_shellPidl); 650 if (!m_shellPidl) 651 return S_FALSE; 652 return S_OK; 653 } 654 655 // Internal 656 protected: 657 virtual HRESULT STDMETHODCALLTYPE CompareIDs( 658 LPARAM lParam, 659 const TItemId * first, 660 const TItemId * second) PURE; 661 662 virtual ULONG STDMETHODCALLTYPE ConvertAttributes( 663 const TItemId * entry, 664 PULONG inMask) PURE; 665 666 virtual BOOL STDMETHODCALLTYPE IsFolder(LPCITEMIDLIST pcidl) 667 { 668 const TItemId * info; 669 670 HRESULT hr = GetInfoFromPidl(pcidl, &info); 671 if (FAILED(hr)) 672 return hr; 673 674 return IsFolder(info); 675 } 676 677 virtual BOOL STDMETHODCALLTYPE IsFolder(const TItemId * info) PURE; 678 679 virtual BOOL STDMETHODCALLTYPE IsSymLink(LPCITEMIDLIST pcidl) 680 { 681 const TItemId * info; 682 683 HRESULT hr = GetInfoFromPidl(pcidl, &info); 684 if (FAILED(hr)) 685 return hr; 686 687 return IsSymLink(info); 688 } 689 690 virtual BOOL STDMETHODCALLTYPE IsSymLink(const TItemId * info) 691 { 692 return FALSE; 693 } 694 695 virtual HRESULT GetInfoFromPidl(LPCITEMIDLIST pcidl, const TItemId ** pentry) PURE; 696 697 public: 698 static HRESULT CALLBACK DefCtxMenuCallback(IShellFolder * /*psf*/, HWND /*hwnd*/, IDataObject * /*pdtobj*/, UINT uMsg, WPARAM /*wParam*/, LPARAM /*lParam*/) 699 { 700 switch (uMsg) 701 { 702 case DFM_MERGECONTEXTMENU: 703 return S_OK; 704 705 case DFM_INVOKECOMMAND: 706 case DFM_INVOKECOMMANDEX: 707 case DFM_GETDEFSTATICID: // Required for Windows 7 to pick a default 708 return S_FALSE; 709 } 710 return E_NOTIMPL; 711 } 712 713 DECLARE_NOT_AGGREGATABLE(TSelf) 714 DECLARE_PROTECT_FINAL_CONSTRUCT() 715 716 BEGIN_COM_MAP(TSelf) 717 COM_INTERFACE_ENTRY_IID(IID_IShellFolder, IShellFolder) 718 COM_INTERFACE_ENTRY_IID(IID_IShellFolder2, IShellFolder2) 719 COM_INTERFACE_ENTRY_IID(IID_IPersist, IPersist) 720 COM_INTERFACE_ENTRY_IID(IID_IPersistFolder, IPersistFolder) 721 COM_INTERFACE_ENTRY_IID(IID_IPersistFolder2, IPersistFolder2) 722 END_COM_MAP() 723 724 }; 725