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 /* 25 * HACK! 26 */ 27 #undef IShellFolder_GetDisplayNameOf 28 #undef IShellFolder_ParseDisplayName 29 #undef IShellFolder_CompareIDs 30 31 #define SHLWAPI_ISHELLFOLDER_HELPERS /* HACK! */ 32 #include <shlwapi_undoc.h> 33 34 #include <strsafe.h> 35 36 #ifndef FAILED_UNEXPECTEDLY 37 #define FAILED_UNEXPECTEDLY FAILED /* FIXME: Make shellutils.h usable without ATL */ 38 #endif 39 40 WINE_DEFAULT_DEBUG_CHANNEL(shell); 41 42 static inline WORD 43 GetVersionMajorMinor() 44 { 45 DWORD version = GetVersion(); 46 return MAKEWORD(HIBYTE(version), LOBYTE(version)); 47 } 48 49 static HRESULT 50 SHInvokeCommandOnContextMenuInternal( 51 _In_opt_ HWND hWnd, 52 _In_opt_ IUnknown* pUnk, 53 _In_ IContextMenu* pCM, 54 _In_ UINT fCMIC, 55 _In_ UINT fCMF, 56 _In_opt_ LPCSTR pszVerb, 57 _In_opt_ LPCWSTR pwszDir, 58 _In_ bool ForceQCM) 59 { 60 CMINVOKECOMMANDINFOEX info = { sizeof(info), fCMIC, hWnd, pszVerb }; 61 INT iDefItem = 0; 62 HMENU hMenu = NULL; 63 HCURSOR hOldCursor; 64 HRESULT hr = S_OK; 65 WCHAR wideverb[MAX_PATH]; 66 67 if (!pCM) 68 return E_INVALIDARG; 69 70 hOldCursor = SetCursor(LoadCursorW(NULL, (LPCWSTR)IDC_WAIT)); 71 info.nShow = SW_NORMAL; 72 if (pUnk) 73 IUnknown_SetSite(pCM, pUnk); 74 75 if (IS_INTRESOURCE(pszVerb)) 76 { 77 hMenu = CreatePopupMenu(); 78 if (hMenu) 79 { 80 hr = pCM->QueryContextMenu(hMenu, 0, 1, MAXSHORT, fCMF | CMF_DEFAULTONLY); 81 iDefItem = GetMenuDefaultItem(hMenu, 0, 0); 82 if (iDefItem != -1) 83 info.lpVerb = MAKEINTRESOURCEA(iDefItem - 1); 84 } 85 info.lpVerbW = MAKEINTRESOURCEW(info.lpVerb); 86 } 87 else 88 { 89 if (GetVersionMajorMinor() >= _WIN32_WINNT_WIN7) 90 { 91 info.fMask |= CMF_OPTIMIZEFORINVOKE; 92 } 93 if (pszVerb && SHAnsiToUnicode(pszVerb, wideverb, _countof(wideverb))) 94 { 95 info.fMask |= CMIC_MASK_UNICODE; 96 info.lpVerbW = wideverb; 97 } 98 if (ForceQCM) 99 { 100 hMenu = CreatePopupMenu(); 101 hr = pCM->QueryContextMenu(hMenu, 0, 1, MAXSHORT, fCMF); 102 } 103 } 104 105 SetCursor(hOldCursor); 106 107 if (!FAILED_UNEXPECTEDLY(hr) && (iDefItem != -1 || info.lpVerb)) 108 { 109 if (!hWnd) 110 info.fMask |= CMIC_MASK_FLAG_NO_UI; 111 112 CHAR dir[MAX_PATH]; 113 if (pwszDir) 114 { 115 info.fMask |= CMIC_MASK_UNICODE; 116 info.lpDirectoryW = pwszDir; 117 if (SHUnicodeToAnsi(pwszDir, dir, _countof(dir))) 118 info.lpDirectory = dir; 119 } 120 121 hr = pCM->InvokeCommand((LPCMINVOKECOMMANDINFO)&info); 122 if (FAILED_UNEXPECTEDLY(hr)) { /* Diagnostic message */ } 123 } 124 125 if (pUnk) 126 IUnknown_SetSite(pCM, NULL); 127 if (hMenu) 128 DestroyMenu(hMenu); 129 130 return hr; 131 } 132 133 /************************************************************************* 134 * SHInvokeCommandOnContextMenuEx [SHLWAPI.639] 135 */ 136 EXTERN_C 137 HRESULT WINAPI 138 SHInvokeCommandOnContextMenuEx( 139 _In_opt_ HWND hWnd, 140 _In_opt_ IUnknown* pUnk, 141 _In_ IContextMenu* pCM, 142 _In_ UINT fCMIC, 143 _In_ UINT fCMF, 144 _In_opt_ LPCSTR pszVerb, 145 _In_opt_ LPCWSTR pwszDir) 146 { 147 return SHInvokeCommandOnContextMenuInternal(hWnd, pUnk, pCM, fCMIC, fCMF, pszVerb, pwszDir, true); 148 } 149 150 /************************************************************************* 151 * SHInvokeCommandOnContextMenu [SHLWAPI.540] 152 */ 153 EXTERN_C 154 HRESULT WINAPI 155 SHInvokeCommandOnContextMenu( 156 _In_opt_ HWND hWnd, 157 _In_opt_ IUnknown* pUnk, 158 _In_ IContextMenu* pCM, 159 _In_ UINT fCMIC, 160 _In_opt_ LPCSTR pszVerb) 161 { 162 return SHInvokeCommandOnContextMenuEx(hWnd, pUnk, pCM, fCMIC, CMF_EXTENDEDVERBS, pszVerb, NULL); 163 } 164 165 /************************************************************************* 166 * SHInvokeCommandWithFlagsAndSite [SHLWAPI.571] 167 */ 168 EXTERN_C 169 HRESULT WINAPI 170 SHInvokeCommandWithFlagsAndSite( 171 _In_opt_ HWND hWnd, 172 _In_opt_ IUnknown* pUnk, 173 _In_ IShellFolder* pShellFolder, 174 _In_ LPCITEMIDLIST pidl, 175 _In_ UINT fCMIC, 176 _In_opt_ LPCSTR pszVerb) 177 { 178 HRESULT hr = E_INVALIDARG; 179 if (pShellFolder) 180 { 181 IContextMenu *pCM; 182 hr = pShellFolder->GetUIObjectOf(hWnd, 1, &pidl, IID_IContextMenu, NULL, (void**)&pCM); 183 if (SUCCEEDED(hr)) 184 { 185 fCMIC |= CMIC_MASK_FLAG_LOG_USAGE; 186 hr = SHInvokeCommandOnContextMenuEx(hWnd, pUnk, pCM, fCMIC, 0, pszVerb, NULL); 187 pCM->Release(); 188 } 189 } 190 return hr; 191 } 192 193 194 /************************************************************************* 195 * IContextMenu_Invoke [SHLWAPI.207] 196 * 197 * Used by Win:SHELL32!CISFBand::_TrySimpleInvoke. 198 */ 199 EXTERN_C 200 BOOL WINAPI 201 IContextMenu_Invoke( 202 _In_ IContextMenu *pContextMenu, 203 _In_ HWND hwnd, 204 _In_ LPCSTR lpVerb, 205 _In_ UINT uFlags) 206 { 207 TRACE("(%p, %p, %s, %u)\n", pContextMenu, hwnd, debugstr_a(lpVerb), uFlags); 208 HRESULT hr = SHInvokeCommandOnContextMenuInternal(hwnd, NULL, pContextMenu, 0, 209 uFlags, lpVerb, NULL, false); 210 return !FAILED_UNEXPECTEDLY(hr); 211 } 212 213 /************************************************************************* 214 * PathFileExistsDefExtAndAttributesW [SHLWAPI.511] 215 * 216 * @param pszPath The path string. 217 * @param dwWhich The WHICH_... flags. 218 * @param pdwFileAttributes A pointer to the file attributes. Optional. 219 * @return TRUE if successful. 220 */ 221 BOOL WINAPI 222 PathFileExistsDefExtAndAttributesW( 223 _Inout_ LPWSTR pszPath, 224 _In_ DWORD dwWhich, 225 _Out_opt_ LPDWORD pdwFileAttributes) 226 { 227 TRACE("(%s, 0x%lX, %p)\n", debugstr_w(pszPath), dwWhich, pdwFileAttributes); 228 229 if (pdwFileAttributes) 230 *pdwFileAttributes = INVALID_FILE_ATTRIBUTES; 231 232 if (!pszPath) 233 return FALSE; 234 235 if (!dwWhich || (*PathFindExtensionW(pszPath) && (dwWhich & WHICH_OPTIONAL))) 236 return PathFileExistsAndAttributesW(pszPath, pdwFileAttributes); 237 238 if (!PathFileExistsDefExtW(pszPath, dwWhich)) 239 { 240 if (pdwFileAttributes) 241 *pdwFileAttributes = INVALID_FILE_ATTRIBUTES; 242 return FALSE; 243 } 244 245 if (pdwFileAttributes) 246 *pdwFileAttributes = GetFileAttributesW(pszPath); 247 248 return TRUE; 249 } 250 251 static inline BOOL 252 SHLWAPI_IsBogusHRESULT(HRESULT hr) 253 { 254 return (hr == E_FAIL || hr == E_INVALIDARG || hr == E_NOTIMPL); 255 } 256 257 // Used for IShellFolder_GetDisplayNameOf 258 struct RETRY_DATA 259 { 260 SHGDNF uRemove; 261 SHGDNF uAdd; 262 DWORD dwRetryFlags; 263 }; 264 static const RETRY_DATA g_RetryData[] = 265 { 266 { SHGDN_FOREDITING, SHGDN_NORMAL, SFGDNO_RETRYALWAYS }, 267 { SHGDN_FORADDRESSBAR, SHGDN_NORMAL, SFGDNO_RETRYALWAYS }, 268 { SHGDN_NORMAL, SHGDN_FORPARSING, SFGDNO_RETRYALWAYS }, 269 { SHGDN_FORPARSING, SHGDN_NORMAL, SFGDNO_RETRYWITHFORPARSING }, 270 { SHGDN_INFOLDER, SHGDN_NORMAL, SFGDNO_RETRYALWAYS }, 271 }; 272 273 /************************************************************************* 274 * IShellFolder_GetDisplayNameOf [SHLWAPI.316] 275 * 276 * @note Don't confuse with <shobjidl.h> inline function of the same name. 277 * If the original call fails with the given uFlags, this function will 278 * retry with other flags to attempt retrieving any meaningful description. 279 */ 280 EXTERN_C HRESULT WINAPI 281 IShellFolder_GetDisplayNameOf( 282 _In_ IShellFolder *psf, 283 _In_ LPCITEMIDLIST pidl, 284 _In_ SHGDNF uFlags, 285 _Out_ LPSTRRET lpName, 286 _In_ DWORD dwRetryFlags) // dwRetryFlags is an additional parameter 287 { 288 HRESULT hr; 289 290 TRACE("(%p)->(%p, 0x%lX, %p, 0x%lX)\n", psf, pidl, uFlags, lpName, dwRetryFlags); 291 292 hr = psf->GetDisplayNameOf(pidl, uFlags, lpName); 293 if (!SHLWAPI_IsBogusHRESULT(hr)) 294 return hr; 295 296 dwRetryFlags |= SFGDNO_RETRYALWAYS; 297 298 if ((uFlags & SHGDN_FORPARSING) == 0) 299 dwRetryFlags |= SFGDNO_RETRYWITHFORPARSING; 300 301 // Retry with other flags to get successful results 302 for (SIZE_T iEntry = 0; iEntry < _countof(g_RetryData); ++iEntry) 303 { 304 const RETRY_DATA *pData = &g_RetryData[iEntry]; 305 if (!(dwRetryFlags & pData->dwRetryFlags)) 306 continue; 307 308 SHGDNF uNewFlags = ((uFlags & ~pData->uRemove) | pData->uAdd); 309 if (uNewFlags == uFlags) 310 continue; 311 312 hr = psf->GetDisplayNameOf(pidl, uNewFlags, lpName); 313 if (!SHLWAPI_IsBogusHRESULT(hr)) 314 break; 315 316 uFlags = uNewFlags; // Update flags every time 317 } 318 319 return hr; 320 } 321 322 /************************************************************************* 323 * IShellFolder_ParseDisplayName [SHLWAPI.317] 324 * 325 * @note Don't confuse with <shobjidl.h> inline function of the same name. 326 * This function is safer than IShellFolder::ParseDisplayName. 327 */ 328 EXTERN_C HRESULT WINAPI 329 IShellFolder_ParseDisplayName( 330 _In_ IShellFolder *psf, 331 _In_opt_ HWND hwndOwner, 332 _In_opt_ LPBC pbcReserved, 333 _In_ LPOLESTR lpszDisplayName, 334 _Out_opt_ ULONG *pchEaten, 335 _Out_opt_ PIDLIST_RELATIVE *ppidl, 336 _Out_opt_ ULONG *pdwAttributes) 337 { 338 ULONG dummy1, dummy2; 339 340 TRACE("(%p)->(%p, %p, %s, %p, %p, %p)\n", psf, hwndOwner, pbcReserved, 341 debugstr_w(lpszDisplayName), pchEaten, ppidl, pdwAttributes); 342 343 if (!pdwAttributes) 344 { 345 dummy1 = 0; 346 pdwAttributes = &dummy1; 347 } 348 349 if (!pchEaten) 350 { 351 dummy2 = 0; 352 pchEaten = &dummy2; 353 } 354 355 if (ppidl) 356 *ppidl = NULL; 357 358 return psf->ParseDisplayName(hwndOwner, pbcReserved, lpszDisplayName, pchEaten, 359 ppidl, pdwAttributes); 360 } 361 362 /************************************************************************* 363 * IShellFolder_CompareIDs [SHLWAPI.551] 364 * 365 * @note Don't confuse with <shobjidl.h> inline function of the same name. 366 * This function tries IShellFolder2 if possible. 367 */ 368 EXTERN_C HRESULT WINAPI 369 IShellFolder_CompareIDs( 370 _In_ IShellFolder *psf, 371 _In_ LPARAM lParam, 372 _In_ PCUIDLIST_RELATIVE pidl1, 373 _In_ PCUIDLIST_RELATIVE pidl2) 374 { 375 TRACE("(%p, %p, %p, %p)\n", psf, lParam, pidl1, pidl2); 376 377 if (lParam & ~(SIZE_T)SHCIDS_COLUMNMASK) 378 { 379 /* Try as IShellFolder2 if possible */ 380 HRESULT hr = psf->QueryInterface(IID_IShellFolder2, (void **)&psf); 381 if (FAILED(hr)) 382 lParam &= SHCIDS_COLUMNMASK; 383 else 384 psf->Release(); 385 } 386 387 return psf->CompareIDs(lParam, pidl1, pidl2); 388 } 389