1 /* 2 * PROJECT: ReactOS CabView Shell Extension 3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) 4 * PURPOSE: Shell folder implementation 5 * COPYRIGHT: Copyright 2024 Whindmar Saksit <whindsaks@proton.me> 6 */ 7 8 #include "cabview.h" 9 #include "util.h" 10 11 enum FOLDERCOLUMNS 12 { 13 COL_NAME, // PKEY_ItemNameDisplay 14 COL_SIZE, // PKEY_Size 15 COL_TYPE, // PKEY_ItemTypeText 16 COL_MDATE, // PKEY_DateModified 17 COL_PATH, // PKEY_?: Archive-relative path 18 COL_ATT, // PKEY_FileAttributes 19 COLCOUNT 20 }; 21 22 static const struct FOLDERCOLUMN 23 { 24 BYTE TextId; 25 BYTE LvcFmt; 26 BYTE LvcChars; 27 BYTE ColFlags; 28 const GUID *pkg; 29 BYTE pki; 30 } g_Columns[] = 31 { 32 { IDS_COL_NAME, LVCFMT_LEFT, 20, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, &FMTID_Storage, PID_STG_NAME }, 33 { IDS_COL_SIZE, LVCFMT_RIGHT, 16, SHCOLSTATE_TYPE_INT | SHCOLSTATE_ONBYDEFAULT, &FMTID_Storage, PID_STG_SIZE }, 34 { IDS_COL_TYPE, LVCFMT_LEFT, 20, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, &FMTID_Storage, PID_STG_STORAGETYPE }, 35 { IDS_COL_MDATE, LVCFMT_LEFT, 20, SHCOLSTATE_TYPE_DATE | SHCOLSTATE_ONBYDEFAULT, &FMTID_Storage, PID_STG_WRITETIME }, 36 { IDS_COL_PATH, LVCFMT_LEFT, 30, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, &CLSID_CabFolder, 0 }, 37 { IDS_COL_ATT, LVCFMT_RIGHT, 10, SHCOLSTATE_TYPE_STR, &FMTID_Storage, PID_STG_ATTRIBUTES }, 38 }; 39 40 #include <pshpack1.h> 41 struct CABITEM 42 { 43 WORD cb; 44 WORD Unknown; // Not sure what Windows uses this for, we always store 0 45 UINT Size; 46 WORD Date, Time; // DOS 47 WORD Attrib; 48 WORD NameOffset; 49 WCHAR Path[ANYSIZE_ARRAY]; 50 51 #if FLATFOLDER 52 inline bool IsFolder() const { return false; } 53 #else 54 inline BOOL IsFolder() const { return Attrib & FILE_ATTRIBUTE_DIRECTORY; } 55 #endif 56 enum { FSATTS = FILE_ATTRIBUTE_READONLY | FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM | 57 FILE_ATTRIBUTE_ARCHIVE | FILE_ATTRIBUTE_DIRECTORY }; 58 WORD GetFSAttributes() const { return Attrib & FSATTS; } 59 LPCWSTR GetName() const { return Path + NameOffset; } 60 61 template<class PIDL> static CABITEM* Validate(PIDL pidl) 62 { 63 CABITEM *p = (CABITEM*)pidl; 64 return p && p->cb > FIELD_OFFSET(CABITEM, Path[1]) && p->Unknown == 0 ? p : NULL; 65 } 66 }; 67 #include <poppack.h> 68 69 static CABITEM* CreateItem(LPCWSTR Path, UINT Attrib, UINT Size, UINT DateTime) 70 { 71 const SIZE_T len = lstrlenW(Path), cb = FIELD_OFFSET(CABITEM, Path[len + 1]); 72 if (cb > 0xffff) 73 return NULL; 74 CABITEM *p = (CABITEM*)SHAlloc(cb + sizeof(USHORT)); 75 if (p) 76 { 77 p->cb = (USHORT)cb; 78 p->Unknown = 0; 79 p->Size = Size; 80 p->Attrib = Attrib; 81 p->Date = HIWORD(DateTime); 82 p->Time = LOWORD(DateTime); 83 p->NameOffset = 0; 84 for (UINT i = 0;; ++i) 85 { 86 WCHAR c = Path[i]; 87 if (c == L':') // Don't allow absolute paths 88 c = L'_'; 89 if (c == L'/') // Normalize 90 c = L'\\'; 91 if (c == '\\') 92 p->NameOffset = i + 1; 93 p->Path[i] = c; 94 if (!c) 95 break; 96 } 97 ((SHITEMID*)((BYTE*)p + cb))->cb = 0; 98 } 99 return p; 100 } 101 102 static CABITEM* CreateItem(LPCSTR Path, UINT Attrib, UINT Size = 0, UINT DateTime = 0) 103 { 104 WCHAR buf[MAX_PATH * 2]; 105 UINT codepage = (Attrib & _A_NAME_IS_UTF) ? CP_UTF8 : CP_ACP; 106 if (MultiByteToWideChar(codepage, 0, Path, -1, buf, _countof(buf))) 107 return CreateItem(buf, Attrib, Size, DateTime); 108 return NULL; 109 } 110 111 static HRESULT CALLBACK ItemMenuCallback(IShellFolder *psf, HWND hwnd, IDataObject *pdtobj, 112 UINT uMsg, WPARAM wParam, LPARAM lParam) 113 { 114 enum { IDC_EXTRACT, IDC_EXTRACTALL }; 115 HRESULT hr = E_NOTIMPL; 116 const BOOL Background = !pdtobj; 117 118 switch (uMsg) 119 { 120 case DFM_MODIFYQCMFLAGS: 121 { 122 *((UINT*)lParam) = wParam | CMF_NOVERBS | (Background ? 0 : CMF_VERBSONLY); 123 return S_OK; 124 } 125 126 case DFM_MERGECONTEXTMENU: 127 { 128 QCMINFO &qcmi = *(QCMINFO*)lParam; 129 UINT pos = qcmi.indexMenu, id = 0; 130 if (Background || SUCCEEDED(hr = InsertMenuItem(qcmi, pos, id, IDC_EXTRACT, IDS_EXTRACT, MFS_DEFAULT))) 131 { 132 hr = InsertMenuItem(qcmi, pos, id, IDC_EXTRACTALL, IDS_EXTRACTALL); 133 if (SUCCEEDED(hr) && !Background) 134 { 135 --pos; 136 InsertMenuItem(qcmi, pos, id, 0, -1); // Separator 137 } 138 } 139 if (SUCCEEDED(hr)) 140 { 141 qcmi.idCmdFirst = id + 1; 142 hr = S_FALSE; // Don't add verbs 143 } 144 break; 145 } 146 147 case DFM_INVOKECOMMAND: 148 { 149 hr = S_FALSE; 150 CCabFolder *pCabFolder = static_cast<CCabFolder*>(psf); 151 switch (wParam) 152 { 153 case IDC_EXTRACT: 154 case IDC_EXTRACTALL: 155 hr = pCabFolder->ExtractFilesUI(hwnd, wParam == IDC_EXTRACT ? pdtobj : NULL); 156 break; 157 } 158 break; 159 } 160 } 161 return hr; 162 } 163 164 static HRESULT CALLBACK FolderBackgroundMenuCallback(IShellFolder *psf, HWND hwnd, 165 IDataObject *pdtobj, UINT uMsg, 166 WPARAM wParam, LPARAM lParam) 167 { 168 return ItemMenuCallback(psf, hwnd, NULL, uMsg, wParam, lParam); 169 } 170 171 int CEnumIDList::FindNamedItem(PCUITEMID_CHILD pidl) const 172 { 173 CABITEM *needle = (CABITEM*)pidl; 174 for (ULONG i = 0, c = GetCount(); i < c; ++i) 175 { 176 CABITEM *item = (CABITEM*)DPA_FastGetPtr(m_Items, i); 177 if (!lstrcmpiW(needle->Path, item->Path)) 178 return i; 179 } 180 return -1; 181 } 182 183 struct FILLCALLBACKDATA 184 { 185 CEnumIDList *pEIDL; 186 SHCONTF ContF; 187 }; 188 189 static HRESULT CALLBACK EnumFillCallback(EXTRACTCALLBACKMSG msg, const EXTRACTCALLBACKDATA &ecd, LPVOID cookie) 190 { 191 FILLCALLBACKDATA &data = *(FILLCALLBACKDATA*)cookie; 192 193 switch ((UINT)msg) 194 { 195 case ECM_FILE: 196 { 197 const FDINOTIFICATION &fdin = *ecd.pfdin; 198 HRESULT hr = S_FALSE; 199 SFGAOF attr = MapFSToSFAttributes(fdin.attribs & CABITEM::FSATTS); 200 if (IncludeInEnumIDList(data.ContF, attr)) 201 { 202 UINT datetime = MAKELONG(fdin.time, fdin.date); 203 CABITEM *item = CreateItem(fdin.psz1, fdin.attribs, fdin.cb, datetime); 204 if (!item) 205 return E_OUTOFMEMORY; 206 if (FAILED(hr = data.pEIDL->Append((LPCITEMIDLIST)item))) 207 SHFree(item); 208 } 209 return SUCCEEDED(hr) ? S_FALSE : hr; // Never extract 210 } 211 } 212 return E_NOTIMPL; 213 } 214 215 HRESULT CEnumIDList::Fill(LPCWSTR path, HWND hwnd, SHCONTF contf) 216 { 217 FILLCALLBACKDATA data = { this, contf }; 218 return ExtractCabinet(path, NULL, EnumFillCallback, &data); 219 } 220 221 HRESULT CEnumIDList::Fill(PCIDLIST_ABSOLUTE pidl, HWND hwnd, SHCONTF contf) 222 { 223 WCHAR path[MAX_PATH]; 224 if (SHGetPathFromIDListW(pidl, path)) 225 return Fill(path, hwnd, contf); 226 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); 227 } 228 229 IFACEMETHODIMP CCabFolder::GetDefaultColumn(DWORD dwRes, ULONG *pSort, ULONG *pDisplay) 230 { 231 if (pSort) 232 *pSort = COL_NAME; 233 if (pDisplay) 234 *pDisplay = COL_NAME; 235 return S_OK; 236 } 237 238 IFACEMETHODIMP CCabFolder::GetDefaultColumnState(UINT iColumn, SHCOLSTATEF *pcsFlags) 239 { 240 if (!pcsFlags || iColumn >= _countof(g_Columns)) 241 return E_INVALIDARG; 242 *pcsFlags = g_Columns[iColumn].ColFlags; 243 return S_OK; 244 } 245 246 IFACEMETHODIMP CCabFolder::GetDisplayNameOf(PCUITEMID_CHILD pidl, DWORD dwFlags, LPSTRRET pName) 247 { 248 CABITEM *item = CABITEM::Validate(pidl); 249 if (!item || !pName) 250 return E_INVALIDARG; 251 252 if (dwFlags & SHGDN_FORPARSING) 253 { 254 if (dwFlags & SHGDN_INFOLDER) 255 return StrTo(FLATFOLDER ? item->Path : item->GetName(), *pName); 256 257 WCHAR parent[MAX_PATH]; 258 if (!SHGetPathFromIDListW(m_CurDir, parent)) 259 return E_FAIL; 260 UINT cch = lstrlenW(parent) + 1 + lstrlenW(item->Path) + 1; 261 pName->uType = STRRET_WSTR; 262 pName->pOleStr = (LPWSTR)SHAlloc(cch * sizeof(WCHAR)); 263 if (!pName->pOleStr) 264 return E_OUTOFMEMORY; 265 lstrcpyW(pName->pOleStr, parent); 266 PathAppendW(pName->pOleStr, item->Path); 267 return S_OK; 268 } 269 270 SHFILEINFO fi; 271 DWORD attr = item->IsFolder() ? FILE_ATTRIBUTE_DIRECTORY : 0; 272 UINT flags = SHGFI_DISPLAYNAME | SHGFI_USEFILEATTRIBUTES; 273 if (SHGetFileInfo(item->GetName(), attr, &fi, sizeof(fi), flags)) 274 return StrTo(fi.szDisplayName, *pName); 275 return StrTo(item->GetName(), *pName); 276 } 277 278 HRESULT CCabFolder::GetItemDetails(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS *psd, VARIANT *pv) 279 { 280 HRESULT hr = E_FAIL; 281 STRRET *psr = &psd->str, srvar; 282 CABITEM *item = CABITEM::Validate(pidl); 283 if (!item) 284 return E_INVALIDARG; 285 286 switch (iColumn) 287 { 288 case COL_NAME: 289 { 290 hr = GetDisplayNameOf(pidl, SHGDN_NORMAL | SHGDN_INFOLDER, pv ? &srvar : psr); 291 return SUCCEEDED(hr) && pv ? StrRetToVariantBSTR(&srvar, *pv) : hr; 292 } 293 294 case COL_SIZE: 295 { 296 UINT data = item->Size; 297 if (pv) 298 { 299 V_VT(pv) = VT_UI4; 300 V_UI4(pv) = data; 301 } 302 else 303 { 304 psr->uType = STRRET_CSTR; 305 StrFormatByteSizeA(data, psr->cStr, _countof(psr->cStr)); 306 } 307 return S_OK; 308 } 309 310 case COL_TYPE: 311 { 312 SHFILEINFO fi; 313 LPCWSTR data = fi.szTypeName; 314 DWORD attr = item->GetFSAttributes(); 315 UINT flags = SHGFI_TYPENAME | SHGFI_USEFILEATTRIBUTES; 316 if (SHGetFileInfo(item->GetName(), attr, &fi, sizeof(fi), flags)) 317 return pv ? StrTo(data, *pv) : StrTo(data, *psr); 318 break; 319 } 320 321 case COL_MDATE: 322 { 323 if (pv) 324 { 325 if (DosDateTimeToVariantTime(item->Date, item->Time, &V_DATE(pv))) 326 { 327 V_VT(pv) = VT_DATE; 328 return S_OK; 329 } 330 } 331 else 332 { 333 FILETIME utc, loc; 334 if (DosDateTimeToFileTime(item->Date, item->Time, &utc) && FileTimeToLocalFileTime(&utc, &loc)) 335 { 336 psr->uType = STRRET_CSTR; 337 if (SHFormatDateTimeA(&loc, NULL, psr->cStr, _countof(psr->cStr))) 338 { 339 return S_OK; 340 } 341 } 342 } 343 break; 344 } 345 346 case COL_PATH: 347 { 348 UINT len = item->NameOffset ? item->NameOffset - 1 : 0; 349 return pv ? StrTo(item->Path, len, *pv) : StrTo(item->Path, len, *psr); 350 } 351 352 case COL_ATT: 353 { 354 UINT data = item->GetFSAttributes(); 355 if (pv) 356 { 357 V_VT(pv) = VT_UI4; 358 V_UI4(pv) = data; 359 } 360 else 361 { 362 UINT i = 0; 363 psr->uType = STRRET_CSTR; 364 if (data & FILE_ATTRIBUTE_READONLY) psr->cStr[i++] = 'R'; 365 if (data & FILE_ATTRIBUTE_HIDDEN) psr->cStr[i++] = 'H'; 366 if (data & FILE_ATTRIBUTE_SYSTEM) psr->cStr[i++] = 'S'; 367 if (data & FILE_ATTRIBUTE_ARCHIVE) psr->cStr[i++] = 'A'; 368 psr->cStr[i++] = '\0'; 369 } 370 return S_OK; 371 } 372 } 373 return hr; 374 } 375 376 IFACEMETHODIMP CCabFolder::GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID *pscid, VARIANT *pv) 377 { 378 if (!pscid || !pv) 379 return E_INVALIDARG; 380 381 CABITEM *item; 382 int col = MapSCIDToColumn(*pscid); 383 if (col >= 0) 384 { 385 return GetItemDetails(pidl, col, NULL, pv); 386 } 387 else if ((item = CABITEM::Validate(pidl)) == NULL) 388 { 389 return E_INVALIDARG; 390 } 391 else if (IsEqual(*pscid, FMTID_ShellDetails, PID_FINDDATA)) 392 { 393 WIN32_FIND_DATA wfd; 394 ZeroMemory(&wfd, sizeof(wfd)); 395 wfd.dwFileAttributes = item->GetFSAttributes(); 396 wfd.nFileSizeLow = item->Size; 397 DosDateTimeToFileTime(item->Date, item->Time, &wfd.ftLastWriteTime); 398 lstrcpyn(wfd.cFileName, item->GetName(), MAX_PATH); 399 return InitVariantFromBuffer(&wfd, sizeof(wfd), pv); 400 } 401 return E_FAIL; 402 } 403 404 IFACEMETHODIMP CCabFolder::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS *psd) 405 { 406 if (!psd || iColumn >= _countof(g_Columns)) 407 { 408 return E_INVALIDARG; 409 } 410 else if (!pidl) 411 { 412 psd->fmt = g_Columns[iColumn].LvcFmt; 413 psd->cxChar = g_Columns[iColumn].LvcChars; 414 WCHAR buf[MAX_PATH]; 415 if (LoadStringW(_AtlBaseModule.GetResourceInstance(), g_Columns[iColumn].TextId, buf, _countof(buf))) 416 return StrTo(buf, psd->str); 417 return E_FAIL; 418 } 419 return GetItemDetails(pidl, iColumn, psd, NULL); 420 } 421 422 int CCabFolder::MapSCIDToColumn(const SHCOLUMNID &scid) 423 { 424 for (UINT i = 0; i < _countof(g_Columns); ++i) 425 { 426 if (g_Columns[i].pkg && IsEqual(scid, *g_Columns[i].pkg, g_Columns[i].pki)) 427 return i; 428 } 429 return -1; 430 } 431 432 IFACEMETHODIMP CCabFolder::MapColumnToSCID(UINT column, SHCOLUMNID *pscid) 433 { 434 if (column < _countof(g_Columns) && g_Columns[column].pkg) 435 { 436 pscid->fmtid = *g_Columns[column].pkg; 437 pscid->pid = g_Columns[column].pki; 438 return S_OK; 439 } 440 return E_FAIL; 441 } 442 443 IFACEMETHODIMP CCabFolder::EnumObjects(HWND hwndOwner, DWORD dwFlags, LPENUMIDLIST *ppEnumIDList) 444 { 445 CEnumIDList *p = CEnumIDList::CreateInstance(); 446 *ppEnumIDList = static_cast<LPENUMIDLIST>(p); 447 return p ? p->Fill(m_CurDir, hwndOwner, dwFlags) : E_OUTOFMEMORY; 448 } 449 450 IFACEMETHODIMP CCabFolder::BindToObject(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut) 451 { 452 UNIMPLEMENTED; 453 return E_NOTIMPL; 454 } 455 456 HRESULT CCabFolder::CompareID(LPARAM lParam, PCUITEMID_CHILD pidl1, PCUITEMID_CHILD pidl2) 457 { 458 CABITEM *p1 = (CABITEM*)pidl1, *p2 = (CABITEM*)pidl2; 459 HRESULT hr = S_OK; 460 int ret = 0; 461 462 if (lParam & (SHCIDS_ALLFIELDS | SHCIDS_CANONICALONLY)) 463 { 464 ret = lstrcmpiW(p1->Path, p2->Path); 465 if (ret && (lParam & SHCIDS_ALLFIELDS)) 466 { 467 for (UINT i = 0; ret && SUCCEEDED(hr) && i < COLCOUNT; ++i) 468 { 469 hr = (i == COL_NAME) ? 0 : CompareID(i, pidl1, pidl2); 470 ret = (short)HRESULT_CODE(hr); 471 } 472 } 473 } 474 else 475 { 476 UINT col = lParam & SHCIDS_COLUMNMASK; 477 switch (col) 478 { 479 case COL_NAME: 480 ret = StrCmpLogicalW(p1->GetName(), p2->GetName()); 481 break; 482 483 case COL_SIZE: 484 ret = p1->Size - p2->Size; 485 break; 486 487 case COL_MDATE: 488 ret = MAKELONG(p1->Time, p1->Date) - MAKELONG(p2->Time, p2->Date); 489 break; 490 491 default: 492 { 493 if (col < COLCOUNT) 494 { 495 PWSTR str1, str2; 496 if (SUCCEEDED(hr = ::GetDetailsOf(*this, pidl1, col, str1))) 497 { 498 if (SUCCEEDED(hr = ::GetDetailsOf(*this, pidl2, col, str2))) 499 { 500 ret = StrCmpLogicalW(str1, str2); 501 SHFree(str2); 502 } 503 SHFree(str1); 504 } 505 } 506 else 507 { 508 hr = E_INVALIDARG; 509 } 510 } 511 } 512 } 513 return SUCCEEDED(hr) ? MAKE_COMPARE_HRESULT(ret) : hr; 514 } 515 516 IFACEMETHODIMP CCabFolder::CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2) 517 { 518 C_ASSERT(FLATFOLDER); 519 if (!pidl1 || !ILIsSingle(pidl1) || !pidl2 || !ILIsSingle(pidl2)) 520 return E_UNEXPECTED; 521 522 return CompareID(lParam, pidl1, pidl2); 523 } 524 525 IFACEMETHODIMP CCabFolder::CreateViewObject(HWND hwndOwner, REFIID riid, LPVOID *ppv) 526 { 527 if (riid == IID_IShellView) 528 { 529 SFV_CREATE sfvc = { sizeof(SFV_CREATE), static_cast<IShellFolder*>(this) }; 530 return SHCreateShellFolderView(&sfvc, (IShellView**)ppv); 531 } 532 if (riid == IID_IContextMenu) 533 { 534 LPFNDFMCALLBACK func = FolderBackgroundMenuCallback; 535 IContextMenu **ppCM = (IContextMenu**)ppv; 536 return CDefFolderMenu_Create2(m_CurDir, hwndOwner, 0, NULL, this, func, 0, NULL, ppCM); 537 } 538 return E_NOINTERFACE; 539 } 540 541 IFACEMETHODIMP CCabFolder::GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, SFGAOF *rgfInOut) 542 { 543 if (!cidl) 544 { 545 const SFGAOF ThisFolder = (SFGAO_FOLDER | SFGAO_BROWSABLE | SFGAO_CANLINK); 546 *rgfInOut = *rgfInOut & ThisFolder; 547 return S_OK; 548 } 549 else if (!apidl) 550 { 551 return E_INVALIDARG; 552 } 553 HRESULT hr = S_OK; 554 const SFGAOF filemask = SFGAO_READONLY | SFGAO_HIDDEN | SFGAO_SYSTEM | SFGAO_ISSLOW; 555 SFGAOF remain = *rgfInOut & filemask, validate = *rgfInOut & SFGAO_VALIDATE; 556 CComPtr<CEnumIDList> list; 557 for (UINT i = 0; i < cidl && (remain || validate); ++i) 558 { 559 CABITEM *item = CABITEM::Validate(apidl[i]); 560 if (!item) 561 { 562 hr = E_INVALIDARG; 563 break; 564 } 565 else if (validate) 566 { 567 if (!list && FAILED_UNEXPECTEDLY(hr = CreateEnum(&list))) 568 return hr; 569 if (list->FindNamedItem((PCUITEMID_CHILD)item) == -1) 570 return HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); 571 } 572 SFGAOF att = MapFSToSFAttributes(item->GetFSAttributes()) | SFGAO_ISSLOW; 573 remain &= att & ~(FLATFOLDER ? SFGAO_FOLDER : 0); 574 } 575 *rgfInOut = remain; 576 return hr; 577 } 578 579 IFACEMETHODIMP CCabFolder::GetUIObjectOf(HWND hwndOwner, UINT cidl, PCUITEMID_CHILD_ARRAY apidl, REFIID riid, UINT *prgfInOut, LPVOID *ppvOut) 580 { 581 HRESULT hr = E_NOINTERFACE; 582 if (riid == IID_IExtractIconA || riid == IID_IExtractIconW) 583 { 584 if (cidl != 1) 585 return E_INVALIDARG; 586 CABITEM *item = CABITEM::Validate(apidl[0]); 587 if (!item) 588 return E_INVALIDARG; 589 590 DWORD attr = item->GetFSAttributes(); 591 return SHCreateFileExtractIconW(item->GetName(), attr, riid, ppvOut); 592 } 593 else if (riid == IID_IContextMenu && cidl) 594 { 595 LPFNDFMCALLBACK func = ItemMenuCallback; 596 IContextMenu **ppCM = (IContextMenu**)ppvOut; 597 return CDefFolderMenu_Create2(NULL, hwndOwner, cidl, apidl, this, func, 0, NULL, ppCM); 598 } 599 else if (riid == IID_IDataObject && cidl) 600 { 601 // Note: This IDataObject is only compatible with IContextMenu, it cannot handle drag&drop of virtual items! 602 return CIDLData_CreateFromIDArray(m_CurDir, cidl, apidl, (IDataObject**)ppvOut); 603 } 604 return hr; 605 } 606 607 IFACEMETHODIMP CCabFolder::MessageSFVCB(UINT uMsg, WPARAM wParam, LPARAM lParam) 608 { 609 switch (uMsg) 610 { 611 case SFVM_WINDOWCREATED: 612 m_ShellViewWindow = (HWND)wParam; 613 return S_OK; 614 case SFVM_WINDOWCLOSING: 615 m_ShellViewWindow = NULL; 616 return S_OK; 617 } 618 return E_NOTIMPL; 619 } 620 621 IFACEMETHODIMP CCabFolder::GetIconOf(PCUITEMID_CHILD pidl, UINT flags, int *pIconIndex) 622 { 623 if (CABITEM *item = CABITEM::Validate(pidl)) 624 { 625 int index = MapPIDLToSystemImageListIndex(this, pidl, flags); 626 if (index == -1 && item->IsFolder()) 627 index = (flags & GIL_OPENICON) ? SIID_FOLDEROPEN : SIID_FOLDER; 628 if (index != -1) 629 { 630 *pIconIndex = index; 631 return S_OK; 632 } 633 } 634 return S_FALSE; 635 } 636 637 static HRESULT GetFsPathFromIDList(PCIDLIST_ABSOLUTE pidl, PWSTR pszPath) 638 { 639 BOOL ret = SHGetPathFromIDListW(pidl, pszPath); 640 if (!ret && ILIsEmpty(pidl)) 641 ret = SHGetSpecialFolderPathW(NULL, pszPath, CSIDL_DESKTOPDIRECTORY, TRUE); 642 return ret ? S_OK : HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND); 643 } 644 645 static int CALLBACK FolderBrowseCallback(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData) 646 { 647 WCHAR buf[MAX_PATH]; 648 switch (uMsg) 649 { 650 case BFFM_INITIALIZED: 651 { 652 if (LoadStringW(_AtlBaseModule.GetResourceInstance(), IDS_EXTRACT, buf, _countof(buf))) 653 { 654 // Remove leading and trailing dots 655 WCHAR *s = buf, *e = s + lstrlenW(s); 656 while (*s == '.') ++s; 657 while (e > s && e[-1] == '.') *--e = UNICODE_NULL; 658 SendMessageW(hwnd, WM_SETTEXT, 0, (LPARAM)s); 659 SendMessageW(GetDlgItem(hwnd, IDOK), WM_SETTEXT, 0, (LPARAM)s); 660 } 661 if (lpData) 662 { 663 SendMessageW(hwnd, BFFM_SETEXPANDED, FALSE, lpData); 664 SendMessageW(hwnd, BFFM_SETSELECTION, FALSE, lpData); 665 } 666 break; 667 } 668 669 case BFFM_SELCHANGED: 670 { 671 SFGAOF wanted = SFGAO_FILESYSTEM | SFGAO_FOLDER, query = wanted | SFGAO_STREAM; 672 PCIDLIST_ABSOLUTE pidl = (PCIDLIST_ABSOLUTE)lParam; 673 BOOL enable = ILIsEmpty(pidl); // Allow the desktop 674 PCUITEMID_CHILD child; 675 IShellFolder *pSF; 676 if (SUCCEEDED(SHBindToParent(pidl, IID_PPV_ARG(IShellFolder, &pSF), &child))) 677 { 678 SFGAOF attrib = query; 679 if (SUCCEEDED(pSF->GetAttributesOf(1, &child, &attrib))) 680 enable = (attrib & query) == wanted; 681 pSF->Release(); 682 } 683 if (enable) 684 { 685 // We don't trust .zip folders, check the file-system to make sure 686 UINT attrib = SUCCEEDED(GetFsPathFromIDList(pidl, buf)) ? GetFileAttributesW(buf) : 0; 687 enable = (attrib & FILE_ATTRIBUTE_DIRECTORY) && attrib != INVALID_FILE_ATTRIBUTES; 688 } 689 PostMessageW(hwnd, BFFM_ENABLEOK, 0, enable); 690 break; 691 } 692 } 693 return 0; 694 } 695 696 struct EXTRACTFILESDATA 697 { 698 CCabFolder *pLifetimeCF; 699 HWND hWndOwner; 700 CIDA *pCIDA; 701 STGMEDIUM cidamedium; 702 IDataObject *pDO; 703 IStream *pMarshalDO; 704 IProgressDialog *pPD; 705 UINT cabfiles, completed; 706 WCHAR path[MAX_PATH], cab[MAX_PATH]; 707 }; 708 709 static HWND GetUiOwner(const EXTRACTFILESDATA &data) 710 { 711 HWND hWnd; 712 if (SUCCEEDED(IUnknown_GetWindow(data.pPD, &hWnd)) && IsWindowVisible(hWnd)) 713 return hWnd; 714 return IsWindowVisible(data.hWndOwner) ? data.hWndOwner : NULL; 715 } 716 717 static HRESULT CALLBACK ExtractFilesCallback(EXTRACTCALLBACKMSG msg, const EXTRACTCALLBACKDATA &ecd, LPVOID cookie) 718 { 719 EXTRACTFILESDATA &data = *(EXTRACTFILESDATA*)cookie; 720 switch ((UINT)msg) 721 { 722 case ECM_BEGIN: 723 { 724 data.cabfiles = (UINT)(SIZE_T)ecd.pfdin->hf; 725 return S_OK; 726 } 727 728 case ECM_FILE: 729 { 730 if (data.pPD && data.pPD->HasUserCancelled()) 731 return HRESULT_FROM_WIN32(ERROR_CANCELLED); 732 HRESULT hr = data.pCIDA ? S_FALSE : S_OK; // Filtering or all items? 733 if (hr != S_OK) 734 { 735 CABITEM *needle = CreateItem(ecd.pfdin->psz1, ecd.pfdin->attribs); 736 if (!needle) 737 return E_OUTOFMEMORY; 738 for (UINT i = 0; i < data.pCIDA->cidl && hr == S_FALSE; ++i) 739 { 740 C_ASSERT(FLATFOLDER); 741 LPCITEMIDLIST pidlChild = ILFindLastID(HIDA_GetPIDLItem(data.pCIDA, i)); 742 CABITEM *haystack = CABITEM::Validate(pidlChild); 743 if (!haystack && FAILED_UNEXPECTEDLY(hr = E_FAIL)) 744 break; 745 if (!lstrcmpiW(needle->Path, haystack->Path)) 746 { 747 if (data.pPD) 748 data.pPD->SetLine(1, needle->Path, TRUE, NULL); 749 hr = S_OK; // Found it in the list of files to extract 750 } 751 } 752 SHFree(needle); 753 } 754 if (data.pPD) 755 data.pPD->SetProgress(data.completed++, data.cabfiles); 756 return hr; 757 } 758 759 case ECM_PREPAREPATH: 760 { 761 UINT flags = SHPPFW_DIRCREATE | SHPPFW_IGNOREFILENAME; 762 return SHPathPrepareForWriteW(GetUiOwner(data), NULL, ecd.Path, flags); 763 } 764 765 case ECM_ERROR: 766 { 767 return ErrorBox(GetUiOwner(data), ecd.hr); 768 } 769 } 770 return E_NOTIMPL; 771 } 772 773 static void Free(EXTRACTFILESDATA &data) 774 { 775 if (data.pPD) 776 { 777 data.pPD->StopProgressDialog(); 778 data.pPD->Release(); 779 } 780 CDataObjectHIDA::DestroyCIDA(data.pCIDA, data.cidamedium); 781 IUnknown_Set((IUnknown**)&data.pDO, NULL); 782 IUnknown_Set((IUnknown**)&data.pMarshalDO, NULL); 783 IUnknown_Set((IUnknown**)&data.pLifetimeCF, NULL); 784 SHFree(&data); 785 } 786 787 static DWORD CALLBACK ExtractFilesThread(LPVOID pParam) 788 { 789 EXTRACTFILESDATA &data = *(EXTRACTFILESDATA*)pParam; 790 HRESULT hr = S_OK; 791 if (SUCCEEDED(SHCoCreateInstance(NULL, &CLSID_ProgressDialog, NULL, IID_PPV_ARG(IProgressDialog, &data.pPD)))) 792 { 793 // TODO: IActionProgress SPACTION_COPYING 794 if (SUCCEEDED(data.pPD->StartProgressDialog(data.hWndOwner, NULL, PROGDLG_NOTIME, NULL))) 795 { 796 data.pPD->SetTitle(data.cab); 797 data.pPD->SetLine(2, data.path, TRUE, NULL); 798 data.pPD->SetAnimation(GetModuleHandleW(L"SHELL32"), 161); 799 data.pPD->SetProgress(0, 0); 800 } 801 } 802 if (data.pMarshalDO) 803 { 804 hr = CoGetInterfaceAndReleaseStream(data.pMarshalDO, IID_PPV_ARG(IDataObject, &data.pDO)); 805 data.pMarshalDO = NULL; 806 if (SUCCEEDED(hr)) 807 hr = CDataObjectHIDA::CreateCIDA(data.pDO, &data.pCIDA, data.cidamedium); 808 } 809 if (SUCCEEDED(hr)) 810 { 811 ExtractCabinet(data.cab, data.path, ExtractFilesCallback, &data); 812 } 813 Free(data); 814 return 0; 815 } 816 817 HRESULT CCabFolder::ExtractFilesUI(HWND hWnd, IDataObject *pDO) 818 { 819 if (!IsWindowVisible(hWnd) && IsWindowVisible(m_ShellViewWindow)) 820 hWnd = m_ShellViewWindow; 821 822 EXTRACTFILESDATA *pData = (EXTRACTFILESDATA*)SHAlloc(sizeof(*pData)); 823 if (!pData) 824 return E_OUTOFMEMORY; 825 ZeroMemory(pData, sizeof(*pData)); 826 pData->hWndOwner = hWnd; 827 pData->pLifetimeCF = this; 828 pData->pLifetimeCF->AddRef(); 829 830 HRESULT hr = GetFsPathFromIDList(m_CurDir, pData->cab); 831 if (SUCCEEDED(hr) && pDO) 832 { 833 hr = CoMarshalInterThreadInterfaceInStream(IID_IDataObject, pDO, &pData->pMarshalDO); 834 } 835 if (SUCCEEDED(hr)) 836 { 837 hr = HRESULT_FROM_WIN32(ERROR_CANCELLED); 838 LPITEMIDLIST pidlInitial = ILClone(m_CurDir); 839 ILRemoveLastID(pidlInitial); // Remove the "name.cab" part (we can't extract into ourselves) 840 UINT bif = BIF_RETURNONLYFSDIRS | BIF_USENEWUI; 841 BROWSEINFO bi = { hWnd, NULL, NULL, pData->cab, bif, FolderBrowseCallback, (LPARAM)pidlInitial }; 842 if (PIDLIST_ABSOLUTE folder = SHBrowseForFolderW(&bi)) 843 { 844 hr = GetFsPathFromIDList(folder, pData->path); 845 ILFree(folder); 846 if (SUCCEEDED(hr)) 847 { 848 UINT ctf = CTF_COINIT | CTF_PROCESS_REF | CTF_THREAD_REF | CTF_FREELIBANDEXIT; 849 hr = SHCreateThread(ExtractFilesThread, pData, ctf, NULL) ? S_OK : E_OUTOFMEMORY; 850 } 851 } 852 ILFree(pidlInitial); 853 } 854 if (hr != S_OK) 855 Free(*pData); 856 return hr == HRESULT_FROM_WIN32(ERROR_CANCELLED) ? S_OK : hr; 857 } 858