1 /* 2 * Shell Folder stuff 3 * 4 * Copyright 1997 Marcus Meissner 5 * Copyright 1998, 1999, 2002 Juergen Schmied 6 * Copyright 2018 Katayama Hirofumi MZ 7 * 8 * IShellFolder2 and related interfaces 9 * 10 * This library is free software; you can redistribute it and/or 11 * modify it under the terms of the GNU Lesser General Public 12 * License as published by the Free Software Foundation; either 13 * version 2.1 of the License, or (at your option) any later version. 14 * 15 * This library is distributed in the hope that it will be useful, 16 * but WITHOUT ANY WARRANTY; without even the implied warranty of 17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 18 * Lesser General Public License for more details. 19 * 20 * You should have received a copy of the GNU Lesser General Public 21 * License along with this library; if not, write to the Free Software 22 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA 23 */ 24 25 #include "precomp.h" 26 27 WINE_DEFAULT_DEBUG_CHANNEL(shell); 28 29 HRESULT 30 Shell_NextElement( 31 _Inout_ LPWSTR *ppch, 32 _Out_ LPWSTR pszOut, 33 _In_ INT cchOut, 34 _In_ BOOL bValidate) 35 { 36 *pszOut = UNICODE_NULL; 37 38 if (!*ppch) 39 return S_FALSE; 40 41 HRESULT hr; 42 LPWSTR pchNext = wcschr(*ppch, L'\\'); 43 if (pchNext) 44 { 45 if (*ppch < pchNext) 46 { 47 /* Get an element */ 48 StringCchCopyNW(pszOut, cchOut, *ppch, pchNext - *ppch); 49 ++pchNext; 50 51 if (!*pchNext) 52 pchNext = NULL; /* No next */ 53 54 hr = S_OK; 55 } 56 else /* Double backslashes found? */ 57 { 58 pchNext = NULL; 59 hr = E_INVALIDARG; 60 } 61 } 62 else /* No more next */ 63 { 64 StringCchCopyW(pszOut, cchOut, *ppch); 65 hr = S_OK; 66 } 67 68 *ppch = pchNext; /* Go next */ 69 70 if (hr == S_OK && bValidate && !PathIsValidElement(pszOut)) 71 { 72 *pszOut = UNICODE_NULL; 73 hr = E_INVALIDARG; 74 } 75 76 return hr; 77 } 78 79 HRESULT SHELL32_ParseNextElement (IShellFolder2 * psf, HWND hwndOwner, LPBC pbc, 80 LPITEMIDLIST * pidlInOut, LPOLESTR szNext, DWORD * pEaten, DWORD * pdwAttributes) 81 { 82 HRESULT hr = E_INVALIDARG; 83 LPITEMIDLIST pidlIn = pidlInOut ? *pidlInOut : NULL; 84 LPITEMIDLIST pidlOut = NULL; 85 LPITEMIDLIST pidlTemp = NULL; 86 CComPtr<IShellFolder> psfChild; 87 88 TRACE ("(%p, %p, %p, %s)\n", psf, pbc, pidlIn, debugstr_w (szNext)); 89 90 /* get the shellfolder for the child pidl and let it analyse further */ 91 hr = psf->BindToObject(pidlIn, pbc, IID_PPV_ARG(IShellFolder, &psfChild)); 92 if (FAILED(hr)) 93 return hr; 94 95 hr = psfChild->ParseDisplayName(hwndOwner, pbc, szNext, pEaten, &pidlOut, pdwAttributes); 96 if (FAILED(hr)) 97 return hr; 98 99 pidlTemp = ILCombine (pidlIn, pidlOut); 100 if (!pidlTemp) 101 { 102 hr = E_OUTOFMEMORY; 103 if (pidlOut) 104 ILFree(pidlOut); 105 return hr; 106 } 107 108 if (pidlOut) 109 ILFree (pidlOut); 110 111 if (pidlIn) 112 ILFree (pidlIn); 113 114 *pidlInOut = pidlTemp; 115 116 TRACE ("-- pidl=%p ret=0x%08x\n", pidlInOut ? *pidlInOut : NULL, hr); 117 return S_OK; 118 } 119 120 /*********************************************************************** 121 * SHELL32_CoCreateInitSF 122 * 123 * Creates a shell folder and initializes it with a pidl and a root folder 124 * via IPersistFolder3 or IPersistFolder. 125 * 126 * NOTES 127 * pathRoot can be NULL for Folders being a drive. 128 * In this case the absolute path is built from pidlChild (eg. C:) 129 */ 130 HRESULT SHELL32_CoCreateInitSF (LPCITEMIDLIST pidlRoot, PERSIST_FOLDER_TARGET_INFO* ppfti, 131 LPCITEMIDLIST pidlChild, const GUID* clsid, REFIID riid, LPVOID *ppvOut) 132 { 133 HRESULT hr; 134 CComPtr<IShellFolder> pShellFolder; 135 136 hr = SHCoCreateInstance(NULL, clsid, NULL, IID_PPV_ARG(IShellFolder, &pShellFolder)); 137 if (FAILED(hr)) 138 return hr; 139 140 LPITEMIDLIST pidlAbsolute = ILCombine (pidlRoot, pidlChild); 141 CComPtr<IPersistFolder> ppf; 142 CComPtr<IPersistFolder3> ppf3; 143 144 if (ppfti && SUCCEEDED(pShellFolder->QueryInterface(IID_PPV_ARG(IPersistFolder3, &ppf3)))) 145 { 146 ppf3->InitializeEx(NULL, pidlAbsolute, ppfti); 147 } 148 else if (SUCCEEDED(pShellFolder->QueryInterface(IID_PPV_ARG(IPersistFolder, &ppf)))) 149 { 150 ppf->Initialize(pidlAbsolute); 151 } 152 ILFree (pidlAbsolute); 153 154 return pShellFolder->QueryInterface(riid, ppvOut); 155 } 156 157 HRESULT SHELL32_CoCreateInitSF (LPCITEMIDLIST pidlRoot, const GUID* clsid, 158 int csidl, REFIID riid, LPVOID *ppvOut) 159 { 160 /* fill the PERSIST_FOLDER_TARGET_INFO */ 161 PERSIST_FOLDER_TARGET_INFO pfti = {0}; 162 pfti.dwAttributes = -1; 163 pfti.csidl = csidl; 164 165 return SHELL32_CoCreateInitSF(pidlRoot, &pfti, NULL, clsid, riid, ppvOut); 166 } 167 168 HRESULT SHELL32_BindToSF (LPCITEMIDLIST pidlRoot, PERSIST_FOLDER_TARGET_INFO* ppfti, 169 LPCITEMIDLIST pidl, const GUID* clsid, REFIID riid, LPVOID *ppvOut) 170 { 171 PITEMID_CHILD pidlChild = ILCloneFirst (pidl); 172 if (!pidlChild) 173 return E_FAIL; 174 175 CComPtr<IShellFolder> psf; 176 HRESULT hr = SHELL32_CoCreateInitSF(pidlRoot, 177 ppfti, 178 pidlChild, 179 clsid, 180 IID_PPV_ARG(IShellFolder, &psf)); 181 ILFree(pidlChild); 182 183 if (FAILED_UNEXPECTEDLY(hr)) 184 return hr; 185 186 if (_ILIsPidlSimple (pidl)) 187 return psf->QueryInterface(riid, ppvOut); 188 else 189 return psf->BindToObject(ILGetNext (pidl), NULL, riid, ppvOut); 190 } 191 192 /*********************************************************************** 193 * SHELL32_GetDisplayNameOfChild 194 * 195 * Retrieves the display name of a child object of a shellfolder. 196 * 197 * For a pidl eg. [subpidl1][subpidl2][subpidl3]: 198 * - it binds to the child shellfolder [subpidl1] 199 * - asks it for the displayname of [subpidl2][subpidl3] 200 * 201 * Is possible the pidl is a simple pidl. In this case it asks the 202 * subfolder for the displayname of an empty pidl. The subfolder 203 * returns the own displayname eg. "::{guid}". This is used for 204 * virtual folders with the registry key WantsFORPARSING set. 205 */ 206 HRESULT SHELL32_GetDisplayNameOfChild (IShellFolder2 * psf, 207 LPCITEMIDLIST pidl, DWORD dwFlags, LPSTRRET strRet) 208 { 209 LPITEMIDLIST pidlFirst = ILCloneFirst(pidl); 210 if (!pidlFirst) 211 return E_OUTOFMEMORY; 212 213 CComPtr<IShellFolder> psfChild; 214 HRESULT hr = psf->BindToObject(pidlFirst, NULL, IID_PPV_ARG(IShellFolder, &psfChild)); 215 if (SUCCEEDED (hr)) 216 { 217 hr = psfChild->GetDisplayNameOf(ILGetNext (pidl), dwFlags, strRet); 218 } 219 ILFree (pidlFirst); 220 221 return hr; 222 } 223 224 HRESULT SHELL32_CompareChildren(IShellFolder2* psf, LPARAM lParam, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2) 225 { 226 PUIDLIST_RELATIVE nextpidl1 = ILGetNext (pidl1); 227 PUIDLIST_RELATIVE nextpidl2 = ILGetNext (pidl2); 228 229 bool isEmpty1 = _ILIsDesktop(nextpidl1); 230 bool isEmpty2 = _ILIsDesktop(nextpidl2); 231 if (isEmpty1 || isEmpty2) 232 return MAKE_COMPARE_HRESULT(isEmpty2 - isEmpty1); 233 234 PITEMID_CHILD firstpidl = ILCloneFirst (pidl1); 235 if (!firstpidl) 236 return E_OUTOFMEMORY; 237 238 CComPtr<IShellFolder> psf2; 239 HRESULT hr = psf->BindToObject(firstpidl, 0, IID_PPV_ARG(IShellFolder, &psf2)); 240 ILFree(firstpidl); 241 if (FAILED(hr)) 242 return MAKE_COMPARE_HRESULT(0); 243 244 return psf2->CompareIDs(lParam, nextpidl1, nextpidl2); 245 } 246 247 HRESULT SHELL32_CompareDetails(IShellFolder2* isf, LPARAM lParam, LPCITEMIDLIST pidl1, LPCITEMIDLIST pidl2) 248 { 249 SHELLDETAILS sd; 250 WCHAR wszItem1[MAX_PATH], wszItem2[MAX_PATH]; 251 HRESULT hres; 252 UINT col = LOWORD(lParam); // Column index without SHCIDS_* flags 253 254 hres = isf->GetDetailsOf(pidl1, col, &sd); 255 if (FAILED(hres)) 256 return MAKE_COMPARE_HRESULT(1); 257 258 hres = StrRetToBufW(&sd.str, pidl1, wszItem1, MAX_PATH); 259 if (FAILED(hres)) 260 return MAKE_COMPARE_HRESULT(1); 261 262 hres = isf->GetDetailsOf(pidl2, col, &sd); 263 if (FAILED(hres)) 264 return MAKE_COMPARE_HRESULT(1); 265 266 hres = StrRetToBufW(&sd.str, pidl2, wszItem2, MAX_PATH); 267 if (FAILED(hres)) 268 return MAKE_COMPARE_HRESULT(1); 269 270 int ret = wcsicmp(wszItem1, wszItem2); 271 if (ret == 0) 272 return SHELL32_CompareChildren(isf, lParam, pidl1, pidl2); 273 274 return MAKE_COMPARE_HRESULT(ret); 275 } 276 277 LSTATUS AddClassKeyToArray(const WCHAR* szClass, HKEY* array, UINT* cKeys) 278 { 279 if (*cKeys >= 16) 280 return ERROR_MORE_DATA; 281 282 HKEY hkey; 283 LSTATUS result = RegOpenKeyExW(HKEY_CLASSES_ROOT, szClass, 0, KEY_READ | KEY_QUERY_VALUE, &hkey); 284 if (result == ERROR_SUCCESS) 285 { 286 array[*cKeys] = hkey; 287 *cKeys += 1; 288 } 289 return result; 290 } 291 292 void AddFSClassKeysToArray(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, HKEY* array, UINT* cKeys) 293 { 294 // This function opens the association array keys in canonical order for filesystem items. 295 // The order is documented: learn.microsoft.com/en-us/windows/win32/shell/fa-associationarray 296 297 ASSERT(cidl >= 1 && apidl); 298 PCUITEMID_CHILD pidl = apidl[0]; 299 if (_ILIsValue(pidl)) 300 { 301 WCHAR buf[MAX_PATH]; 302 PWSTR name; 303 FileStructW* pFileData = _ILGetFileStructW(pidl); 304 if (pFileData) 305 { 306 name = pFileData->wszName; 307 } 308 else 309 { 310 _ILSimpleGetTextW(pidl, buf, _countof(buf)); 311 name = buf; 312 } 313 LPCWSTR extension = PathFindExtension(name); 314 315 if (extension) 316 { 317 WCHAR wszClass[MAX_PATH], wszSFA[23 + _countof(wszClass)]; 318 DWORD dwSize = sizeof(wszClass); 319 if (RegGetValueW(HKEY_CLASSES_ROOT, extension, NULL, RRF_RT_REG_SZ, NULL, wszClass, &dwSize) != ERROR_SUCCESS || 320 !*wszClass || AddClassKeyToArray(wszClass, array, cKeys) != ERROR_SUCCESS) 321 { 322 // Only add the extension key if the ProgId is not valid 323 AddClassKeyToArray(extension, array, cKeys); 324 325 // "Open With" becomes the default when there are no verbs in the above keys 326 if (cidl == 1) 327 AddClassKeyToArray(L"Unknown", array, cKeys); 328 } 329 330 swprintf(wszSFA, L"SystemFileAssociations\\%s", extension); 331 AddClassKeyToArray(wszSFA, array, cKeys); 332 333 dwSize = sizeof(wszClass); 334 if (RegGetValueW(HKEY_CLASSES_ROOT, extension, L"PerceivedType ", RRF_RT_REG_SZ, NULL, wszClass, &dwSize) == ERROR_SUCCESS) 335 { 336 swprintf(wszSFA, L"SystemFileAssociations\\%s", wszClass); 337 AddClassKeyToArray(wszSFA, array, cKeys); 338 } 339 } 340 341 AddClassKeyToArray(L"*", array, cKeys); 342 AddClassKeyToArray(L"AllFilesystemObjects", array, cKeys); 343 } 344 else if (_ILIsFolder(pidl)) 345 { 346 // FIXME: Directory > Folder > AFO is the correct order and it's 347 // the order Windows reports in its undocumented association array 348 // but it is somehow not the order Windows adds the items to its menu! 349 // Until the correct algorithm in CDefaultContextMenu can be determined, 350 // we add the folder keys in "menu order". 351 AddClassKeyToArray(L"Folder", array, cKeys); 352 AddClassKeyToArray(L"AllFilesystemObjects", array, cKeys); 353 AddClassKeyToArray(L"Directory", array, cKeys); 354 } 355 else 356 { 357 ERR("Got non FS pidl\n"); 358 } 359 } 360 361 HRESULT SH_GetApidlFromDataObject(IDataObject *pDataObject, PIDLIST_ABSOLUTE* ppidlfolder, PUITEMID_CHILD **apidlItems, UINT *pcidl) 362 { 363 CDataObjectHIDA cida(pDataObject); 364 365 if (FAILED_UNEXPECTEDLY(cida.hr())) 366 return cida.hr(); 367 368 /* convert the data into pidl */ 369 LPITEMIDLIST pidl; 370 LPITEMIDLIST *apidl = _ILCopyCidaToaPidl(&pidl, cida); 371 if (!apidl) 372 { 373 return E_OUTOFMEMORY; 374 } 375 376 *ppidlfolder = pidl; 377 *apidlItems = apidl; 378 *pcidl = cida->cidl; 379 380 return S_OK; 381 } 382 383 /*********************************************************************** 384 * SHCreateLinks 385 * 386 * Undocumented. 387 */ 388 HRESULT WINAPI SHCreateLinks( HWND hWnd, LPCSTR lpszDir, IDataObject * lpDataObject, 389 UINT uFlags, LPITEMIDLIST *lppidlLinks) 390 { 391 FIXME("%p %s %p %08x %p\n", hWnd, lpszDir, lpDataObject, uFlags, lppidlLinks); 392 return E_NOTIMPL; 393 } 394 395 /*********************************************************************** 396 * SHOpenFolderAndSelectItems 397 * 398 * Unimplemented. 399 */ 400 EXTERN_C HRESULT 401 WINAPI 402 SHOpenFolderAndSelectItems(PCIDLIST_ABSOLUTE pidlFolder, 403 UINT cidl, 404 PCUITEMID_CHILD_ARRAY apidl, 405 DWORD dwFlags) 406 { 407 ERR("SHOpenFolderAndSelectItems() is hackplemented\n"); 408 CComHeapPtr<ITEMIDLIST> freeItem; 409 PCIDLIST_ABSOLUTE pidlItem; 410 if (cidl) 411 { 412 /* Firefox sends a full pidl here dispite the fact it is a PCUITEMID_CHILD_ARRAY -_- */ 413 if (!ILIsSingle(apidl[0])) 414 { 415 pidlItem = apidl[0]; 416 } 417 else 418 { 419 HRESULT hr = SHILCombine(pidlFolder, apidl[0], &pidlItem); 420 if (FAILED_UNEXPECTEDLY(hr)) 421 return hr; 422 freeItem.Attach(const_cast<PIDLIST_ABSOLUTE>(pidlItem)); 423 } 424 } 425 else 426 { 427 pidlItem = pidlFolder; 428 } 429 430 CComPtr<IShellFolder> psfDesktop; 431 432 HRESULT hr = SHGetDesktopFolder(&psfDesktop); 433 if (FAILED_UNEXPECTEDLY(hr)) 434 return hr; 435 436 STRRET strret; 437 hr = psfDesktop->GetDisplayNameOf(pidlItem, SHGDN_FORPARSING, &strret); 438 if (FAILED_UNEXPECTEDLY(hr)) 439 return hr; 440 441 WCHAR wszBuf[MAX_PATH]; 442 hr = StrRetToBufW(&strret, pidlItem, wszBuf, _countof(wszBuf)); 443 if (FAILED_UNEXPECTEDLY(hr)) 444 return hr; 445 446 WCHAR wszParams[MAX_PATH]; 447 wcscpy(wszParams, L"/select,"); 448 wcscat(wszParams, wszBuf); 449 450 SHELLEXECUTEINFOW sei; 451 memset(&sei, 0, sizeof sei); 452 sei.cbSize = sizeof sei; 453 sei.fMask = SEE_MASK_WAITFORINPUTIDLE; 454 sei.lpFile = L"explorer.exe"; 455 sei.lpParameters = wszParams; 456 457 if (ShellExecuteExW(&sei)) 458 return S_OK; 459 else 460 return E_FAIL; 461 } 462 463 static 464 DWORD WINAPI 465 _ShowPropertiesDialogThread(LPVOID lpParameter) 466 { 467 CComPtr<IDataObject> pDataObject; 468 pDataObject.Attach((IDataObject*)lpParameter); 469 470 CDataObjectHIDA cida(pDataObject); 471 472 if (FAILED_UNEXPECTEDLY(cida.hr())) 473 return cida.hr(); 474 475 if (cida->cidl > 1) 476 { 477 ERR("SHMultiFileProperties is not yet implemented\n"); 478 return E_FAIL; 479 } 480 481 CComHeapPtr<ITEMIDLIST> completePidl(ILCombine(HIDA_GetPIDLFolder(cida), HIDA_GetPIDLItem(cida, 0))); 482 CComHeapPtr<WCHAR> wszName; 483 if (FAILED_UNEXPECTEDLY(SHGetNameFromIDList(completePidl, SIGDN_PARENTRELATIVEPARSING, &wszName))) 484 return 0; 485 486 BOOL bSuccess = SH_ShowPropertiesDialog(wszName, pDataObject); 487 if (!bSuccess) 488 ERR("SH_ShowPropertiesDialog failed\n"); 489 490 return 0; 491 } 492 493 /* 494 * for internal use 495 */ 496 HRESULT 497 SHELL32_ShowPropertiesDialog(IDataObject *pdtobj) 498 { 499 if (!pdtobj) 500 return E_INVALIDARG; 501 502 pdtobj->AddRef(); 503 if (!SHCreateThread(_ShowPropertiesDialogThread, pdtobj, CTF_INSIST | CTF_COINIT, NULL)) 504 { 505 pdtobj->Release(); 506 return HResultFromWin32(GetLastError()); 507 } 508 else 509 { 510 return S_OK; 511 } 512 } 513 514 HRESULT 515 SHELL32_DefaultContextMenuCallBack(IShellFolder *psf, IDataObject *pdo, UINT msg) 516 { 517 switch (msg) 518 { 519 case DFM_MERGECONTEXTMENU: 520 return S_OK; // Yes, I want verbs 521 case DFM_INVOKECOMMAND: 522 return S_FALSE; // Do it for me please 523 case DFM_GETDEFSTATICID: 524 return S_FALSE; // Supposedly "required for Windows 7 to pick a default" 525 } 526 return E_NOTIMPL; 527 } 528