xref: /reactos/dll/win32/shlwapi/utils.cpp (revision 0b366ea1)
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 WINE_DEFAULT_DEBUG_CHANNEL(shell);
37 
38 /*************************************************************************
39  * IContextMenu_Invoke [SHLWAPI.207]
40  *
41  * Used by Win:SHELL32!CISFBand::_TrySimpleInvoke.
42  */
43 EXTERN_C
44 BOOL WINAPI
45 IContextMenu_Invoke(
46     _In_ IContextMenu *pContextMenu,
47     _In_ HWND hwnd,
48     _In_ LPCSTR lpVerb,
49     _In_ UINT uFlags)
50 {
51     CMINVOKECOMMANDINFO info;
52     BOOL ret = FALSE;
53     INT iDefItem = 0;
54     HMENU hMenu = NULL;
55     HCURSOR hOldCursor;
56 
57     TRACE("(%p, %p, %s, %u)\n", pContextMenu, hwnd, debugstr_a(lpVerb), uFlags);
58 
59     if (!pContextMenu)
60         return FALSE;
61 
62     hOldCursor = SetCursor(LoadCursorW(NULL, (LPCWSTR)IDC_WAIT));
63 
64     ZeroMemory(&info, sizeof(info));
65     info.cbSize = sizeof(info);
66     info.hwnd = hwnd;
67     info.nShow = SW_NORMAL;
68     info.lpVerb = lpVerb;
69 
70     if (IS_INTRESOURCE(lpVerb))
71     {
72         hMenu = CreatePopupMenu();
73         if (hMenu)
74         {
75             pContextMenu->QueryContextMenu(hMenu, 0, 1, MAXSHORT, uFlags | CMF_DEFAULTONLY);
76             iDefItem = GetMenuDefaultItem(hMenu, 0, 0);
77             if (iDefItem != -1)
78                 info.lpVerb = MAKEINTRESOURCEA(iDefItem - 1);
79         }
80     }
81 
82     if (iDefItem != -1 || info.lpVerb)
83     {
84         if (!hwnd)
85             info.fMask |= CMIC_MASK_FLAG_NO_UI;
86         ret = SUCCEEDED(pContextMenu->InvokeCommand(&info));
87     }
88 
89     /* Invoking itself doesn't need the menu object, but getting the command info
90        needs the menu. */
91     if (hMenu)
92         DestroyMenu(hMenu);
93 
94     SetCursor(hOldCursor);
95 
96     return ret;
97 }
98 
99 /*************************************************************************
100  * PathFileExistsDefExtAndAttributesW [SHLWAPI.511]
101  *
102  * @param pszPath The path string.
103  * @param dwWhich The WHICH_... flags.
104  * @param pdwFileAttributes A pointer to the file attributes. Optional.
105  * @return TRUE if successful.
106  */
107 BOOL WINAPI
108 PathFileExistsDefExtAndAttributesW(
109     _Inout_ LPWSTR pszPath,
110     _In_ DWORD dwWhich,
111     _Out_opt_ LPDWORD pdwFileAttributes)
112 {
113     TRACE("(%s, 0x%lX, %p)\n", debugstr_w(pszPath), dwWhich, pdwFileAttributes);
114 
115     if (pdwFileAttributes)
116         *pdwFileAttributes = INVALID_FILE_ATTRIBUTES;
117 
118     if (!pszPath)
119         return FALSE;
120 
121     if (!dwWhich || (*PathFindExtensionW(pszPath) && (dwWhich & WHICH_OPTIONAL)))
122         return PathFileExistsAndAttributesW(pszPath, pdwFileAttributes);
123 
124     if (!PathFileExistsDefExtW(pszPath, dwWhich))
125     {
126         if (pdwFileAttributes)
127             *pdwFileAttributes = INVALID_FILE_ATTRIBUTES;
128         return FALSE;
129     }
130 
131     if (pdwFileAttributes)
132         *pdwFileAttributes = GetFileAttributesW(pszPath);
133 
134     return TRUE;
135 }
136 
137 static inline BOOL
138 SHLWAPI_IsBogusHRESULT(HRESULT hr)
139 {
140     return (hr == E_FAIL || hr == E_INVALIDARG || hr == E_NOTIMPL);
141 }
142 
143 // Used for IShellFolder_GetDisplayNameOf
144 struct RETRY_DATA
145 {
146     SHGDNF uRemove;
147     SHGDNF uAdd;
148     DWORD dwRetryFlags;
149 };
150 static const RETRY_DATA g_RetryData[] =
151 {
152     { SHGDN_FOREDITING,    SHGDN_NORMAL,     SFGDNO_RETRYALWAYS         },
153     { SHGDN_FORADDRESSBAR, SHGDN_NORMAL,     SFGDNO_RETRYALWAYS         },
154     { SHGDN_NORMAL,        SHGDN_FORPARSING, SFGDNO_RETRYALWAYS         },
155     { SHGDN_FORPARSING,    SHGDN_NORMAL,     SFGDNO_RETRYWITHFORPARSING },
156     { SHGDN_INFOLDER,      SHGDN_NORMAL,     SFGDNO_RETRYALWAYS         },
157 };
158 
159 /*************************************************************************
160  * IShellFolder_GetDisplayNameOf [SHLWAPI.316]
161  *
162  * @note Don't confuse with <shobjidl.h> inline function of the same name.
163  *       If the original call fails with the given uFlags, this function will
164  *       retry with other flags to attempt retrieving any meaningful description.
165  */
166 EXTERN_C HRESULT WINAPI
167 IShellFolder_GetDisplayNameOf(
168     _In_ IShellFolder *psf,
169     _In_ LPCITEMIDLIST pidl,
170     _In_ SHGDNF uFlags,
171     _Out_ LPSTRRET lpName,
172     _In_ DWORD dwRetryFlags) // dwRetryFlags is an additional parameter
173 {
174     HRESULT hr;
175 
176     TRACE("(%p)->(%p, 0x%lX, %p, 0x%lX)\n", psf, pidl, uFlags, lpName, dwRetryFlags);
177 
178     hr = psf->GetDisplayNameOf(pidl, uFlags, lpName);
179     if (!SHLWAPI_IsBogusHRESULT(hr))
180         return hr;
181 
182     dwRetryFlags |= SFGDNO_RETRYALWAYS;
183 
184     if ((uFlags & SHGDN_FORPARSING) == 0)
185         dwRetryFlags |= SFGDNO_RETRYWITHFORPARSING;
186 
187     // Retry with other flags to get successful results
188     for (SIZE_T iEntry = 0; iEntry < _countof(g_RetryData); ++iEntry)
189     {
190         const RETRY_DATA *pData = &g_RetryData[iEntry];
191         if (!(dwRetryFlags & pData->dwRetryFlags))
192             continue;
193 
194         SHGDNF uNewFlags = ((uFlags & ~pData->uRemove) | pData->uAdd);
195         if (uNewFlags == uFlags)
196             continue;
197 
198         hr = psf->GetDisplayNameOf(pidl, uNewFlags, lpName);
199         if (!SHLWAPI_IsBogusHRESULT(hr))
200             break;
201 
202         uFlags = uNewFlags; // Update flags every time
203     }
204 
205     return hr;
206 }
207 
208 /*************************************************************************
209  * IShellFolder_ParseDisplayName [SHLWAPI.317]
210  *
211  * @note Don't confuse with <shobjidl.h> inline function of the same name.
212  *       This function is safer than IShellFolder::ParseDisplayName.
213  */
214 EXTERN_C HRESULT WINAPI
215 IShellFolder_ParseDisplayName(
216     _In_ IShellFolder *psf,
217     _In_opt_ HWND hwndOwner,
218     _In_opt_ LPBC pbcReserved,
219     _In_ LPOLESTR lpszDisplayName,
220     _Out_opt_ ULONG *pchEaten,
221     _Out_opt_ PIDLIST_RELATIVE *ppidl,
222     _Out_opt_ ULONG *pdwAttributes)
223 {
224     ULONG dummy1, dummy2;
225 
226     TRACE("(%p)->(%p, %p, %s, %p, %p, %p)\n", psf, hwndOwner, pbcReserved,
227           debugstr_w(lpszDisplayName), pchEaten, ppidl, pdwAttributes);
228 
229     if (!pdwAttributes)
230     {
231         dummy1 = 0;
232         pdwAttributes = &dummy1;
233     }
234 
235     if (!pchEaten)
236     {
237         dummy2 = 0;
238         pchEaten = &dummy2;
239     }
240 
241     if (ppidl)
242         *ppidl = NULL;
243 
244     return psf->ParseDisplayName(hwndOwner, pbcReserved, lpszDisplayName, pchEaten,
245                                  ppidl, pdwAttributes);
246 }
247 
248 /*************************************************************************
249  * IShellFolder_CompareIDs [SHLWAPI.551]
250  *
251  * @note Don't confuse with <shobjidl.h> inline function of the same name.
252  *       This function tries IShellFolder2 if possible.
253  */
254 EXTERN_C HRESULT WINAPI
255 IShellFolder_CompareIDs(
256     _In_ IShellFolder *psf,
257     _In_ LPARAM lParam,
258     _In_ PCUIDLIST_RELATIVE pidl1,
259     _In_ PCUIDLIST_RELATIVE pidl2)
260 {
261     TRACE("(%p, %p, %p, %p)\n", psf, lParam, pidl1, pidl2);
262 
263     if (lParam & ~(SIZE_T)SHCIDS_COLUMNMASK)
264     {
265         /* Try as IShellFolder2 if possible */
266         HRESULT hr = psf->QueryInterface(IID_IShellFolder2, (void **)&psf);
267         if (FAILED(hr))
268             lParam &= SHCIDS_COLUMNMASK;
269         else
270             psf->Release();
271     }
272 
273     return psf->CompareIDs(lParam, pidl1, pidl2);
274 }
275