xref: /reactos/dll/win32/shlwapi/utils.cpp (revision 0c2cdcae)
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