Reactos
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}