1 /*
2 * Shell AutoComplete list
3 *
4 * Copyright 2015 Thomas Faber
5 * Copyright 2020-2021 Katayama Hirofumi MZ
6 *
7 * This library is free software; you can redistribute it and/or
8 * modify it under the terms of the GNU Lesser General Public
9 * License as published by the Free Software Foundation; either
10 * version 2.1 of the License, or (at your option) any later version.
11 *
12 * This library is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * Lesser General Public License for more details.
16 *
17 * You should have received a copy of the GNU Lesser General Public
18 * License along with this library; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20 */
21
22 #include "precomp.h"
23
CACListISF()24 CACListISF::CACListISF()
25 : m_dwOptions(ACLO_CURRENTDIR | ACLO_MYCOMPUTER)
26 , m_iNextLocation(LT_DIRECTORY)
27 , m_fShowHidden(FALSE)
28 {
29 }
30
~CACListISF()31 CACListISF::~CACListISF()
32 {
33 }
34
NextLocation()35 HRESULT CACListISF::NextLocation()
36 {
37 TRACE("(%p)\n", this);
38 HRESULT hr;
39 switch (m_iNextLocation)
40 {
41 case LT_DIRECTORY:
42 m_iNextLocation = LT_DESKTOP;
43 if (!ILIsEmpty(m_pidlCurDir) && (m_dwOptions & ACLO_CURRENTDIR))
44 {
45 CComHeapPtr<ITEMIDLIST> pidl(ILClone(m_pidlCurDir));
46 hr = SetLocation(pidl.Detach());
47 if (SUCCEEDED(hr))
48 {
49 TRACE("LT_DIRECTORY\n");
50 return hr;
51 }
52 }
53 // FALL THROUGH
54 case LT_DESKTOP:
55 m_iNextLocation = LT_MYCOMPUTER;
56 if (m_dwOptions & ACLO_DESKTOP)
57 {
58 CComHeapPtr<ITEMIDLIST> pidl;
59 hr = SHGetSpecialFolderLocation(NULL, CSIDL_DESKTOP, &pidl);
60 if (FAILED_UNEXPECTEDLY(hr))
61 return S_FALSE;
62 hr = SetLocation(pidl.Detach());
63 if (SUCCEEDED(hr))
64 {
65 TRACE("LT_DESKTOP\n");
66 return hr;
67 }
68 }
69 // FALL THROUGH
70 case LT_MYCOMPUTER:
71 m_iNextLocation = LT_FAVORITES;
72 if (m_dwOptions & ACLO_MYCOMPUTER)
73 {
74 CComHeapPtr<ITEMIDLIST> pidl;
75 hr = SHGetSpecialFolderLocation(NULL, CSIDL_DRIVES, &pidl);
76 if (FAILED_UNEXPECTEDLY(hr))
77 return S_FALSE;
78 hr = SetLocation(pidl.Detach());
79 if (SUCCEEDED(hr))
80 {
81 TRACE("LT_MYCOMPUTER\n");
82 return hr;
83 }
84 }
85 // FALL THROUGH
86 case LT_FAVORITES:
87 m_iNextLocation = LT_MAX;
88 if (m_dwOptions & ACLO_FAVORITES)
89 {
90 CComHeapPtr<ITEMIDLIST> pidl;
91 hr = SHGetSpecialFolderLocation(NULL, CSIDL_FAVORITES, &pidl);
92 if (FAILED_UNEXPECTEDLY(hr))
93 return S_FALSE;
94 hr = SetLocation(pidl.Detach());
95 if (SUCCEEDED(hr))
96 {
97 TRACE("LT_FAVORITES\n");
98 return hr;
99 }
100 }
101 // FALL THROUGH
102 case LT_MAX:
103 default:
104 TRACE("LT_MAX\n");
105 return S_FALSE;
106 }
107 }
108
SetLocation(LPITEMIDLIST pidl)109 HRESULT CACListISF::SetLocation(LPITEMIDLIST pidl)
110 {
111 TRACE("(%p, %p)\n", this, pidl);
112
113 m_pEnumIDList.Release();
114 m_pShellFolder.Release();
115 m_pidlLocation.Free();
116
117 if (!pidl)
118 return E_FAIL;
119
120 m_pidlLocation.Attach(pidl);
121
122 CComPtr<IShellFolder> pFolder;
123 HRESULT hr = SHGetDesktopFolder(&pFolder);
124 if (FAILED_UNEXPECTEDLY(hr))
125 return hr;
126
127 if (!ILIsEmpty(pidl))
128 {
129 hr = pFolder->BindToObject(pidl, NULL, IID_PPV_ARG(IShellFolder, &m_pShellFolder));
130 if (FAILED_UNEXPECTEDLY(hr))
131 return hr;
132 }
133 else
134 {
135 m_pShellFolder.Attach(pFolder.Detach());
136 }
137
138 SHCONTF Flags = SHCONTF_FOLDERS | SHCONTF_INIT_ON_FIRST_NEXT;
139 if (m_fShowHidden)
140 Flags |= SHCONTF_INCLUDEHIDDEN;
141 if (!(m_dwOptions & ACLO_FILESYSDIRS))
142 Flags |= SHCONTF_NONFOLDERS;
143
144 hr = m_pShellFolder->EnumObjects(NULL, Flags, &m_pEnumIDList);
145 if (hr != S_OK)
146 {
147 ERR("EnumObjects failed: 0x%lX\n", hr);
148 hr = E_FAIL;
149 }
150 return hr;
151 }
152
GetDisplayName(LPCITEMIDLIST pidlChild,CComHeapPtr<WCHAR> & pszChild)153 HRESULT CACListISF::GetDisplayName(LPCITEMIDLIST pidlChild, CComHeapPtr<WCHAR>& pszChild)
154 {
155 TRACE("(%p, %p)\n", this, pidlChild);
156 pszChild.Free();
157
158 STRRET StrRet;
159 DWORD dwFlags = SHGDN_INFOLDER | SHGDN_FORPARSING | SHGDN_FORADDRESSBAR;
160 HRESULT hr = m_pShellFolder->GetDisplayNameOf(pidlChild, dwFlags, &StrRet);
161 if (FAILED(hr))
162 {
163 dwFlags = SHGDN_INFOLDER | SHGDN_FORPARSING;
164 hr = m_pShellFolder->GetDisplayNameOf(pidlChild, dwFlags, &StrRet);
165 if (FAILED_UNEXPECTEDLY(hr))
166 return hr;
167 }
168
169 hr = StrRetToStrW(&StrRet, NULL, &pszChild);
170 if (FAILED_UNEXPECTEDLY(hr))
171 return hr;
172
173 TRACE("pszChild: '%S'\n", static_cast<LPCWSTR>(pszChild));
174 return hr;
175 }
176
177 HRESULT
GetPaths(LPCITEMIDLIST pidlChild,CComHeapPtr<WCHAR> & pszRaw,CComHeapPtr<WCHAR> & pszExpanded)178 CACListISF::GetPaths(LPCITEMIDLIST pidlChild, CComHeapPtr<WCHAR>& pszRaw,
179 CComHeapPtr<WCHAR>& pszExpanded)
180 {
181 TRACE("(%p, %p)\n", this, pidlChild);
182
183 CComHeapPtr<WCHAR> pszChild;
184 HRESULT hr = GetDisplayName(pidlChild, pszChild);
185 if (FAILED_UNEXPECTEDLY(hr))
186 return hr;
187
188 CStringW szRawPath, szExpanded;
189 if (m_szRawPath.GetLength() && m_iNextLocation == LT_DIRECTORY)
190 {
191 INT cchExpand = m_szRawPath.GetLength();
192 if (StrCmpNIW(pszChild, m_szRawPath, cchExpand) != 0 ||
193 pszChild[0] != L'\\' || pszChild[1] != L'\\')
194 {
195 szRawPath = m_szRawPath;
196 szExpanded = m_szExpanded;
197 }
198 }
199 szRawPath += pszChild;
200 szExpanded += pszChild;
201
202 SHStrDupW(szRawPath, &pszRaw);
203 SHStrDupW(szExpanded, &pszExpanded);
204 TRACE("pszRaw: '%S'\n", static_cast<LPCWSTR>(pszRaw));
205 TRACE("pszExpanded: '%S'\n", static_cast<LPCWSTR>(pszExpanded));
206 return S_OK;
207 }
208
209 // *** IEnumString methods ***
Next(ULONG celt,LPOLESTR * rgelt,ULONG * pceltFetched)210 STDMETHODIMP CACListISF::Next(ULONG celt, LPOLESTR *rgelt, ULONG *pceltFetched)
211 {
212 TRACE("(%p, %d, %p, %p)\n", this, celt, rgelt, pceltFetched);
213
214 if (celt == 0)
215 return S_OK;
216 if (!rgelt)
217 return S_FALSE;
218
219 *rgelt = NULL;
220 if (pceltFetched)
221 *pceltFetched = 0;
222
223 if (!m_pEnumIDList)
224 {
225 NextLocation();
226 if (!m_pEnumIDList)
227 return S_FALSE;
228 }
229
230 HRESULT hr;
231 CComHeapPtr<ITEMIDLIST> pidlChild;
232 CComHeapPtr<WCHAR> pszRawPath, pszExpanded;
233
234 do
235 {
236 for (;;)
237 {
238 pidlChild.Free();
239 hr = m_pEnumIDList->Next(1, &pidlChild, NULL);
240 if (hr != S_OK)
241 break;
242
243 pszRawPath.Free();
244 pszExpanded.Free();
245 GetPaths(pidlChild, pszRawPath, pszExpanded);
246 if (!pszRawPath || !pszExpanded)
247 continue;
248
249 DWORD attrs = SFGAO_FOLDER | SFGAO_FILESYSTEM;
250 LPCITEMIDLIST pidlRef = pidlChild;
251 hr = m_pShellFolder->GetAttributesOf(1, &pidlRef, &attrs);
252 if (FAILED_UNEXPECTEDLY(hr))
253 continue;
254
255 if ((m_dwOptions & ACLO_FILESYSDIRS) && !(attrs & SFGAO_FOLDER))
256 continue;
257 if ((m_dwOptions & (ACLO_FILESYSONLY | ACLO_FILESYSDIRS)) && !(attrs & SFGAO_FILESYSTEM))
258 continue;
259
260 hr = S_OK;
261 break;
262 }
263 } while (hr == S_FALSE && NextLocation() == S_OK);
264
265 if (hr == S_OK)
266 {
267 *rgelt = pszRawPath.Detach();
268 if (pceltFetched)
269 *pceltFetched = 1;
270 }
271 else
272 {
273 hr = S_FALSE;
274 }
275
276 TRACE("*rgelt: %S\n", *rgelt);
277 return hr;
278 }
279
Reset()280 STDMETHODIMP CACListISF::Reset()
281 {
282 TRACE("(%p)\n", this);
283
284 m_iNextLocation = LT_DIRECTORY;
285 m_szRawPath = L"";
286
287 SHELLSTATE ss = { 0 };
288 SHGetSetSettings(&ss, SSF_SHOWALLOBJECTS, FALSE);
289 m_fShowHidden = ss.fShowAllObjects;
290
291 if (m_dwOptions & ACLO_CURRENTDIR)
292 {
293 CComHeapPtr<ITEMIDLIST> pidl;
294 if (m_pBrowserService)
295 {
296 m_pBrowserService->GetPidl(&pidl);
297 if (pidl)
298 Initialize(pidl);
299 }
300 HRESULT hr = SetLocation(pidl.Detach());
301 if (FAILED_UNEXPECTEDLY(hr))
302 return S_FALSE;
303 }
304 return S_OK;
305 }
306
Skip(ULONG celt)307 STDMETHODIMP CACListISF::Skip(ULONG celt)
308 {
309 TRACE("(%p, %d)\n", this, celt);
310 return E_NOTIMPL;
311 }
312
Clone(IEnumString ** ppOut)313 STDMETHODIMP CACListISF::Clone(IEnumString **ppOut)
314 {
315 TRACE("(%p, %p)\n", this, ppOut);
316 *ppOut = NULL;
317 return E_NOTIMPL;
318 }
319
320 // *** IACList methods ***
Expand(LPCOLESTR pszExpand)321 STDMETHODIMP CACListISF::Expand(LPCOLESTR pszExpand)
322 {
323 TRACE("(%p, %ls)\n", this, pszExpand);
324
325 m_szRawPath = pszExpand;
326 m_iNextLocation = LT_DIRECTORY;
327
328 // skip left space
329 while (*pszExpand == L' ')
330 ++pszExpand;
331
332 // expand environment variables (%WINDIR% etc.)
333 WCHAR szExpanded[MAX_PATH], szPath1[MAX_PATH], szPath2[MAX_PATH];
334 ExpandEnvironmentStringsW(pszExpand, szExpanded, _countof(szExpanded));
335 pszExpand = szExpanded;
336
337 // get full path
338 if (szExpanded[0] && szExpanded[1] == L':' && szExpanded[2] == 0)
339 {
340 // 'C:' --> 'C:\'
341 szExpanded[2] = L'\\';
342 szExpanded[3] = 0;
343 }
344 else
345 {
346 if (PathIsRelativeW(pszExpand) &&
347 SHGetPathFromIDListW(m_pidlCurDir, szPath1) &&
348 PathCombineW(szPath2, szPath1, pszExpand) &&
349 PathFileExistsW(szPath2))
350 {
351 pszExpand = szPath2;
352 }
353 else if (PathFileExistsW(pszExpand))
354 {
355 GetFullPathNameW(pszExpand, _countof(szPath1), szPath1, NULL);
356 pszExpand = szPath1;
357 }
358 }
359
360 CComHeapPtr<ITEMIDLIST> pidl;
361 m_szExpanded = pszExpand;
362 HRESULT hr = SHParseDisplayName(m_szExpanded, NULL, &pidl, NULL, NULL);
363 if (SUCCEEDED(hr))
364 {
365 hr = SetLocation(pidl.Detach());
366 if (FAILED_UNEXPECTEDLY(hr))
367 {
368 m_szRawPath = L"";
369 m_szExpanded = L"";
370 }
371 }
372 return hr;
373 }
374
375 // *** IACList2 methods ***
SetOptions(DWORD dwFlag)376 STDMETHODIMP CACListISF::SetOptions(DWORD dwFlag)
377 {
378 TRACE("(%p, %lu)\n", this, dwFlag);
379 m_dwOptions = dwFlag;
380 return S_OK;
381 }
382
GetOptions(DWORD * pdwFlag)383 STDMETHODIMP CACListISF::GetOptions(DWORD* pdwFlag)
384 {
385 TRACE("(%p, %p)\n", this, pdwFlag);
386 if (pdwFlag)
387 {
388 *pdwFlag = m_dwOptions;
389 return S_OK;
390 }
391 return E_INVALIDARG;
392 }
393
394 // *** IShellService methods ***
SetOwner(IUnknown * punkOwner)395 STDMETHODIMP CACListISF::SetOwner(IUnknown *punkOwner)
396 {
397 TRACE("(%p, %p)\n", this, punkOwner);
398 m_pBrowserService.Release();
399 punkOwner->QueryInterface(IID_PPV_ARG(IBrowserService, &m_pBrowserService));
400 return S_OK;
401 }
402
403 // *** IPersist methods ***
GetClassID(CLSID * pClassID)404 STDMETHODIMP CACListISF::GetClassID(CLSID *pClassID)
405 {
406 TRACE("(%p, %p)\n", this, pClassID);
407 if (pClassID == NULL)
408 return E_POINTER;
409 *pClassID = CLSID_ACListISF;
410 return S_OK;
411 }
412
413 // *** IPersistFolder methods ***
Initialize(PCIDLIST_ABSOLUTE pidl)414 STDMETHODIMP CACListISF::Initialize(PCIDLIST_ABSOLUTE pidl)
415 {
416 TRACE("(%p, %p)\n", this, pidl);
417 m_pidlCurDir.Free();
418 if (!pidl)
419 return S_OK;
420
421 LPITEMIDLIST pidlClone = ILClone(pidl);
422 if (!pidlClone)
423 {
424 ERR("Out of memory\n");
425 return E_OUTOFMEMORY;
426 }
427 m_pidlCurDir.Attach(pidlClone);
428 return S_OK;
429 }
430
431 // *** ICurrentWorkingDirectory methods ***
GetDirectory(LPWSTR pwzPath,DWORD cchSize)432 STDMETHODIMP CACListISF::GetDirectory(LPWSTR pwzPath, DWORD cchSize)
433 {
434 TRACE("(%p, %p, %ld)\n", this, pwzPath, cchSize);
435 return E_NOTIMPL;
436 }
437
SetDirectory(LPCWSTR pwzPath)438 STDMETHODIMP CACListISF::SetDirectory(LPCWSTR pwzPath)
439 {
440 TRACE("(%p, %ls, %ld)\n", this, pwzPath);
441 LPITEMIDLIST pidl = ILCreateFromPathW(pwzPath);
442 if (!pidl)
443 {
444 ERR("Out of memory\n");
445 return E_OUTOFMEMORY;
446 }
447 m_pidlCurDir.Attach(pidl);
448 return S_OK;
449 }
450