Reactos
at master 466 lines 14 kB view raw
1/* 2 * PROJECT: ReactOS Shell 3 * LICENSE: LGPL-2.0-or-later (https://spdx.org/licenses/LGPL-2.0-or-later) 4 * PURPOSE: Implement shell light-weight utility functions 5 * COPYRIGHT: Copyright 2023-2024 Katayama Hirofumi MZ <katayama.hirofumi.mz@gmail.com> 6 */ 7 8#define _ATL_NO_EXCEPTIONS 9 10/* 11 * HACK! These functions are conflicting with <shobjidl.h> inline functions... 12 */ 13#define IShellFolder_GetDisplayNameOf _disabled_IShellFolder_GetDisplayNameOf_ 14#define IShellFolder_ParseDisplayName _disabled_IShellFolder_ParseDisplayName_ 15#define IShellFolder_CompareIDs _disabled_IShellFolder_CompareIDs_ 16 17#include "precomp.h" 18#include <shellapi.h> 19#include <shlwapi.h> 20#include <shlobj_undoc.h> 21#include <shlguid_undoc.h> 22#include <atlstr.h> 23 24#include <shlwapi_undoc.h> 25#include <ishellfolder_helpers.h> 26 27#include <strsafe.h> 28 29#ifndef FAILED_UNEXPECTEDLY 30#define FAILED_UNEXPECTEDLY FAILED /* FIXME: Make shellutils.h usable without ATL */ 31#endif 32 33WINE_DEFAULT_DEBUG_CHANNEL(shell); 34 35EXTERN_C LSTATUS WINAPI RegGetValueW(HKEY, LPCWSTR, LPCWSTR, DWORD, LPDWORD, PVOID, LPDWORD); 36 37static inline WORD 38GetVersionMajorMinor() 39{ 40 DWORD version = GetVersion(); 41 return MAKEWORD(HIBYTE(version), LOBYTE(version)); 42} 43 44static HRESULT 45SHInvokeCommandOnContextMenuInternal( 46 _In_opt_ HWND hWnd, 47 _In_opt_ IUnknown* pUnk, 48 _In_ IContextMenu* pCM, 49 _In_ UINT fCMIC, 50 _In_ UINT fCMF, 51 _In_opt_ LPCSTR pszVerb, 52 _In_opt_ LPCWSTR pwszDir, 53 _In_ bool ForceQCM) 54{ 55 CMINVOKECOMMANDINFOEX info = { sizeof(info), fCMIC, hWnd, pszVerb }; 56 INT iDefItem = 0; 57 HMENU hMenu = NULL; 58 HCURSOR hOldCursor; 59 HRESULT hr = S_OK; 60 WCHAR wideverb[MAX_PATH]; 61 62 if (!pCM) 63 return E_INVALIDARG; 64 65 hOldCursor = SetCursor(LoadCursorW(NULL, (LPCWSTR)IDC_WAIT)); 66 info.nShow = SW_NORMAL; 67 if (pUnk) 68 IUnknown_SetSite(pCM, pUnk); 69 70 if (IS_INTRESOURCE(pszVerb)) 71 { 72 hMenu = CreatePopupMenu(); 73 if (hMenu) 74 { 75 hr = pCM->QueryContextMenu(hMenu, 0, 1, MAXSHORT, fCMF | CMF_DEFAULTONLY); 76 iDefItem = GetMenuDefaultItem(hMenu, 0, 0); 77 if (iDefItem != -1) 78 info.lpVerb = MAKEINTRESOURCEA(iDefItem - 1); 79 } 80 info.lpVerbW = MAKEINTRESOURCEW(info.lpVerb); 81 } 82 else 83 { 84 if (GetVersionMajorMinor() >= _WIN32_WINNT_WIN7) 85 { 86 info.fMask |= CMF_OPTIMIZEFORINVOKE; 87 } 88 if (pszVerb && SHAnsiToUnicode(pszVerb, wideverb, _countof(wideverb))) 89 { 90 info.fMask |= CMIC_MASK_UNICODE; 91 info.lpVerbW = wideverb; 92 } 93 if (ForceQCM) 94 { 95 hMenu = CreatePopupMenu(); 96 hr = pCM->QueryContextMenu(hMenu, 0, 1, MAXSHORT, fCMF); 97 } 98 } 99 100 SetCursor(hOldCursor); 101 102 if (!FAILED_UNEXPECTEDLY(hr) && (iDefItem != -1 || info.lpVerb)) 103 { 104 if (!hWnd) 105 info.fMask |= CMIC_MASK_FLAG_NO_UI; 106 107 CHAR dir[MAX_PATH]; 108 if (pwszDir) 109 { 110 info.fMask |= CMIC_MASK_UNICODE; 111 info.lpDirectoryW = pwszDir; 112 if (SHUnicodeToAnsi(pwszDir, dir, _countof(dir))) 113 info.lpDirectory = dir; 114 } 115 116 hr = pCM->InvokeCommand((LPCMINVOKECOMMANDINFO)&info); 117 if (FAILED_UNEXPECTEDLY(hr)) { /* Diagnostic message */ } 118 } 119 120 if (pUnk) 121 IUnknown_SetSite(pCM, NULL); 122 if (hMenu) 123 DestroyMenu(hMenu); 124 125 return hr; 126} 127 128/************************************************************************* 129 * SHInvokeCommandOnContextMenuEx [SHLWAPI.639] 130 */ 131EXTERN_C 132HRESULT WINAPI 133SHInvokeCommandOnContextMenuEx( 134 _In_opt_ HWND hWnd, 135 _In_opt_ IUnknown* pUnk, 136 _In_ IContextMenu* pCM, 137 _In_ UINT fCMIC, 138 _In_ UINT fCMF, 139 _In_opt_ LPCSTR pszVerb, 140 _In_opt_ LPCWSTR pwszDir) 141{ 142 return SHInvokeCommandOnContextMenuInternal(hWnd, pUnk, pCM, fCMIC, fCMF, pszVerb, pwszDir, true); 143} 144 145/************************************************************************* 146 * SHInvokeCommandOnContextMenu [SHLWAPI.540] 147 */ 148EXTERN_C 149HRESULT WINAPI 150SHInvokeCommandOnContextMenu( 151 _In_opt_ HWND hWnd, 152 _In_opt_ IUnknown* pUnk, 153 _In_ IContextMenu* pCM, 154 _In_ UINT fCMIC, 155 _In_opt_ LPCSTR pszVerb) 156{ 157 return SHInvokeCommandOnContextMenuEx(hWnd, pUnk, pCM, fCMIC, CMF_EXTENDEDVERBS, pszVerb, NULL); 158} 159 160/************************************************************************* 161 * SHInvokeCommandWithFlagsAndSite [SHLWAPI.571] 162 */ 163EXTERN_C 164HRESULT WINAPI 165SHInvokeCommandWithFlagsAndSite( 166 _In_opt_ HWND hWnd, 167 _In_opt_ IUnknown* pUnk, 168 _In_ IShellFolder* pShellFolder, 169 _In_ LPCITEMIDLIST pidl, 170 _In_ UINT fCMIC, 171 _In_opt_ LPCSTR pszVerb) 172{ 173 HRESULT hr = E_INVALIDARG; 174 if (pShellFolder) 175 { 176 IContextMenu *pCM; 177 hr = pShellFolder->GetUIObjectOf(hWnd, 1, &pidl, IID_IContextMenu, NULL, (void**)&pCM); 178 if (SUCCEEDED(hr)) 179 { 180 fCMIC |= CMIC_MASK_FLAG_LOG_USAGE; 181 hr = SHInvokeCommandOnContextMenuEx(hWnd, pUnk, pCM, fCMIC, 0, pszVerb, NULL); 182 pCM->Release(); 183 } 184 } 185 return hr; 186} 187 188 189/************************************************************************* 190 * IContextMenu_Invoke [SHLWAPI.207] 191 * 192 * Used by Win:SHELL32!CISFBand::_TrySimpleInvoke. 193 */ 194EXTERN_C 195BOOL WINAPI 196IContextMenu_Invoke( 197 _In_ IContextMenu *pContextMenu, 198 _In_ HWND hwnd, 199 _In_ LPCSTR lpVerb, 200 _In_ UINT uFlags) 201{ 202 TRACE("(%p, %p, %s, %u)\n", pContextMenu, hwnd, debugstr_a(lpVerb), uFlags); 203 HRESULT hr = SHInvokeCommandOnContextMenuInternal(hwnd, NULL, pContextMenu, 0, 204 uFlags, lpVerb, NULL, false); 205 return !FAILED_UNEXPECTEDLY(hr); 206} 207 208/************************************************************************* 209 * ShellExecuteCommand [INTERNAL] 210 */ 211static HRESULT 212ShellExecuteCommand(_In_opt_ HWND hWnd, _In_ PCWSTR Command, _In_opt_ UINT Flags) 213{ 214 WCHAR szCmd[MAX_PATH * 2]; 215 int len = PathProcessCommand(Command, szCmd, _countof(szCmd), PPCF_ADDARGUMENTS | PPCF_FORCEQUALIFY); 216 if (len <= 0) // Could not resolve the command, just use the input 217 { 218 HRESULT hr = StringCchCopyW(szCmd, _countof(szCmd), Command); 219 if (FAILED(hr)) 220 return hr; 221 } 222 PWSTR pszArgs = PathGetArgsW(szCmd); 223 PathRemoveArgsW(szCmd); 224 PathUnquoteSpacesW(szCmd); 225 226 SHELLEXECUTEINFOW sei = { sizeof(sei), Flags, hWnd, NULL, szCmd, pszArgs }; 227 sei.nShow = SW_SHOW; 228 UINT error = ShellExecuteExW(&sei) ? ERROR_SUCCESS : GetLastError(); 229 return HRESULT_FROM_WIN32(error); 230} 231 232/************************************************************************* 233 * RunRegCommand [SHLWAPI.469] 234 */ 235EXTERN_C HRESULT WINAPI 236RunRegCommand(_In_opt_ HWND hWnd, _In_ HKEY hKey, _In_opt_ PCWSTR pszSubKey) 237{ 238 WCHAR szCmd[MAX_PATH * 2]; 239 DWORD cb = sizeof(szCmd); 240 DWORD error = RegGetValueW(hKey, pszSubKey, NULL, RRF_RT_REG_SZ, NULL, szCmd, &cb); 241 if (error) 242 return HRESULT_FROM_WIN32(error); 243 return ShellExecuteCommand(hWnd, szCmd, SEE_MASK_FLAG_LOG_USAGE); 244} 245 246/************************************************************************* 247 * RunIndirectRegCommand [SHLWAPI.468] 248 */ 249EXTERN_C HRESULT WINAPI 250RunIndirectRegCommand(_In_opt_ HWND hWnd, _In_ HKEY hKey, _In_opt_ PCWSTR pszSubKey, _In_ PCWSTR pszVerb) 251{ 252 WCHAR szKey[MAX_PATH]; 253 HRESULT hr; 254 if (pszSubKey) 255 hr = StringCchPrintfW(szKey, _countof(szKey), L"%s\\shell\\%s\\command", pszSubKey, pszVerb); 256 else 257 hr = StringCchPrintfW(szKey, _countof(szKey), L"shell\\%s\\command", pszVerb); 258 return SUCCEEDED(hr) ? RunRegCommand(hWnd, hKey, szKey) : hr; 259} 260 261/************************************************************************* 262 * SHRunIndirectRegClientCommand [SHLWAPI.467] 263 */ 264EXTERN_C HRESULT WINAPI 265SHRunIndirectRegClientCommand(_In_opt_ HWND hWnd, _In_ PCWSTR pszClientType) 266{ 267 WCHAR szKey[MAX_PATH], szClient[MAX_PATH]; 268 HRESULT hr = StringCchPrintfW(szKey, _countof(szKey), L"Software\\Clients\\%s", pszClientType); 269 if (FAILED(hr)) 270 return hr; 271 272 // Find the default client 273 DWORD error, cb; 274 cb = sizeof(szClient); 275 error = RegGetValueW(HKEY_CURRENT_USER, szKey, NULL, RRF_RT_REG_SZ, NULL, szClient, &cb); 276 if (error) 277 { 278 cb = sizeof(szClient); 279 if (error != ERROR_MORE_DATA && error != ERROR_BUFFER_OVERFLOW) 280 error = RegGetValueW(HKEY_LOCAL_MACHINE, szKey, NULL, RRF_RT_REG_SZ, NULL, szClient, &cb); 281 if (error) 282 return HRESULT_FROM_WIN32(error); 283 } 284 285 hr = StringCchPrintfW(szKey, _countof(szKey), L"Software\\Clients\\%s\\%s", pszClientType, szClient); 286 if (SUCCEEDED(hr)) 287 hr = RunIndirectRegCommand(hWnd, HKEY_LOCAL_MACHINE, szKey, L"open"); 288 return hr; 289} 290 291/************************************************************************* 292 * PathFileExistsDefExtAndAttributesW [SHLWAPI.511] 293 * 294 * @param pszPath The path string. 295 * @param dwWhich The WHICH_... flags. 296 * @param pdwFileAttributes A pointer to the file attributes. Optional. 297 * @return TRUE if successful. 298 */ 299BOOL WINAPI 300PathFileExistsDefExtAndAttributesW( 301 _Inout_ LPWSTR pszPath, 302 _In_ DWORD dwWhich, 303 _Out_opt_ LPDWORD pdwFileAttributes) 304{ 305 TRACE("(%s, 0x%lX, %p)\n", debugstr_w(pszPath), dwWhich, pdwFileAttributes); 306 307 if (pdwFileAttributes) 308 *pdwFileAttributes = INVALID_FILE_ATTRIBUTES; 309 310 if (!pszPath) 311 return FALSE; 312 313 if (!dwWhich || (*PathFindExtensionW(pszPath) && (dwWhich & WHICH_OPTIONAL))) 314 return PathFileExistsAndAttributesW(pszPath, pdwFileAttributes); 315 316 if (!PathFileExistsDefExtW(pszPath, dwWhich)) 317 { 318 if (pdwFileAttributes) 319 *pdwFileAttributes = INVALID_FILE_ATTRIBUTES; 320 return FALSE; 321 } 322 323 if (pdwFileAttributes) 324 *pdwFileAttributes = GetFileAttributesW(pszPath); 325 326 return TRUE; 327} 328 329static inline BOOL 330SHLWAPI_IsBogusHRESULT(HRESULT hr) 331{ 332 return (hr == E_FAIL || hr == E_INVALIDARG || hr == E_NOTIMPL); 333} 334 335// Used for IShellFolder_GetDisplayNameOf 336struct RETRY_DATA 337{ 338 SHGDNF uRemove; 339 SHGDNF uAdd; 340 DWORD dwRetryFlags; 341}; 342static const RETRY_DATA g_RetryData[] = 343{ 344 { SHGDN_FOREDITING, SHGDN_NORMAL, SFGDNO_RETRYALWAYS }, 345 { SHGDN_FORADDRESSBAR, SHGDN_NORMAL, SFGDNO_RETRYALWAYS }, 346 { SHGDN_NORMAL, SHGDN_FORPARSING, SFGDNO_RETRYALWAYS }, 347 { SHGDN_FORPARSING, SHGDN_NORMAL, SFGDNO_RETRYWITHFORPARSING }, 348 { SHGDN_INFOLDER, SHGDN_NORMAL, SFGDNO_RETRYALWAYS }, 349}; 350 351/************************************************************************* 352 * IShellFolder_GetDisplayNameOf [SHLWAPI.316] 353 * 354 * @note Don't confuse with <shobjidl.h> inline function of the same name. 355 * If the original call fails with the given uFlags, this function will 356 * retry with other flags to attempt retrieving any meaningful description. 357 */ 358EXTERN_C HRESULT WINAPI 359IShellFolder_GetDisplayNameOf( 360 _In_ IShellFolder *psf, 361 _In_ LPCITEMIDLIST pidl, 362 _In_ SHGDNF uFlags, 363 _Out_ LPSTRRET lpName, 364 _In_ DWORD dwRetryFlags) // dwRetryFlags is an additional parameter 365{ 366 HRESULT hr; 367 368 TRACE("(%p)->(%p, 0x%lX, %p, 0x%lX)\n", psf, pidl, uFlags, lpName, dwRetryFlags); 369 370 hr = psf->GetDisplayNameOf(pidl, uFlags, lpName); 371 if (!SHLWAPI_IsBogusHRESULT(hr)) 372 return hr; 373 374 dwRetryFlags |= SFGDNO_RETRYALWAYS; 375 376 if ((uFlags & SHGDN_FORPARSING) == 0) 377 dwRetryFlags |= SFGDNO_RETRYWITHFORPARSING; 378 379 // Retry with other flags to get successful results 380 for (SIZE_T iEntry = 0; iEntry < _countof(g_RetryData); ++iEntry) 381 { 382 const RETRY_DATA *pData = &g_RetryData[iEntry]; 383 if (!(dwRetryFlags & pData->dwRetryFlags)) 384 continue; 385 386 SHGDNF uNewFlags = ((uFlags & ~pData->uRemove) | pData->uAdd); 387 if (uNewFlags == uFlags) 388 continue; 389 390 hr = psf->GetDisplayNameOf(pidl, uNewFlags, lpName); 391 if (!SHLWAPI_IsBogusHRESULT(hr)) 392 break; 393 394 uFlags = uNewFlags; // Update flags every time 395 } 396 397 return hr; 398} 399 400/************************************************************************* 401 * IShellFolder_ParseDisplayName [SHLWAPI.317] 402 * 403 * @note Don't confuse with <shobjidl.h> inline function of the same name. 404 * This function is safer than IShellFolder::ParseDisplayName. 405 */ 406EXTERN_C HRESULT WINAPI 407IShellFolder_ParseDisplayName( 408 _In_ IShellFolder *psf, 409 _In_opt_ HWND hwndOwner, 410 _In_opt_ LPBC pbcReserved, 411 _In_ LPOLESTR lpszDisplayName, 412 _Out_opt_ ULONG *pchEaten, 413 _Out_opt_ PIDLIST_RELATIVE *ppidl, 414 _Out_opt_ ULONG *pdwAttributes) 415{ 416 ULONG dummy1, dummy2; 417 418 TRACE("(%p)->(%p, %p, %s, %p, %p, %p)\n", psf, hwndOwner, pbcReserved, 419 debugstr_w(lpszDisplayName), pchEaten, ppidl, pdwAttributes); 420 421 if (!pdwAttributes) 422 { 423 dummy1 = 0; 424 pdwAttributes = &dummy1; 425 } 426 427 if (!pchEaten) 428 { 429 dummy2 = 0; 430 pchEaten = &dummy2; 431 } 432 433 if (ppidl) 434 *ppidl = NULL; 435 436 return psf->ParseDisplayName(hwndOwner, pbcReserved, lpszDisplayName, pchEaten, 437 ppidl, pdwAttributes); 438} 439 440/************************************************************************* 441 * IShellFolder_CompareIDs [SHLWAPI.551] 442 * 443 * @note Don't confuse with <shobjidl.h> inline function of the same name. 444 * This function tries IShellFolder2 if possible. 445 */ 446EXTERN_C HRESULT WINAPI 447IShellFolder_CompareIDs( 448 _In_ IShellFolder *psf, 449 _In_ LPARAM lParam, 450 _In_ PCUIDLIST_RELATIVE pidl1, 451 _In_ PCUIDLIST_RELATIVE pidl2) 452{ 453 TRACE("(%p, %p, %p, %p)\n", psf, lParam, pidl1, pidl2); 454 455 if (lParam & ~(SIZE_T)SHCIDS_COLUMNMASK) 456 { 457 /* Try as IShellFolder2 if possible */ 458 HRESULT hr = psf->QueryInterface(IID_IShellFolder2, (void **)&psf); 459 if (FAILED(hr)) 460 lParam &= SHCIDS_COLUMNMASK; 461 else 462 psf->Release(); 463 } 464 465 return psf->CompareIDs(lParam, pidl1, pidl2); 466}