1 // Windows/Shell.cpp
2 
3 #include "StdAfx.h"
4 
5 #include "../Common/MyCom.h"
6 #include "../Common/StringConvert.h"
7 
8 #include "COM.h"
9 #include "FileName.h"
10 #include "MemoryGlobal.h"
11 #include "Shell.h"
12 
13 #ifndef _UNICODE
14 extern bool g_IsNT;
15 #endif
16 
17 // MSVC6 and old SDK don't support this function:
18 // #define LWSTDAPI  EXTERN_C DECLSPEC_IMPORT HRESULT STDAPICALLTYPE
19 // LWSTDAPI StrRetToStrW(STRRET *pstr, LPCITEMIDLIST pidl, LPWSTR *ppsz);
20 
21 // #define SHOW_DEBUG_SHELL
22 
23 #ifdef SHOW_DEBUG_SHELL
24 
25 #include "../Common/IntToString.h"
26 
Print_Number(UInt32 number, const char *s)27 static void Print_Number(UInt32 number, const char *s)
28 {
29   AString s2;
30   s2.Add_UInt32(number);
31   s2.Add_Space();
32   s2 += s;
33   OutputDebugStringA(s2);
34 }
35 
36 #define ODS(sz) { OutputDebugStringA(sz); }
37 #define ODS_U(s) { OutputDebugStringW(s); }
38 #define ODS_(op) { op; }
39 
40 #else
41 
42 #define ODS(sz)
43 #define ODS_U(s)
44 #define ODS_(op)
45 
46 #endif
47 
48 
49 namespace NWindows {
50 namespace NShell {
51 
52 #ifndef UNDER_CE
53 
54 // SHGetMalloc is unsupported in Windows Mobile?
55 
Free()56 void CItemIDList::Free()
57 {
58   if (!m_Object)
59     return;
60   /* DOCs:
61       SHGetMalloc was introduced in Windows 95 and Microsoft Windows NT 4.0,
62       but as of Windows 2000 it is no longer necessary.
63       In its place, programs can call the equivalent (and easier to use) CoTaskMemAlloc and CoTaskMemFree.
64      Description from oldnewthings:
65        shell functions could work without COM (if OLE32.DLL is not loaded),
66        but now if OLE32.DLL is loaded, then shell functions and com functions do same things.
67      22.02: so we use OLE32.DLL function to free memory:
68   */
69   /*
70   CMyComPtr<IMalloc> shellMalloc;
71   if (::SHGetMalloc(&shellMalloc) != NOERROR)
72     throw 41099;
73   shellMalloc->Free(m_Object);
74   */
75   CoTaskMemFree(m_Object);
76   m_Object = NULL;
77 }
78 
79 /*
80 CItemIDList::(LPCITEMIDLIST itemIDList): m_Object(NULL)
81   {  *this = itemIDList; }
82 CItemIDList::(const CItemIDList& itemIDList): m_Object(NULL)
83   {  *this = itemIDList; }
84 
85 CItemIDList& CItemIDList::operator=(LPCITEMIDLIST object)
86 {
87   Free();
88   if (object != 0)
89   {
90     UINT32 size = GetSize(object);
91     m_Object = (LPITEMIDLIST)CoTaskMemAlloc(size);
92     if (m_Object != NULL)
93       MoveMemory(m_Object, object, size);
94   }
95   return *this;
96 }
97 
98 CItemIDList& CItemIDList::operator=(const CItemIDList &object)
99 {
100   Free();
101   if (object.m_Object != NULL)
102   {
103     UINT32 size = GetSize(object.m_Object);
104     m_Object = (LPITEMIDLIST)CoTaskMemAlloc(size);
105     if (m_Object != NULL)
106       MoveMemory(m_Object, object.m_Object, size);
107   }
108   return *this;
109 }
110 */
111 
112 
ReadUnicodeStrings(const wchar_t *p, size_t size, UStringVector &names)113 static HRESULT ReadUnicodeStrings(const wchar_t *p, size_t size, UStringVector &names)
114 {
115   names.Clear();
116   const wchar_t *lim = p + size;
117   UString s;
118   /*
119   if (size == 0 || p[size - 1] != 0)
120     return E_INVALIDARG;
121   if (size == 1)
122     return S_OK;
123   if (p[size - 2] != 0)
124     return E_INVALIDARG;
125   */
126   for (;;)
127   {
128     const wchar_t *start = p;
129     for (;;)
130     {
131       if (p == lim) return E_INVALIDARG; // S_FALSE
132       if (*p++ == 0)
133         break;
134     }
135     const size_t num = (size_t)(p - start);
136     if (num == 1)
137     {
138       if (p != lim) return E_INVALIDARG; // S_FALSE
139       return S_OK;
140     }
141     s.SetFrom(start, (unsigned)(num - 1));
142     ODS_U(s)
143     names.Add(s);
144     // names.ReserveOnePosition();
145     // names.AddInReserved_Ptr_of_new(new UString((unsigned)num - 1, start));
146   }
147 }
148 
149 
ReadAnsiStrings(const char *p, size_t size, UStringVector &names)150 static HRESULT ReadAnsiStrings(const char *p, size_t size, UStringVector &names)
151 {
152   names.Clear();
153   AString name;
154   for (; size != 0; size--)
155   {
156     const char c = *p++;
157     if (c == 0)
158     {
159       if (name.IsEmpty())
160         return S_OK;
161       names.Add(GetUnicodeString(name));
162       name.Empty();
163     }
164     else
165       name += c;
166   }
167   return E_INVALIDARG;
168 }
169 
170 
171 #define INIT_FORMATETC_HGLOBAL(type) { (type), NULL, DVASPECT_CONTENT, -1, TYMED_HGLOBAL }
172 
DataObject_GetData_HGLOBAL(IDataObject *dataObject, CLIPFORMAT cf, NCOM::CStgMedium &medium)173 static HRESULT DataObject_GetData_HGLOBAL(IDataObject *dataObject, CLIPFORMAT cf, NCOM::CStgMedium &medium)
174 {
175   FORMATETC etc = INIT_FORMATETC_HGLOBAL(cf);
176   RINOK(dataObject->GetData(&etc, &medium))
177   if (medium.tymed != TYMED_HGLOBAL)
178     return E_INVALIDARG;
179   return S_OK;
180 }
181 
DataObject_GetData_HDROP_Names(IDataObject *dataObject, UStringVector &names)182 static HRESULT DataObject_GetData_HDROP_Names(IDataObject *dataObject, UStringVector &names)
183 {
184   names.Clear();
185   NCOM::CStgMedium medium;
186 
187   /* Win10 : if (dataObject) is from IContextMenu::Initialize() and
188     if (len_of_path >= MAX_PATH (260) for some file in data object)
189     {
190       GetData() returns HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER)
191         "The data area passed to a system call is too small",
192       Is there a way to fix this code for long paths?
193     } */
194 
195   RINOK(DataObject_GetData_HGLOBAL(dataObject, CF_HDROP, medium))
196   const size_t blockSize = GlobalSize(medium.hGlobal);
197   if (blockSize < sizeof(DROPFILES))
198     return E_INVALIDARG;
199   NMemory::CGlobalLock dropLock(medium.hGlobal);
200   const DROPFILES *dropFiles = (const DROPFILES *)dropLock.GetPointer();
201   if (!dropFiles)
202     return E_INVALIDARG;
203   if (blockSize < dropFiles->pFiles
204       || dropFiles->pFiles < sizeof(DROPFILES)
205       // || dropFiles->pFiles != sizeof(DROPFILES)
206       )
207     return E_INVALIDARG;
208   const size_t size = blockSize - dropFiles->pFiles;
209   const void *namesData = (const Byte *)(const void *)dropFiles + dropFiles->pFiles;
210   HRESULT hres;
211   if (dropFiles->fWide)
212   {
213     if (size % sizeof(wchar_t) != 0)
214       return E_INVALIDARG;
215     hres = ReadUnicodeStrings((const wchar_t *)namesData, size / sizeof(wchar_t), names);
216   }
217   else
218     hres = ReadAnsiStrings((const char *)namesData, size, names);
219 
220   ODS_(Print_Number(names.Size(), "DataObject_GetData_HDROP_Names"))
221   return hres;
222 }
223 
224 
225 
226 // CF_IDLIST:
227 #define MYWIN_CFSTR_SHELLIDLIST  TEXT("Shell IDList Array")
228 
229 typedef struct
230 {
231   UINT cidl;
232   UINT aoffset[1];
233 } MYWIN_CIDA;
234 /*
235   cidl : number of PIDLs that are being transferred, not including the parent folder.
236   aoffset : An array of offsets, relative to the beginning of this structure.
237   aoffset[0] - fully qualified PIDL of a parent folder.
238                If this PIDL is empty, the parent folder is the desktop.
239   aoffset[1] ... aoffset[cidl] : offset to one of the PIDLs to be transferred.
240   All of these PIDLs are relative to the PIDL of the parent folder.
241 */
242 
DataObject_GetData_IDLIST(IDataObject *dataObject, UStringVector &names)243 static HRESULT DataObject_GetData_IDLIST(IDataObject *dataObject, UStringVector &names)
244 {
245   names.Clear();
246   NCOM::CStgMedium medium;
247   RINOK(DataObject_GetData_HGLOBAL(dataObject, (CLIPFORMAT)
248       RegisterClipboardFormat(MYWIN_CFSTR_SHELLIDLIST), medium))
249   const size_t blockSize = GlobalSize(medium.hGlobal);
250   if (blockSize < sizeof(MYWIN_CIDA) || blockSize >= (UInt32)((UInt32)0 - 1))
251     return E_INVALIDARG;
252   NMemory::CGlobalLock dropLock(medium.hGlobal);
253   const MYWIN_CIDA *cida = (const MYWIN_CIDA *)dropLock.GetPointer();
254   if (!cida)
255     return E_INVALIDARG;
256   if (cida->cidl == 0)
257   {
258     // is it posssible to have no selected items?
259     // it's unexpected case.
260     return E_INVALIDARG;
261   }
262   if (cida->cidl >= (blockSize - (UInt32)sizeof(MYWIN_CIDA)) / sizeof(UINT))
263     return E_INVALIDARG;
264   const UInt32 start = cida->cidl * (UInt32)sizeof(UINT) + (UInt32)sizeof(MYWIN_CIDA);
265 
266   STRRET strret;
267   CMyComPtr<IShellFolder> parentFolder;
268   {
269     const UINT offset = cida->aoffset[0];
270     if (offset < start || offset >= blockSize
271         // || offset != start
272         )
273       return E_INVALIDARG;
274 
275     CMyComPtr<IShellFolder> desktopFolder;
276     RINOK(::SHGetDesktopFolder(&desktopFolder))
277     if (!desktopFolder)
278       return E_FAIL;
279 
280     LPCITEMIDLIST const lpcItem = (LPCITEMIDLIST)(const void *)((const Byte *)cida + offset);
281 
282    #ifdef SHOW_DEBUG_SHELL
283     {
284       const HRESULT res = desktopFolder->GetDisplayNameOf(
285           lpcItem, SHGDN_FORPARSING, &strret);
286       if (res == S_OK && strret.uType == STRRET_WSTR)
287       {
288         ODS_U(strret.pOleStr)
289         /* if lpcItem is empty, the path will be
290              "C:\Users\user_name\Desktop"
291            if lpcItem is "My Computer" folder, the path will be
292              "::{20D04FE0-3AEA-1069-A2D8-08002B30309D}" */
293         CoTaskMemFree(strret.pOleStr);
294       }
295     }
296    #endif
297 
298     RINOK(desktopFolder->BindToObject(lpcItem,
299         NULL, IID_IShellFolder, (void **)&parentFolder))
300     if (!parentFolder)
301       return E_FAIL;
302   }
303 
304   names.ClearAndReserve(cida->cidl);
305   UString path;
306 
307   // for (int y = 0; y < 1; y++) // for debug
308   for (unsigned i = 1; i <= cida->cidl; i++)
309   {
310     const UINT offset = cida->aoffset[i];
311     if (offset < start || offset >= blockSize)
312       return E_INVALIDARG;
313     const void *p = (const Byte *)(const void *)cida + offset;
314     /* ITEMIDLIST of file can contain more than one SHITEMID item.
315        In win10 only SHGDN_FORPARSING returns path that contains
316        all path parts related to parts of ITEMIDLIST.
317        So we can use only SHGDN_FORPARSING here.
318        Don't use (SHGDN_INFOLDER)
319        Don't use (SHGDN_INFOLDER | SHGDN_FORPARSING)
320     */
321     RINOK(parentFolder->GetDisplayNameOf((LPCITEMIDLIST)p, SHGDN_FORPARSING, &strret))
322 
323     /*
324     // MSVC6 and old SDK do not support StrRetToStrW().
325     LPWSTR lpstr;
326     RINOK (StrRetToStrW(&strret, NULL, &lpstr))
327     ODS_U(lpstr)
328     path = lpstr;
329     CoTaskMemFree(lpstr);
330     */
331     if (strret.uType != STRRET_WSTR)
332       return E_INVALIDARG;
333     ODS_U(strret.pOleStr)
334     path = strret.pOleStr;
335     // the path could have super path prefix "\\\\?\\"
336     // we can remove super path prefix here, if we don't need that prefix
337   #ifdef Z7_LONG_PATH
338     // we remove super prefix, if we can work without that prefix
339     NFile::NName::If_IsSuperPath_RemoveSuperPrefix(path);
340   #endif
341     names.AddInReserved(path);
342     CoTaskMemFree(strret.pOleStr);
343   }
344 
345   ODS_(Print_Number(cida->cidl, "CFSTR_SHELLIDLIST END"))
346   return S_OK;
347 }
348 
349 
DataObject_GetData_HDROP_or_IDLIST_Names(IDataObject *dataObject, UStringVector &paths)350 HRESULT DataObject_GetData_HDROP_or_IDLIST_Names(IDataObject *dataObject, UStringVector &paths)
351 {
352   ODS("-- DataObject_GetData_HDROP_or_IDLIST_Names START")
353   HRESULT hres = NShell::DataObject_GetData_HDROP_Names(dataObject, paths);
354   // if (hres == HRESULT_FROM_WIN32(ERROR_INSUFFICIENT_BUFFER))
355   if (hres != S_OK)
356   {
357     ODS("-- DataObject_GetData_IDLIST START")
358     // for (int y = 0; y < 10000; y++) // for debug
359     hres = NShell::DataObject_GetData_IDLIST(dataObject, paths);
360   }
361   ODS("-- DataObject_GetData_HDROP_or_IDLIST_Names END")
362   return hres;
363 }
364 
365 
366 
367 // #if (NTDDI_VERSION >= NTDDI_VISTA)
368 typedef struct
369 {
370   UINT cItems;                    // number of items in rgdwFileAttributes array
371   DWORD dwSumFileAttributes;      // all of the attributes ORed together
372   DWORD dwProductFileAttributes;  // all of the attributes ANDed together
373   DWORD rgdwFileAttributes[1];    // array
374 } MYWIN_FILE_ATTRIBUTES_ARRAY;
375 
376 #define MYWIN_CFSTR_FILE_ATTRIBUTES_ARRAY  TEXT("File Attributes Array")
377 
DataObject_GetData_FILE_ATTRS(IDataObject *dataObject, CFileAttribs &attribs)378 HRESULT DataObject_GetData_FILE_ATTRS(IDataObject *dataObject, CFileAttribs &attribs)
379 {
380   attribs.Clear();
381   NCOM::CStgMedium medium;
382   RINOK(DataObject_GetData_HGLOBAL(dataObject, (CLIPFORMAT)
383       RegisterClipboardFormat(MYWIN_CFSTR_FILE_ATTRIBUTES_ARRAY), medium))
384   const size_t blockSize = GlobalSize(medium.hGlobal);
385   if (blockSize < sizeof(MYWIN_FILE_ATTRIBUTES_ARRAY))
386     return E_INVALIDARG;
387   NMemory::CGlobalLock dropLock(medium.hGlobal);
388   const MYWIN_FILE_ATTRIBUTES_ARRAY *faa = (const MYWIN_FILE_ATTRIBUTES_ARRAY *)dropLock.GetPointer();
389   if (!faa)
390     return E_INVALIDARG;
391   const unsigned numFiles = faa->cItems;
392   if (numFiles == 0)
393   {
394     // is it posssible to have empty array here?
395     return E_INVALIDARG;
396   }
397   if ((blockSize - (sizeof(MYWIN_FILE_ATTRIBUTES_ARRAY) - sizeof(DWORD)))
398       / sizeof(DWORD) != numFiles)
399     return E_INVALIDARG;
400   // attribs.Sum = faa->dwSumFileAttributes;
401   // attribs.Product = faa->dwProductFileAttributes;
402   // attribs.Vals.SetFromArray(faa->rgdwFileAttributes, numFiles);
403   // attribs.IsDirVector.ClearAndSetSize(numFiles);
404 
405   if ((faa->dwSumFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0)
406   {
407     /* in win10: if selected items are volumes (c:\, d:\ ..) in  My Compter,
408        all items have FILE_ATTRIBUTE_DIRECTORY attribute
409        ntfs volume also have FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM
410        udf volume: FILE_ATTRIBUTE_READONLY
411        dvd-rom device: (-1) : all bits are set
412     */
413     const DWORD *attr = faa->rgdwFileAttributes;
414     // DWORD product = (UInt32)0 - 1, sum = 0;
415     for (unsigned i = 0; i < numFiles; i++)
416     {
417       if (attr[i] & FILE_ATTRIBUTE_DIRECTORY)
418       {
419         // attribs.ThereAreDirs = true;
420         attribs.FirstDirIndex = (int)i;
421         break;
422       }
423       // attribs.IsDirVector[i] = (attr[i] & FILE_ATTRIBUTE_DIRECTORY) != 0;
424       // product &= v;
425       // sum |= v;
426     }
427     // ODS_(Print_Number(product, "Product calc FILE_ATTRIBUTES_ARRAY ==== DataObject_GetData_HDROP_Names"))
428     // ODS_(Print_Number(sum, "Sum calc FILE_ATTRIBUTES_ARRAY ==== DataObject_GetData_HDROP_Names"))
429   }
430   // ODS_(Print_Number(attribs.Product, "Product FILE_ATTRIBUTES_ARRAY ==== DataObject_GetData_HDROP_Names"))
431   // ODS_(Print_Number(attribs.Sum, "Sum FILE_ATTRIBUTES_ARRAY ==== DataObject_GetData_HDROP_Names"))
432   ODS_(Print_Number(numFiles, "FILE_ATTRIBUTES_ARRAY ==== DataObject_GetData_HDROP_Names"))
433   return S_OK;
434 }
435 
436 
437 /////////////////////////////
438 // CDrop
439 
440 /*
441   win10:
442   DragQueryFile() implementation code is not effective because
443   there is no pointer inside DROP internal file list, so
444   DragQueryFile(fileIndex) runs all names in range [0, fileIndex].
445   DragQueryFile(,, buf, bufSize)
446   if (buf == NULL) by spec
447   {
448     returns value is the required size
449     in characters, of the buffer, not including the terminating null character
450     tests show that if (bufSize == 0), then it also returns  required size.
451   }
452   if (bufSize != NULL)
453   {
454     returns: the count of the characters copied, not including null character.
455     win10: null character is also  copied at position buf[ret_count];
456   }
457 */
458 
459 /*
460 void CDrop::Attach(HDROP object)
461 {
462   Free();
463   m_Object = object;
464   m_Assigned = true;
465 }
466 
467 void CDrop::Free()
468 {
469   if (m_MustBeFinished && m_Assigned)
470     Finish();
471   m_Assigned = false;
472 }
473 
474 UINT CDrop::QueryCountOfFiles()
475 {
476   return QueryFile(0xFFFFFFFF, (LPTSTR)NULL, 0);
477 }
478 
479 void CDrop::QueryFileName(UINT fileIndex, UString &fileName)
480 {
481   #ifndef _UNICODE
482   if (!g_IsNT)
483   {
484     AString fileNameA;
485     const UINT len = QueryFile(fileIndex, (LPTSTR)NULL, 0);
486     const UINT numCopied = QueryFile(fileIndex, fileNameA.GetBuf(len + 2), len + 2);
487     fileNameA.ReleaseBuf_CalcLen(len);
488     if (numCopied != len)
489       throw 20221223;
490     fileName = GetUnicodeString(fileNameA);
491   }
492   else
493   #endif
494   {
495     // kReserve must be >= 3 for additional buffer size
496     //   safety and for optimal performance
497     const unsigned kReserve = 3;
498     {
499       unsigned len = 0;
500       wchar_t *buf = fileName.GetBuf_GetMaxAvail(len);
501       if (len >= kReserve)
502       {
503         const UINT numCopied = QueryFile(fileIndex, buf, len);
504         if (numCopied < len - 1)
505         {
506           // (numCopied < len - 1) case means that it have copied full string.
507           fileName.ReleaseBuf_CalcLen(numCopied);
508           return;
509         }
510       }
511     }
512     const UINT len = QueryFile(fileIndex, (LPWSTR)NULL, 0);
513     const UINT numCopied = QueryFile(fileIndex,
514         fileName.GetBuf(len + kReserve), len + kReserve);
515     fileName.ReleaseBuf_CalcLen(len);
516     if (numCopied != len)
517       throw 20221223;
518   }
519 }
520 
521 
522 void CDrop::QueryFileNames(UStringVector &fileNames)
523 {
524   UINT numFiles = QueryCountOfFiles();
525 
526   Print_Number(numFiles, "\n====== CDrop::QueryFileNames START ===== \n");
527 
528   fileNames.ClearAndReserve(numFiles);
529   UString s;
530   for (UINT i = 0; i < numFiles; i++)
531   {
532     QueryFileName(i, s);
533     if (!s.IsEmpty())
534       fileNames.AddInReserved(s);
535   }
536   Print_Number(numFiles, "\n====== CDrop::QueryFileNames END ===== \n");
537 }
538 */
539 
540 
541 // #if (NTDDI_VERSION >= NTDDI_VISTA)
542 // SHGetPathFromIDListEx returns a win32 file system path for the item in the name space.
543 typedef int Z7_WIN_GPFIDL_FLAGS;
544 
545 extern "C" {
546 typedef BOOL (WINAPI * Func_SHGetPathFromIDListW)(LPCITEMIDLIST pidl, LPWSTR pszPath);
547 typedef BOOL (WINAPI * Func_SHGetPathFromIDListEx)(LPCITEMIDLIST pidl, PWSTR pszPath, DWORD cchPath, Z7_WIN_GPFIDL_FLAGS uOpts);
548 }
549 
550 #ifndef _UNICODE
551 
GetPathFromIDList(LPCITEMIDLIST itemIDList, AString &path)552 bool GetPathFromIDList(LPCITEMIDLIST itemIDList, AString &path)
553 {
554   path.Empty();
555   const unsigned len = MAX_PATH + 16;
556   const bool result = BOOLToBool(::SHGetPathFromIDList(itemIDList, path.GetBuf(len)));
557   path.ReleaseBuf_CalcLen(len);
558   return result;
559 }
560 
561 #endif
562 
GetPathFromIDList(LPCITEMIDLIST itemIDList, UString &path)563 bool GetPathFromIDList(LPCITEMIDLIST itemIDList, UString &path)
564 {
565   path.Empty();
566   unsigned len = MAX_PATH + 16;
567 
568 #ifdef _UNICODE
569   bool result = BOOLToBool(::SHGetPathFromIDList(itemIDList, path.GetBuf(len)));
570 #else
571   const
572   Func_SHGetPathFromIDListW
573        shGetPathFromIDListW = Z7_GET_PROC_ADDRESS(
574   Func_SHGetPathFromIDListW, ::GetModuleHandleW(L"shell32.dll"),
575       "SHGetPathFromIDListW");
576   if (!shGetPathFromIDListW)
577     return false;
578   bool result = BOOLToBool(shGetPathFromIDListW(itemIDList, path.GetBuf(len)));
579 #endif
580 
581   if (!result)
582   {
583     ODS("==== GetPathFromIDList() SHGetPathFromIDList() returned false")
584     /* for long path we need SHGetPathFromIDListEx().
585       win10: SHGetPathFromIDListEx() for long path returns path with
586              with super path prefix "\\\\?\\". */
587     const
588     Func_SHGetPathFromIDListEx
589     func_SHGetPathFromIDListEx = Z7_GET_PROC_ADDRESS(
590     Func_SHGetPathFromIDListEx, ::GetModuleHandleW(L"shell32.dll"),
591         "SHGetPathFromIDListEx");
592     if (func_SHGetPathFromIDListEx)
593     {
594       ODS("==== GetPathFromIDList() (SHGetPathFromIDListEx)")
595       do
596       {
597         len *= 4;
598         result = BOOLToBool(func_SHGetPathFromIDListEx(itemIDList, path.GetBuf(len), len, 0));
599         if (result)
600           break;
601       }
602       while (len <= (1 << 16));
603     }
604   }
605 
606   path.ReleaseBuf_CalcLen(len);
607   return result;
608 }
609 
610 #endif
611 
612 #ifdef UNDER_CE
613 
BrowseForFolder(LPBROWSEINFO, CSysString)614 bool BrowseForFolder(LPBROWSEINFO, CSysString)
615 {
616   return false;
617 }
618 
BrowseForFolder(HWND, LPCTSTR, UINT, LPCTSTR, CSysString &)619 bool BrowseForFolder(HWND, LPCTSTR, UINT, LPCTSTR, CSysString &)
620 {
621   return false;
622 }
623 
BrowseForFolder(HWND , LPCTSTR , LPCTSTR , CSysString & )624 bool BrowseForFolder(HWND /* owner */, LPCTSTR /* title */,
625     LPCTSTR /* initialFolder */, CSysString & /* resultPath */)
626 {
627   /*
628   // SHBrowseForFolder doesn't work before CE 6.0 ?
629   if (GetProcAddress(LoadLibrary(L"ceshell.dll", L"SHBrowseForFolder") == 0)
630     MessageBoxW(0, L"no", L"", 0);
631   else
632     MessageBoxW(0, L"yes", L"", 0);
633   */
634   /*
635   UString s = "all files";
636   s += " (*.*)";
637   return MyGetOpenFileName(owner, title, initialFolder, s, resultPath, true);
638   */
639   return false;
640 }
641 
642 #else
643 
644 /* win10: SHBrowseForFolder() doesn't support long paths,
645    even if long path suppport is enabled in registry and in manifest.
646    and SHBrowseForFolder() doesn't support super path prefix "\\\\?\\". */
647 
BrowseForFolder(LPBROWSEINFO browseInfo, CSysString &resultPath)648 bool BrowseForFolder(LPBROWSEINFO browseInfo, CSysString &resultPath)
649 {
650   resultPath.Empty();
651   NWindows::NCOM::CComInitializer comInitializer;
652   LPITEMIDLIST itemIDList = ::SHBrowseForFolder(browseInfo);
653   if (!itemIDList)
654     return false;
655   CItemIDList itemIDListHolder;
656   itemIDListHolder.Attach(itemIDList);
657   return GetPathFromIDList(itemIDList, resultPath);
658 }
659 
660 
BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM , LPARAM data)661 static int CALLBACK BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM /* lp */, LPARAM data)
662 {
663   #ifndef UNDER_CE
664   switch (uMsg)
665   {
666     case BFFM_INITIALIZED:
667     {
668       SendMessage(hwnd, BFFM_SETSELECTION, TRUE, data);
669       break;
670     }
671     /*
672     case BFFM_SELCHANGED:
673     {
674       TCHAR dir[MAX_PATH];
675       if (::SHGetPathFromIDList((LPITEMIDLIST) lp , dir))
676         SendMessage(hwnd, BFFM_SETSTATUSTEXT, 0, (LPARAM)dir);
677       else
678         SendMessage(hwnd, BFFM_SETSTATUSTEXT, 0, (LPARAM)TEXT(""));
679       break;
680     }
681     */
682     default:
683       break;
684   }
685   #endif
686   return 0;
687 }
688 
689 
BrowseForFolder(HWND owner, LPCTSTR title, UINT ulFlags, LPCTSTR initialFolder, CSysString &resultPath)690 static bool BrowseForFolder(HWND owner, LPCTSTR title, UINT ulFlags,
691     LPCTSTR initialFolder, CSysString &resultPath)
692 {
693   CSysString displayName;
694   BROWSEINFO browseInfo;
695   browseInfo.hwndOwner = owner;
696   browseInfo.pidlRoot = NULL;
697 
698   // there are Unicode/Astring problems in some WinCE SDK ?
699   /*
700   #ifdef UNDER_CE
701   browseInfo.pszDisplayName = (LPSTR)displayName.GetBuf(MAX_PATH);
702   browseInfo.lpszTitle = (LPCSTR)title;
703   #else
704   */
705   browseInfo.pszDisplayName = displayName.GetBuf(MAX_PATH);
706   browseInfo.lpszTitle = title;
707   // #endif
708   browseInfo.ulFlags = ulFlags;
709   browseInfo.lpfn = initialFolder ? BrowseCallbackProc : NULL;
710   browseInfo.lParam = (LPARAM)initialFolder;
711   return BrowseForFolder(&browseInfo, resultPath);
712 }
713 
714 #ifdef Z7_OLD_WIN_SDK
715 // ShlObj.h:
716 #ifndef BIF_NEWDIALOGSTYLE
717 #define BIF_NEWDIALOGSTYLE     0x0040
718 #endif
719 #endif
720 
BrowseForFolder(HWND owner, LPCTSTR title, LPCTSTR initialFolder, CSysString &resultPath)721 bool BrowseForFolder(HWND owner, LPCTSTR title,
722     LPCTSTR initialFolder, CSysString &resultPath)
723 {
724   return BrowseForFolder(owner, title,
725       #ifndef UNDER_CE
726       BIF_NEWDIALOGSTYLE |
727       #endif
728       BIF_RETURNONLYFSDIRS | BIF_STATUSTEXT, initialFolder, resultPath);
729   // BIF_STATUSTEXT; BIF_USENEWUI   (Version 5.0)
730 }
731 
732 #ifndef _UNICODE
733 
734 extern "C" {
735 typedef LPITEMIDLIST (WINAPI * Func_SHBrowseForFolderW)(LPBROWSEINFOW lpbi);
736 }
737 
BrowseForFolder(LPBROWSEINFOW browseInfo, UString &resultPath)738 static bool BrowseForFolder(LPBROWSEINFOW browseInfo, UString &resultPath)
739 {
740   NWindows::NCOM::CComInitializer comInitializer;
741   const
742   Func_SHBrowseForFolderW
743      f_SHBrowseForFolderW = Z7_GET_PROC_ADDRESS(
744   Func_SHBrowseForFolderW, ::GetModuleHandleW(L"shell32.dll"),
745       "SHBrowseForFolderW");
746   if (!f_SHBrowseForFolderW)
747     return false;
748   LPITEMIDLIST itemIDList = f_SHBrowseForFolderW(browseInfo);
749   if (!itemIDList)
750     return false;
751   CItemIDList itemIDListHolder;
752   itemIDListHolder.Attach(itemIDList);
753   return GetPathFromIDList(itemIDList, resultPath);
754 }
755 
756 static
BrowseCallbackProc2(HWND hwnd, UINT uMsg, LPARAM , LPARAM data)757 int CALLBACK BrowseCallbackProc2(HWND hwnd, UINT uMsg, LPARAM /* lp */, LPARAM data)
758 {
759   switch (uMsg)
760   {
761     case BFFM_INITIALIZED:
762     {
763       SendMessageW(hwnd, BFFM_SETSELECTIONW, TRUE, data);
764       break;
765     }
766     /*
767     case BFFM_SELCHANGED:
768     {
769       wchar_t dir[MAX_PATH * 2];
770 
771       if (shGetPathFromIDListW((LPITEMIDLIST)lp , dir))
772         SendMessageW(hwnd, BFFM_SETSTATUSTEXTW, 0, (LPARAM)dir);
773       else
774         SendMessageW(hwnd, BFFM_SETSTATUSTEXTW, 0, (LPARAM)L"");
775       break;
776     }
777     */
778     default:
779       break;
780   }
781   return 0;
782 }
783 
784 
BrowseForFolder(HWND owner, LPCWSTR title, UINT ulFlags, LPCWSTR initialFolder, UString &resultPath)785 static bool BrowseForFolder(HWND owner, LPCWSTR title, UINT ulFlags,
786     LPCWSTR initialFolder, UString &resultPath)
787 {
788   UString displayName;
789   BROWSEINFOW browseInfo;
790   browseInfo.hwndOwner = owner;
791   browseInfo.pidlRoot = NULL;
792   browseInfo.pszDisplayName = displayName.GetBuf(MAX_PATH);
793   browseInfo.lpszTitle = title;
794   browseInfo.ulFlags = ulFlags;
795   browseInfo.lpfn = initialFolder ? BrowseCallbackProc2 : NULL;
796   browseInfo.lParam = (LPARAM)initialFolder;
797   return BrowseForFolder(&browseInfo, resultPath);
798 }
799 
BrowseForFolder(HWND owner, LPCWSTR title, LPCWSTR initialFolder, UString &resultPath)800 bool BrowseForFolder(HWND owner, LPCWSTR title, LPCWSTR initialFolder, UString &resultPath)
801 {
802   if (g_IsNT)
803     return BrowseForFolder(owner, title,
804       BIF_NEWDIALOGSTYLE | BIF_RETURNONLYFSDIRS
805       //  | BIF_STATUSTEXT // This flag is not supported when BIF_NEWDIALOGSTYLE is specified.
806       , initialFolder, resultPath);
807   // BIF_STATUSTEXT; BIF_USENEWUI   (Version 5.0)
808   CSysString s;
809   bool res = BrowseForFolder(owner, GetSystemString(title),
810       BIF_NEWDIALOGSTYLE | BIF_RETURNONLYFSDIRS
811       // | BIF_STATUSTEXT  // This flag is not supported when BIF_NEWDIALOGSTYLE is specified.
812       , GetSystemString(initialFolder), s);
813   resultPath = GetUnicodeString(s);
814   return res;
815 }
816 
817 #endif
818 
819 #endif
820 
821 }}
822