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
GetVersionMajorMinor()43 GetVersionMajorMinor()
44 {
45 DWORD version = GetVersion();
46 return MAKEWORD(HIBYTE(version), LOBYTE(version));
47 }
48
49 static HRESULT
SHInvokeCommandOnContextMenuInternal(_In_opt_ HWND hWnd,_In_opt_ IUnknown * pUnk,_In_ IContextMenu * pCM,_In_ UINT fCMIC,_In_ UINT fCMF,_In_opt_ LPCSTR pszVerb,_In_opt_ LPCWSTR pwszDir,_In_ bool ForceQCM)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
SHInvokeCommandOnContextMenuEx(_In_opt_ HWND hWnd,_In_opt_ IUnknown * pUnk,_In_ IContextMenu * pCM,_In_ UINT fCMIC,_In_ UINT fCMF,_In_opt_ LPCSTR pszVerb,_In_opt_ LPCWSTR pwszDir)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
SHInvokeCommandOnContextMenu(_In_opt_ HWND hWnd,_In_opt_ IUnknown * pUnk,_In_ IContextMenu * pCM,_In_ UINT fCMIC,_In_opt_ LPCSTR pszVerb)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
SHInvokeCommandWithFlagsAndSite(_In_opt_ HWND hWnd,_In_opt_ IUnknown * pUnk,_In_ IShellFolder * pShellFolder,_In_ LPCITEMIDLIST pidl,_In_ UINT fCMIC,_In_opt_ LPCSTR pszVerb)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
IContextMenu_Invoke(_In_ IContextMenu * pContextMenu,_In_ HWND hwnd,_In_ LPCSTR lpVerb,_In_ UINT uFlags)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
PathFileExistsDefExtAndAttributesW(_Inout_ LPWSTR pszPath,_In_ DWORD dwWhich,_Out_opt_ LPDWORD pdwFileAttributes)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
SHLWAPI_IsBogusHRESULT(HRESULT hr)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
IShellFolder_GetDisplayNameOf(_In_ IShellFolder * psf,_In_ LPCITEMIDLIST pidl,_In_ SHGDNF uFlags,_Out_ LPSTRRET lpName,_In_ DWORD dwRetryFlags)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
IShellFolder_ParseDisplayName(_In_ IShellFolder * psf,_In_opt_ HWND hwndOwner,_In_opt_ LPBC pbcReserved,_In_ LPOLESTR lpszDisplayName,_Out_opt_ ULONG * pchEaten,_Out_opt_ PIDLIST_RELATIVE * ppidl,_Out_opt_ ULONG * pdwAttributes)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
IShellFolder_CompareIDs(_In_ IShellFolder * psf,_In_ LPARAM lParam,_In_ PCUIDLIST_RELATIVE pidl1,_In_ PCUIDLIST_RELATIVE pidl2)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