1 /*
2  *    Virtual Workplace folder
3  *
4  *    Copyright 1997                Marcus Meissner
5  *    Copyright 1998, 1999, 2002    Juergen Schmied
6  *    Copyright 2009                Andrew Hill
7  *    Copyright 2017-2018           Katayama Hirofumi MZ
8  *
9  * This library is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU Lesser General Public
11  * License as published by the Free Software Foundation; either
12  * version 2.1 of the License, or (at your option) any later version.
13  *
14  * This library is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * Lesser General Public License for more details.
18  *
19  * You should have received a copy of the GNU Lesser General Public
20  * License along with this library; if not, write to the Free Software
21  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
22  */
23 
24 #include <precomp.h>
25 
26 WINE_DEFAULT_DEBUG_CHANNEL (shell);
27 
28 /*
29 CDrivesFolder should create a CRegFolder to represent the virtual items that exist only in
30 the registry. The CRegFolder is aggregated by the CDrivesFolder.
31 The CDrivesFolderEnum class should enumerate only drives on the system. Since the CRegFolder
32 implementation of IShellFolder::EnumObjects enumerates the virtual items, the
33 CDrivesFolderEnum is only responsible for returning the physical items.
34 
35 2. At least on my XP system, the drive pidls returned are of type PT_DRIVE1, not PT_DRIVE
36 3. The parsing name returned for my computer is incorrect. It should be "My Computer"
37 */
38 
39 static int iDriveIconIds[7] = { IDI_SHELL_DRIVE,       /* DRIVE_UNKNOWN */
40                                 IDI_SHELL_CDROM,       /* DRIVE_NO_ROOT_DIR*/
41                                 IDI_SHELL_3_14_FLOPPY, /* DRIVE_REMOVABLE*/
42                                 IDI_SHELL_DRIVE,       /* DRIVE_FIXED*/
43                                 IDI_SHELL_NETDRIVE,    /* DRIVE_REMOTE*/
44                                 IDI_SHELL_CDROM,       /* DRIVE_CDROM*/
45                                 IDI_SHELL_RAMDISK      /* DRIVE_RAMDISK*/
46                                 };
47 
48 static int iDriveTypeIds[7] = { IDS_DRIVE_FIXED,       /* DRIVE_UNKNOWN */
49                                 IDS_DRIVE_FIXED,       /* DRIVE_NO_ROOT_DIR*/
50                                 IDS_DRIVE_FLOPPY,      /* DRIVE_REMOVABLE*/
51                                 IDS_DRIVE_FIXED,       /* DRIVE_FIXED*/
52                                 IDS_DRIVE_NETWORK,     /* DRIVE_REMOTE*/
53                                 IDS_DRIVE_CDROM,       /* DRIVE_CDROM*/
54                                 IDS_DRIVE_FIXED        /* DRIVE_RAMDISK*/
55                                 };
56 
57 /***********************************************************************
58 *   IShellFolder implementation
59 */
60 
61 #define RETRY_COUNT 3
62 #define RETRY_SLEEP 250
63 static BOOL TryToLockOrUnlockDrive(HANDLE hDrive, BOOL bLock)
64 {
65     DWORD dwError, dwBytesReturned;
66     DWORD dwCode = (bLock ? FSCTL_LOCK_VOLUME : FSCTL_UNLOCK_VOLUME);
67     for (DWORD i = 0; i < RETRY_COUNT; ++i)
68     {
69         if (DeviceIoControl(hDrive, dwCode, NULL, 0, NULL, 0, &dwBytesReturned, NULL))
70             return TRUE;
71 
72         dwError = GetLastError();
73         if (dwError == ERROR_INVALID_FUNCTION)
74             break; /* don't sleep if function is not implemented */
75 
76         Sleep(RETRY_SLEEP);
77     }
78     SetLastError(dwError);
79     return FALSE;
80 }
81 
82 // NOTE: See also https://support.microsoft.com/en-us/help/165721/how-to-ejecting-removable-media-in-windows-nt-windows-2000-windows-xp
83 static BOOL DoEjectDrive(const WCHAR *physical, UINT nDriveType, INT *pnStringID)
84 {
85     /* GENERIC_WRITE isn't needed for umount */
86     DWORD dwAccessMode = GENERIC_READ;
87     DWORD dwShareMode = FILE_SHARE_READ | FILE_SHARE_WRITE;
88 
89     HANDLE hDrive = CreateFile(physical, dwAccessMode, dwShareMode, 0, OPEN_EXISTING, 0, NULL);
90     if (hDrive == INVALID_HANDLE_VALUE)
91         return FALSE;
92 
93     BOOL bResult, bNeedUnlock = FALSE;
94     DWORD dwBytesReturned, dwError = NO_ERROR;
95     PREVENT_MEDIA_REMOVAL removal;
96     do
97     {
98         bResult = TryToLockOrUnlockDrive(hDrive, TRUE);
99         if (!bResult)
100         {
101             dwError = GetLastError();
102             *pnStringID = IDS_CANTLOCKVOLUME; /* Unable to lock volume */
103             break;
104         }
105         bResult = DeviceIoControl(hDrive, FSCTL_DISMOUNT_VOLUME, NULL, 0, NULL, 0, &dwBytesReturned, NULL);
106         if (!bResult)
107         {
108             dwError = GetLastError();
109             *pnStringID = IDS_CANTDISMOUNTVOLUME; /* Unable to dismount volume */
110             bNeedUnlock = TRUE;
111             break;
112         }
113         removal.PreventMediaRemoval = FALSE;
114         bResult = DeviceIoControl(hDrive, IOCTL_STORAGE_MEDIA_REMOVAL, &removal, sizeof(removal), NULL,
115                                   0, &dwBytesReturned, NULL);
116         if (!bResult)
117         {
118             *pnStringID = IDS_CANTEJECTMEDIA; /* Unable to eject media */
119             dwError = GetLastError();
120             bNeedUnlock = TRUE;
121             break;
122         }
123         bResult = DeviceIoControl(hDrive, IOCTL_STORAGE_EJECT_MEDIA, NULL, 0, NULL, 0, &dwBytesReturned, NULL);
124         if (!bResult)
125         {
126             *pnStringID = IDS_CANTEJECTMEDIA; /* Unable to eject media */
127             dwError = GetLastError();
128             bNeedUnlock = TRUE;
129             break;
130         }
131     } while (0);
132 
133     if (bNeedUnlock)
134     {
135         TryToLockOrUnlockDrive(hDrive, FALSE);
136     }
137 
138     CloseHandle(hDrive);
139 
140     SetLastError(dwError);
141     return bResult;
142 }
143 
144 HRESULT CALLBACK DrivesContextMenuCallback(IShellFolder *psf,
145                                            HWND         hwnd,
146                                            IDataObject  *pdtobj,
147                                            UINT         uMsg,
148                                            WPARAM       wParam,
149                                            LPARAM       lParam)
150 {
151     if (uMsg != DFM_MERGECONTEXTMENU && uMsg != DFM_INVOKECOMMAND)
152         return S_OK;
153 
154     PIDLIST_ABSOLUTE pidlFolder;
155     PUITEMID_CHILD *apidl;
156     UINT cidl;
157     UINT nDriveType;
158     DWORD dwFlags;
159     HRESULT hr = SH_GetApidlFromDataObject(pdtobj, &pidlFolder, &apidl, &cidl);
160     if (FAILED_UNEXPECTEDLY(hr))
161         return hr;
162 
163     char szDrive[8] = {0};
164     if (!_ILGetDrive(apidl[0], szDrive, sizeof(szDrive)))
165     {
166         ERR("pidl is not a drive\n");
167         SHFree(pidlFolder);
168         _ILFreeaPidl(apidl, cidl);
169         return E_FAIL;
170     }
171     nDriveType = GetDriveTypeA(szDrive);
172     GetVolumeInformationA(szDrive, NULL, 0, NULL, NULL, &dwFlags, NULL, 0);
173 
174 // custom command IDs
175 #define CMDID_FORMAT        1
176 #define CMDID_EJECT         2
177 #define CMDID_DISCONNECT    3
178 
179     if (uMsg == DFM_MERGECONTEXTMENU)
180     {
181         QCMINFO *pqcminfo = (QCMINFO *)lParam;
182 
183         UINT idCmdFirst = pqcminfo->idCmdFirst;
184         if (!(dwFlags & FILE_READ_ONLY_VOLUME) && nDriveType != DRIVE_REMOTE)
185         {
186             /* add separator and Format */
187             UINT idCmd = idCmdFirst + CMDID_FORMAT;
188             _InsertMenuItemW(pqcminfo->hmenu, pqcminfo->indexMenu++, TRUE, 0, MFT_SEPARATOR, NULL, 0);
189             _InsertMenuItemW(pqcminfo->hmenu, pqcminfo->indexMenu++, TRUE, idCmd, MFT_STRING, MAKEINTRESOURCEW(IDS_FORMATDRIVE), MFS_ENABLED);
190         }
191         if (nDriveType == DRIVE_REMOVABLE || nDriveType == DRIVE_CDROM)
192         {
193             /* add separator and Eject */
194             UINT idCmd = idCmdFirst + CMDID_EJECT;
195             _InsertMenuItemW(pqcminfo->hmenu, pqcminfo->indexMenu++, TRUE, 0, MFT_SEPARATOR, NULL, 0);
196             _InsertMenuItemW(pqcminfo->hmenu, pqcminfo->indexMenu++, TRUE, idCmd, MFT_STRING, MAKEINTRESOURCEW(IDS_EJECT), MFS_ENABLED);
197         }
198         if (nDriveType == DRIVE_REMOTE)
199         {
200             /* add separator and Disconnect */
201             UINT idCmd = idCmdFirst + CMDID_DISCONNECT;
202             _InsertMenuItemW(pqcminfo->hmenu, pqcminfo->indexMenu++, TRUE, 0, MFT_SEPARATOR, NULL, 0);
203             _InsertMenuItemW(pqcminfo->hmenu, pqcminfo->indexMenu++, TRUE, idCmd, MFT_STRING, MAKEINTRESOURCEW(IDS_DISCONNECT), MFS_ENABLED);
204         }
205 
206         pqcminfo->idCmdFirst += 3;
207     }
208     else if (uMsg == DFM_INVOKECOMMAND)
209     {
210         WCHAR wszBuf[4] = L"A:\\";
211         wszBuf[0] = (WCHAR)szDrive[0];
212 
213         INT nStringID = 0;
214         DWORD dwError = NO_ERROR;
215 
216         if (wParam == DFM_CMD_PROPERTIES)
217         {
218             hr = SH_ShowDriveProperties(wszBuf, pidlFolder, apidl);
219             if (FAILED(hr))
220             {
221                 dwError = ERROR_CAN_NOT_COMPLETE;
222                 nStringID = IDS_CANTSHOWPROPERTIES;
223             }
224         }
225         else
226         {
227             if (wParam == CMDID_FORMAT)
228             {
229                 /* do format */
230                 DWORD dwRet = SHFormatDrive(hwnd, szDrive[0] - 'A', SHFMT_ID_DEFAULT, 0);
231                 switch (dwRet)
232                 {
233                 case SHFMT_ERROR: case SHFMT_CANCEL: case SHFMT_NOFORMAT:
234                     hr = E_FAIL;
235                     break;
236                 }
237             }
238             else if (wParam == CMDID_EJECT)
239             {
240                 /* do eject */
241                 WCHAR physical[10];
242                 wsprintfW(physical, _T("\\\\.\\%c:"), szDrive[0]);
243 
244                 if (DoEjectDrive(physical, nDriveType, &nStringID))
245                 {
246                     SHChangeNotify(SHCNE_MEDIAREMOVED, SHCNF_PATHW | SHCNF_FLUSHNOWAIT, wszBuf, NULL);
247                 }
248                 else
249                 {
250                     dwError = GetLastError();
251                 }
252             }
253             else if (wParam == CMDID_DISCONNECT)
254             {
255                 /* do disconnect */
256                 wszBuf[2] = UNICODE_NULL;
257                 dwError = WNetCancelConnection2W(wszBuf, 0, FALSE);
258                 if (dwError == NO_ERROR)
259                 {
260                     SHChangeNotify(SHCNE_DRIVEREMOVED, SHCNF_PATHW | SHCNF_FLUSHNOWAIT, wszBuf, NULL);
261                 }
262                 else
263                 {
264                     nStringID = IDS_CANTDISCONNECT;
265                 }
266             }
267         }
268 
269         if (nStringID != 0)
270         {
271             /* show error message */
272             WCHAR szFormat[128], szMessage[128];
273             LoadStringW(shell32_hInstance, nStringID, szFormat, _countof(szFormat));
274             wsprintfW(szMessage, szFormat, dwError);
275             MessageBoxW(hwnd, szMessage, NULL, MB_ICONERROR);
276         }
277     }
278 
279     SHFree(pidlFolder);
280     _ILFreeaPidl(apidl, cidl);
281 
282     return hr;
283 }
284 
285 HRESULT CDrivesContextMenu_CreateInstance(PCIDLIST_ABSOLUTE pidlFolder,
286                                           HWND hwnd,
287                                           UINT cidl,
288                                           PCUITEMID_CHILD_ARRAY apidl,
289                                           IShellFolder *psf,
290                                           IContextMenu **ppcm)
291 {
292     HKEY hKeys[2];
293     UINT cKeys = 0;
294     AddClassKeyToArray(L"Drive", hKeys, &cKeys);
295     AddClassKeyToArray(L"Folder", hKeys, &cKeys);
296 
297     return CDefFolderMenu_Create2(pidlFolder, hwnd, cidl, apidl, psf, DrivesContextMenuCallback, cKeys, hKeys, ppcm);
298 }
299 
300 static HRESULT
301 getIconLocationForDrive(IShellFolder *psf, PCITEMID_CHILD pidl, UINT uFlags,
302                         LPWSTR szIconFile, UINT cchMax, int *piIndex, UINT *pwFlags)
303 {
304     WCHAR wszPath[MAX_PATH];
305     WCHAR wszAutoRunInfPath[MAX_PATH];
306     WCHAR wszValue[MAX_PATH], wszTemp[MAX_PATH];
307     static const WCHAR wszAutoRunInf[] = { 'a','u','t','o','r','u','n','.','i','n','f',0 };
308     static const WCHAR wszAutoRun[] = { 'a','u','t','o','r','u','n',0 };
309 
310     // get path
311     if (!ILGetDisplayNameExW(psf, pidl, wszPath, 0))
312         return E_FAIL;
313     if (!PathIsDirectoryW(wszPath))
314         return E_FAIL;
315 
316     // build the full path of autorun.inf
317     StringCchCopyW(wszAutoRunInfPath, _countof(wszAutoRunInfPath), wszPath);
318     PathAppendW(wszAutoRunInfPath, wszAutoRunInf);
319 
320     // autorun.inf --> wszValue
321     if (GetPrivateProfileStringW(wszAutoRun, L"icon", NULL, wszValue, _countof(wszValue),
322                                  wszAutoRunInfPath) && wszValue[0] != 0)
323     {
324         // wszValue --> wszTemp
325         ExpandEnvironmentStringsW(wszValue, wszTemp, _countof(wszTemp));
326 
327         // parse the icon location
328         *piIndex = PathParseIconLocationW(wszTemp);
329 
330         // wszPath + wszTemp --> wszPath
331         if (PathIsRelativeW(wszTemp))
332             PathAppendW(wszPath, wszTemp);
333         else
334             StringCchCopyW(wszPath, _countof(wszPath), wszTemp);
335 
336         // wszPath --> szIconFile
337         GetFullPathNameW(wszPath, cchMax, szIconFile, NULL);
338 
339         return S_OK;
340     }
341 
342     return E_FAIL;
343 }
344 
345 HRESULT CDrivesExtractIcon_CreateInstance(IShellFolder * psf, LPCITEMIDLIST pidl, REFIID riid, LPVOID * ppvOut)
346 {
347     CComPtr<IDefaultExtractIconInit> initIcon;
348     HRESULT hr = SHCreateDefaultExtractIcon(IID_PPV_ARG(IDefaultExtractIconInit, &initIcon));
349     if (FAILED_UNEXPECTEDLY(hr))
350         return hr;
351 
352     CHAR* pszDrive = _ILGetDataPointer(pidl)->u.drive.szDriveName;
353     UINT DriveType = GetDriveTypeA(pszDrive);
354     if (DriveType > DRIVE_RAMDISK)
355         DriveType = DRIVE_FIXED;
356 
357     WCHAR wTemp[MAX_PATH];
358     int icon_idx;
359     UINT flags = 0;
360     if ((DriveType == DRIVE_FIXED || DriveType == DRIVE_UNKNOWN) &&
361         (HCR_GetIconW(L"Drive", wTemp, NULL, MAX_PATH, &icon_idx)))
362     {
363         initIcon->SetNormalIcon(wTemp, icon_idx);
364     }
365     else if (SUCCEEDED(getIconLocationForDrive(psf, pidl, 0, wTemp, _countof(wTemp),
366                                                &icon_idx, &flags)))
367     {
368         initIcon->SetNormalIcon(wTemp, icon_idx);
369     }
370     else
371     {
372         icon_idx = iDriveIconIds[DriveType];
373         initIcon->SetNormalIcon(swShell32Name, -icon_idx);
374     }
375 
376     return initIcon->QueryInterface(riid, ppvOut);
377 }
378 
379 class CDrivesFolderEnum :
380     public CEnumIDListBase
381 {
382     public:
383         HRESULT WINAPI Initialize(HWND hwndOwner, DWORD dwFlags, IEnumIDList* pRegEnumerator)
384         {
385             /* enumerate the folders */
386             if (dwFlags & SHCONTF_FOLDERS)
387             {
388                 WCHAR wszDriveName[] = {'A', ':', '\\', '\0'};
389                 DWORD dwDrivemap = GetLogicalDrives();
390 
391                 while (wszDriveName[0] <= 'Z')
392                 {
393                     if(dwDrivemap & 0x00000001L)
394                         AddToEnumList(_ILCreateDrive(wszDriveName));
395                     wszDriveName[0]++;
396                     dwDrivemap = dwDrivemap >> 1;
397                 }
398             }
399 
400             /* Enumerate the items of the reg folder */
401             AppendItemsFromEnumerator(pRegEnumerator);
402 
403             return S_OK;
404         }
405 
406         BEGIN_COM_MAP(CDrivesFolderEnum)
407         COM_INTERFACE_ENTRY_IID(IID_IEnumIDList, IEnumIDList)
408         END_COM_MAP()
409 };
410 
411 /***********************************************************************
412 *   IShellFolder [MyComputer] implementation
413 */
414 
415 static const shvheader MyComputerSFHeader[] = {
416     {IDS_SHV_COLUMN_NAME, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT, 15},
417     {IDS_SHV_COLUMN_COMMENTS, SHCOLSTATE_TYPE_STR, LVCFMT_LEFT, 10},
418     {IDS_SHV_COLUMN_TYPE, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, LVCFMT_LEFT, 10},
419     {IDS_SHV_COLUMN_DISK_CAPACITY, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, LVCFMT_RIGHT, 10},
420     {IDS_SHV_COLUMN_DISK_AVAILABLE, SHCOLSTATE_TYPE_STR | SHCOLSTATE_ONBYDEFAULT, LVCFMT_RIGHT, 10},
421 };
422 
423 #define MYCOMPUTERSHELLVIEWCOLUMNS 5
424 
425 static const DWORD dwComputerAttributes =
426     SFGAO_CANRENAME | SFGAO_CANDELETE | SFGAO_HASPROPSHEET | SFGAO_DROPTARGET |
427     SFGAO_FILESYSANCESTOR | SFGAO_FOLDER | SFGAO_HASSUBFOLDER;
428 static const DWORD dwControlPanelAttributes =
429     SFGAO_HASSUBFOLDER | SFGAO_FOLDER | SFGAO_CANLINK;
430 static const DWORD dwDriveAttributes =
431     SFGAO_HASSUBFOLDER | SFGAO_FILESYSTEM | SFGAO_FOLDER | SFGAO_FILESYSANCESTOR |
432     SFGAO_DROPTARGET | SFGAO_HASPROPSHEET | SFGAO_CANRENAME | SFGAO_CANLINK;
433 
434 CDrivesFolder::CDrivesFolder()
435 {
436     pidlRoot = NULL;
437 }
438 
439 CDrivesFolder::~CDrivesFolder()
440 {
441     TRACE ("-- destroying IShellFolder(%p)\n", this);
442     SHFree(pidlRoot);
443 }
444 
445 HRESULT WINAPI CDrivesFolder::FinalConstruct()
446 {
447     pidlRoot = _ILCreateMyComputer();    /* my qualified pidl */
448     if (pidlRoot == NULL)
449         return E_OUTOFMEMORY;
450 
451     HRESULT hr = CRegFolder_CreateInstance(&CLSID_MyComputer,
452                                            pidlRoot,
453                                            L"::{20D04FE0-3AEA-1069-A2D8-08002B30309D}",
454                                            L"MyComputer",
455                                            IID_PPV_ARG(IShellFolder2, &m_regFolder));
456 
457     return hr;
458 }
459 
460 /**************************************************************************
461 *    CDrivesFolder::ParseDisplayName
462 */
463 HRESULT WINAPI CDrivesFolder::ParseDisplayName(HWND hwndOwner, LPBC pbc, LPOLESTR lpszDisplayName,
464         DWORD * pchEaten, PIDLIST_RELATIVE * ppidl, DWORD * pdwAttributes)
465 {
466     HRESULT hr = E_INVALIDARG;
467     LPCWSTR szNext = NULL;
468     LPITEMIDLIST pidlTemp = NULL;
469 
470     TRACE("(%p)->(HWND=%p,%p,%p=%s,%p,pidl=%p,%p)\n", this,
471           hwndOwner, pbc, lpszDisplayName, debugstr_w (lpszDisplayName),
472           pchEaten, ppidl, pdwAttributes);
473 
474     *ppidl = 0;
475     if (pchEaten)
476         *pchEaten = 0;        /* strange but like the original */
477 
478     /* handle CLSID paths */
479     if (lpszDisplayName[0] == ':' && lpszDisplayName[1] == ':')
480         return m_regFolder->ParseDisplayName(hwndOwner, pbc, lpszDisplayName, pchEaten, ppidl, pdwAttributes);
481 
482     if (PathGetDriveNumberW(lpszDisplayName) < 0)
483         return E_INVALIDARG;
484 
485     pidlTemp = _ILCreateDrive(lpszDisplayName);
486     if (!pidlTemp)
487         return E_OUTOFMEMORY;
488 
489     if (lpszDisplayName[2] == L'\\')
490     {
491         szNext = &lpszDisplayName[3];
492     }
493 
494     if (szNext && *szNext)
495     {
496         hr = SHELL32_ParseNextElement (this, hwndOwner, pbc, &pidlTemp,
497                                        (LPOLESTR) szNext, pchEaten, pdwAttributes);
498     }
499     else
500     {
501         hr = S_OK;
502         if (pdwAttributes && *pdwAttributes)
503         {
504             if (_ILIsDrive(pidlTemp))
505                 *pdwAttributes &= dwDriveAttributes;
506             else if (_ILIsSpecialFolder(pidlTemp))
507                 m_regFolder->GetAttributesOf(1, &pidlTemp, pdwAttributes);
508             else
509                 ERR("Got an unkown pidl here!\n");
510         }
511     }
512 
513     *ppidl = pidlTemp;
514 
515     TRACE ("(%p)->(-- ret=0x%08x)\n", this, hr);
516 
517     return hr;
518 }
519 
520 /**************************************************************************
521 *        CDrivesFolder::EnumObjects
522 */
523 HRESULT WINAPI CDrivesFolder::EnumObjects(HWND hwndOwner, DWORD dwFlags, LPENUMIDLIST *ppEnumIDList)
524 {
525     CComPtr<IEnumIDList> pRegEnumerator;
526     m_regFolder->EnumObjects(hwndOwner, dwFlags, &pRegEnumerator);
527 
528     return ShellObjectCreatorInit<CDrivesFolderEnum>(hwndOwner, dwFlags, pRegEnumerator, IID_PPV_ARG(IEnumIDList, ppEnumIDList));
529 }
530 
531 /**************************************************************************
532 *        CDrivesFolder::BindToObject
533 */
534 HRESULT WINAPI CDrivesFolder::BindToObject(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut)
535 {
536     TRACE("(%p)->(pidl=%p,%p,%s,%p)\n", this,
537           pidl, pbcReserved, shdebugstr_guid(&riid), ppvOut);
538 
539     if (!pidl)
540         return E_INVALIDARG;
541 
542     if (_ILIsSpecialFolder(pidl))
543         return m_regFolder->BindToObject(pidl, pbcReserved, riid, ppvOut);
544 
545     CHAR* pchDrive = _ILGetDataPointer(pidl)->u.drive.szDriveName;
546 
547     PERSIST_FOLDER_TARGET_INFO pfti = {0};
548     pfti.dwAttributes = -1;
549     pfti.csidl = -1;
550     pfti.szTargetParsingName[0] = *pchDrive;
551     pfti.szTargetParsingName[1] = L':';
552     pfti.szTargetParsingName[2] = L'\\';
553 
554     HRESULT hr = SHELL32_BindToSF(pidlRoot,
555                                   &pfti,
556                                   pidl,
557                                   &CLSID_ShellFSFolder,
558                                   riid,
559                                   ppvOut);
560     if (FAILED_UNEXPECTEDLY(hr))
561         return hr;
562 
563     return S_OK;
564 }
565 
566 /**************************************************************************
567 *    CDrivesFolder::BindToStorage
568 */
569 HRESULT WINAPI CDrivesFolder::BindToStorage(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut)
570 {
571     FIXME("(%p)->(pidl=%p,%p,%s,%p) stub\n", this,
572           pidl, pbcReserved, shdebugstr_guid (&riid), ppvOut);
573 
574     *ppvOut = NULL;
575     return E_NOTIMPL;
576 }
577 
578 /**************************************************************************
579 *     CDrivesFolder::CompareIDs
580 */
581 
582 HRESULT WINAPI CDrivesFolder::CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2)
583 {
584     HRESULT hres;
585 
586     if (!pidl1 || !pidl2)
587     {
588         ERR("Got null pidl pointer (%Ix %p %p)!\n", lParam, pidl1, pidl2);
589         return E_INVALIDARG;
590     }
591 
592     if (_ILIsSpecialFolder(pidl1) || _ILIsSpecialFolder(pidl2))
593         return m_regFolder->CompareIDs(lParam, pidl1, pidl2);
594 
595     if (!_ILIsDrive(pidl1) || !_ILIsDrive(pidl2) || LOWORD(lParam) >= MYCOMPUTERSHELLVIEWCOLUMNS)
596         return E_INVALIDARG;
597 
598     CHAR* pszDrive1 = _ILGetDataPointer(pidl1)->u.drive.szDriveName;
599     CHAR* pszDrive2 = _ILGetDataPointer(pidl2)->u.drive.szDriveName;
600 
601     int result;
602     switch(LOWORD(lParam))
603     {
604         case 0:        /* name */
605         {
606             result = stricmp(pszDrive1, pszDrive2);
607             hres = MAKE_COMPARE_HRESULT(result);
608             break;
609         }
610         case 1:        /* comments */
611             hres = MAKE_COMPARE_HRESULT(0);
612             break;
613         case 2:        /* Type */
614         {
615             /* We want to return immediately because SHELL32_CompareDetails also compares children. */
616             return SHELL32_CompareDetails(this, lParam, pidl1, pidl2);
617         }
618         case 3:       /* Size */
619         case 4:       /* Size Available */
620         {
621             ULARGE_INTEGER Drive1Available, Drive1Total, Drive2Available, Drive2Total;
622 
623             if (GetVolumeInformationA(pszDrive1, NULL, 0, NULL, NULL, NULL, NULL, 0))
624                 GetDiskFreeSpaceExA(pszDrive1, &Drive1Available, &Drive1Total, NULL);
625             else
626                 Drive1Available.QuadPart = Drive1Total.QuadPart = 0;
627 
628             if (GetVolumeInformationA(pszDrive2, NULL, 0, NULL, NULL, NULL, NULL, 0))
629                 GetDiskFreeSpaceExA(pszDrive2, &Drive2Available, &Drive2Total, NULL);
630             else
631                 Drive2Available.QuadPart = Drive2Total.QuadPart = 0;
632 
633             LARGE_INTEGER Diff;
634             if (lParam == 3) /* Size */
635                 Diff.QuadPart = Drive1Total.QuadPart - Drive2Total.QuadPart;
636             else /* Size available */
637                 Diff.QuadPart = Drive1Available.QuadPart - Drive2Available.QuadPart;
638 
639             hres = MAKE_COMPARE_HRESULT(Diff.QuadPart);
640             break;
641         }
642         default:
643             return E_INVALIDARG;
644     }
645 
646     if (HRESULT_CODE(hres) == 0)
647         return SHELL32_CompareChildren(this, lParam, pidl1, pidl2);
648 
649     return hres;
650 }
651 
652 /**************************************************************************
653 *    CDrivesFolder::CreateViewObject
654 */
655 HRESULT WINAPI CDrivesFolder::CreateViewObject(HWND hwndOwner, REFIID riid, LPVOID * ppvOut)
656 {
657     CComPtr<IShellView> pShellView;
658     HRESULT hr = E_INVALIDARG;
659 
660     TRACE("(%p)->(hwnd=%p,%s,%p)\n", this,
661           hwndOwner, shdebugstr_guid (&riid), ppvOut);
662 
663     if (!ppvOut)
664         return hr;
665 
666     *ppvOut = NULL;
667 
668     if (IsEqualIID(riid, IID_IDropTarget))
669     {
670         WARN("IDropTarget not implemented\n");
671         hr = E_NOTIMPL;
672     }
673     else if (IsEqualIID(riid, IID_IContextMenu))
674     {
675         HKEY hKeys[16];
676         UINT cKeys = 0;
677         AddClassKeyToArray(L"Directory\\Background", hKeys, &cKeys);
678 
679         DEFCONTEXTMENU dcm;
680         dcm.hwnd = hwndOwner;
681         dcm.pcmcb = this;
682         dcm.pidlFolder = pidlRoot;
683         dcm.psf = this;
684         dcm.cidl = 0;
685         dcm.apidl = NULL;
686         dcm.cKeys = cKeys;
687         dcm.aKeys = hKeys;
688         dcm.punkAssociationInfo = NULL;
689         hr = SHCreateDefaultContextMenu(&dcm, riid, ppvOut);
690     }
691     else if (IsEqualIID(riid, IID_IShellView))
692     {
693             SFV_CREATE sfvparams = {sizeof(SFV_CREATE), this};
694             hr = SHCreateShellFolderView(&sfvparams, (IShellView**)ppvOut);
695     }
696     TRACE ("-- (%p)->(interface=%p)\n", this, ppvOut);
697     return hr;
698 }
699 
700 static BOOL _ILIsControlPanel(LPCITEMIDLIST pidl)
701 {
702     GUID *guid = _ILGetGUIDPointer(pidl);
703 
704     TRACE("(%p)\n", pidl);
705 
706     if (guid)
707         return IsEqualIID(*guid, CLSID_ControlPanel);
708     return FALSE;
709 }
710 
711 /**************************************************************************
712 *  CDrivesFolder::GetAttributesOf
713 */
714 HRESULT WINAPI CDrivesFolder::GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, DWORD * rgfInOut)
715 {
716     TRACE ("(%p)->(cidl=%d apidl=%p mask=%p (0x%08x))\n",
717            this, cidl, apidl, rgfInOut, rgfInOut ? *rgfInOut : 0);
718 
719     if (cidl && !apidl)
720         return E_INVALIDARG;
721 
722     if (*rgfInOut == 0)
723         *rgfInOut = ~0;
724 
725     /* FIXME: always add SFGAO_CANLINK */
726     if(cidl == 0)
727         *rgfInOut &= dwComputerAttributes;
728     else
729     {
730         for (UINT i = 0; i < cidl; ++i)
731         {
732             if (_ILIsDrive(apidl[i]))
733                 *rgfInOut &= dwDriveAttributes;
734             else if (_ILIsControlPanel(apidl[i]))
735                 *rgfInOut &= dwControlPanelAttributes;
736             else if (_ILIsSpecialFolder(*apidl))
737                 m_regFolder->GetAttributesOf(1, &apidl[i], rgfInOut);
738             else
739                 ERR("Got unknown pidl type!\n");
740         }
741     }
742 
743     /* make sure SFGAO_VALIDATE is cleared, some apps depend on that */
744     *rgfInOut &= ~SFGAO_VALIDATE;
745 
746     TRACE ("-- result=0x%08x\n", *rgfInOut);
747     return S_OK;
748 }
749 
750 /**************************************************************************
751 *    CDrivesFolder::GetUIObjectOf
752 *
753 * PARAMETERS
754 *  hwndOwner [in]  Parent window for any output
755 *  cidl      [in]  array size
756 *  apidl     [in]  simple pidl array
757 *  riid      [in]  Requested Interface
758 *  prgfInOut [   ] reserved
759 *  ppvObject [out] Resulting Interface
760 *
761 */
762 HRESULT WINAPI CDrivesFolder::GetUIObjectOf(HWND hwndOwner,
763     UINT cidl, PCUITEMID_CHILD_ARRAY apidl,
764     REFIID riid, UINT *prgfInOut, LPVOID *ppvOut)
765 {
766     LPVOID pObj = NULL;
767     HRESULT hr = E_INVALIDARG;
768 
769     TRACE("(%p)->(%p,%u,apidl=%p,%s,%p,%p)\n", this,
770           hwndOwner, cidl, apidl, shdebugstr_guid (&riid), prgfInOut, ppvOut);
771 
772     if (!ppvOut)
773         return hr;
774 
775     *ppvOut = NULL;
776 
777     if (IsEqualIID (riid, IID_IContextMenu) && (cidl >= 1))
778     {
779         if (_ILIsDrive(apidl[0]))
780             hr = CDrivesContextMenu_CreateInstance(pidlRoot, hwndOwner, cidl, apidl, static_cast<IShellFolder*>(this), (IContextMenu**)&pObj);
781         else
782             hr = m_regFolder->GetUIObjectOf(hwndOwner, cidl, apidl, riid, prgfInOut, &pObj);
783     }
784     else if (IsEqualIID (riid, IID_IDataObject) && (cidl >= 1))
785     {
786         hr = IDataObject_Constructor (hwndOwner,
787                                       pidlRoot, apidl, cidl, TRUE, (IDataObject **)&pObj);
788     }
789     else if ((IsEqualIID (riid, IID_IExtractIconA) || IsEqualIID (riid, IID_IExtractIconW)) && (cidl == 1))
790     {
791         if (_ILIsDrive(apidl[0]))
792             hr = CDrivesExtractIcon_CreateInstance(this, apidl[0], riid, &pObj);
793         else
794             hr = m_regFolder->GetUIObjectOf(hwndOwner, cidl, apidl, riid, prgfInOut, &pObj);
795     }
796     else if (IsEqualIID (riid, IID_IDropTarget) && (cidl == 1))
797     {
798         CComPtr<IShellFolder> psfChild;
799         hr = this->BindToObject(apidl[0], NULL, IID_PPV_ARG(IShellFolder, &psfChild));
800         if (FAILED_UNEXPECTEDLY(hr))
801             return hr;
802 
803         return psfChild->CreateViewObject(NULL, riid, ppvOut);
804     }
805     else
806         hr = E_NOINTERFACE;
807 
808     if (SUCCEEDED(hr) && !pObj)
809         hr = E_OUTOFMEMORY;
810 
811     *ppvOut = pObj;
812     TRACE ("(%p)->hr=0x%08x\n", this, hr);
813     return hr;
814 }
815 
816 /**************************************************************************
817 *    CDrivesFolder::GetDisplayNameOf
818 */
819 HRESULT WINAPI CDrivesFolder::GetDisplayNameOf(PCUITEMID_CHILD pidl, DWORD dwFlags, LPSTRRET strRet)
820 {
821     LPWSTR pszPath;
822     HRESULT hr = S_OK;
823 
824     TRACE ("(%p)->(pidl=%p,0x%08x,%p)\n", this, pidl, dwFlags, strRet);
825     pdump (pidl);
826 
827     if (!strRet)
828         return E_INVALIDARG;
829 
830     if (!_ILIsPidlSimple (pidl))
831     {
832         return SHELL32_GetDisplayNameOfChild(this, pidl, dwFlags, strRet);
833     }
834     else if (_ILIsSpecialFolder(pidl))
835     {
836         return m_regFolder->GetDisplayNameOf(pidl, dwFlags, strRet);
837     }
838     else if (!_ILIsDrive(pidl))
839     {
840         ERR("Wrong pidl type\n");
841         return E_INVALIDARG;
842     }
843 
844     pszPath = (LPWSTR)CoTaskMemAlloc((MAX_PATH + 1) * sizeof(WCHAR));
845     if (!pszPath)
846         return E_OUTOFMEMORY;
847 
848     pszPath[0] = 0;
849 
850     _ILSimpleGetTextW(pidl, pszPath, MAX_PATH);    /* append my own path */
851     /* long view "lw_name (C:)" */
852     if (!(dwFlags & SHGDN_FORPARSING))
853     {
854         WCHAR wszDrive[18] = {0};
855         DWORD dwVolumeSerialNumber, dwMaximumComponentLength, dwFileSystemFlags;
856         static const WCHAR wszOpenBracket[] = {' ', '(', 0};
857         static const WCHAR wszCloseBracket[] = {')', 0};
858 
859         lstrcpynW(wszDrive, pszPath, 4);
860         pszPath[0] = L'\0';
861         GetVolumeInformationW(wszDrive, pszPath,
862                                 MAX_PATH - 7,
863                                 &dwVolumeSerialNumber,
864                                 &dwMaximumComponentLength, &dwFileSystemFlags, NULL, 0);
865         pszPath[MAX_PATH-1] = L'\0';
866         if (!wcslen(pszPath))
867         {
868             UINT DriveType, ResourceId;
869             DriveType = GetDriveTypeW(wszDrive);
870             switch(DriveType)
871             {
872                 case DRIVE_FIXED:
873                     ResourceId = IDS_DRIVE_FIXED;
874                     break;
875                 case DRIVE_REMOTE:
876                     ResourceId = IDS_DRIVE_NETWORK;
877                     break;
878                 case DRIVE_CDROM:
879                     ResourceId = IDS_DRIVE_CDROM;
880                     break;
881                 default:
882                     ResourceId = 0;
883             }
884             if (ResourceId)
885             {
886                 dwFileSystemFlags = LoadStringW(shell32_hInstance, ResourceId, pszPath, MAX_PATH);
887                 if (dwFileSystemFlags > MAX_PATH - 7)
888                     pszPath[MAX_PATH-7] = L'\0';
889             }
890         }
891         wcscat (pszPath, wszOpenBracket);
892         wszDrive[2] = L'\0';
893         wcscat (pszPath, wszDrive);
894         wcscat (pszPath, wszCloseBracket);
895     }
896 
897     if (SUCCEEDED(hr))
898     {
899         strRet->uType = STRRET_WSTR;
900         strRet->pOleStr = pszPath;
901     }
902     else
903         CoTaskMemFree(pszPath);
904 
905     TRACE("-- (%p)->(%s)\n", this, strRet->uType == STRRET_CSTR ? strRet->cStr : debugstr_w(strRet->pOleStr));
906     return hr;
907 }
908 
909 /**************************************************************************
910 *  CDrivesFolder::SetNameOf
911 *  Changes the name of a file object or subfolder, possibly changing its item
912 *  identifier in the process.
913 *
914 * PARAMETERS
915 *  hwndOwner  [in]   Owner window for output
916 *  pidl       [in]   simple pidl of item to change
917 *  lpszName   [in]   the items new display name
918 *  dwFlags    [in]   SHGNO formatting flags
919 *  ppidlOut   [out]  simple pidl returned
920 */
921 HRESULT WINAPI CDrivesFolder::SetNameOf(HWND hwndOwner, PCUITEMID_CHILD pidl,
922                                         LPCOLESTR lpName, DWORD dwFlags, PITEMID_CHILD *pPidlOut)
923 {
924     WCHAR szName[30];
925 
926     if (_ILIsDrive(pidl))
927     {
928         if (_ILSimpleGetTextW(pidl, szName, _countof(szName)))
929             SetVolumeLabelW(szName, lpName);
930         if (pPidlOut)
931             *pPidlOut = _ILCreateDrive(szName);
932         return S_OK;
933     }
934 
935     return m_regFolder->SetNameOf(hwndOwner, pidl, lpName, dwFlags, pPidlOut);
936 }
937 
938 HRESULT WINAPI CDrivesFolder::GetDefaultSearchGUID(GUID * pguid)
939 {
940     FIXME ("(%p)\n", this);
941     return E_NOTIMPL;
942 }
943 
944 HRESULT WINAPI CDrivesFolder::EnumSearches(IEnumExtraSearch ** ppenum)
945 {
946     FIXME ("(%p)\n", this);
947     return E_NOTIMPL;
948 }
949 
950 HRESULT WINAPI CDrivesFolder::GetDefaultColumn (DWORD dwRes, ULONG *pSort, ULONG *pDisplay)
951 {
952     TRACE ("(%p)\n", this);
953 
954     if (pSort)
955         *pSort = 0;
956     if (pDisplay)
957         *pDisplay = 0;
958     return S_OK;
959 }
960 
961 HRESULT WINAPI CDrivesFolder::GetDefaultColumnState(UINT iColumn, DWORD * pcsFlags)
962 {
963     TRACE ("(%p)\n", this);
964 
965     if (!pcsFlags || iColumn >= MYCOMPUTERSHELLVIEWCOLUMNS)
966         return E_INVALIDARG;
967     *pcsFlags = MyComputerSFHeader[iColumn].pcsFlags;
968     return S_OK;
969 }
970 
971 HRESULT WINAPI CDrivesFolder::GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID * pscid, VARIANT * pv)
972 {
973     FIXME ("(%p)\n", this);
974     return E_NOTIMPL;
975 }
976 
977 HRESULT WINAPI CDrivesFolder::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS *psd)
978 {
979     HRESULT hr;
980 
981     TRACE ("(%p)->(%p %i %p)\n", this, pidl, iColumn, psd);
982 
983     if (!psd || iColumn >= MYCOMPUTERSHELLVIEWCOLUMNS)
984         return E_INVALIDARG;
985 
986     if (!pidl)
987     {
988         psd->fmt = MyComputerSFHeader[iColumn].fmt;
989         psd->cxChar = MyComputerSFHeader[iColumn].cxChar;
990         return SHSetStrRet(&psd->str, MyComputerSFHeader[iColumn].colnameid);
991     }
992     else if (!_ILIsDrive(pidl))
993     {
994         return m_regFolder->GetDetailsOf(pidl, iColumn, psd);
995     }
996     else
997     {
998         ULARGE_INTEGER ulTotalBytes, ulFreeBytes;
999         CHAR* pszDrive = _ILGetDataPointer(pidl)->u.drive.szDriveName;
1000         UINT DriveType = GetDriveTypeA(pszDrive);
1001         if (DriveType > DRIVE_RAMDISK)
1002             DriveType = DRIVE_FIXED;
1003 
1004         switch (iColumn)
1005         {
1006             case 0:        /* name */
1007                 hr = GetDisplayNameOf(pidl, SHGDN_NORMAL | SHGDN_INFOLDER, &psd->str);
1008                 break;
1009             case 1:                /* FIXME: comments */
1010                 hr = SHSetStrRet(&psd->str, "");
1011                 break;
1012             case 2:        /* type */
1013                 hr = SHSetStrRet(&psd->str, iDriveTypeIds[DriveType]);
1014                 break;
1015             case 3:        /* total size */
1016             case 4:        /* free size */
1017                 psd->str.cStr[0] = 0x00;
1018                 psd->str.uType = STRRET_CSTR;
1019                 if (GetVolumeInformationA(pszDrive, NULL, 0, NULL, NULL, NULL, NULL, 0))
1020                 {
1021                     GetDiskFreeSpaceExA(pszDrive, &ulFreeBytes, &ulTotalBytes, NULL);
1022                     if (iColumn == 3)
1023                         StrFormatByteSize64A(ulTotalBytes.QuadPart, psd->str.cStr, MAX_PATH);
1024                     else
1025                         StrFormatByteSize64A(ulFreeBytes.QuadPart, psd->str.cStr, MAX_PATH);
1026                 }
1027                 hr = S_OK;
1028                 break;
1029         }
1030     }
1031 
1032     return hr;
1033 }
1034 
1035 HRESULT WINAPI CDrivesFolder::MapColumnToSCID(UINT column, SHCOLUMNID * pscid)
1036 {
1037     FIXME("(%p)\n", this);
1038     return E_NOTIMPL;
1039 }
1040 
1041 /************************************************************************
1042  *    CDrivesFolder::GetClassID
1043  */
1044 HRESULT WINAPI CDrivesFolder::GetClassID(CLSID *lpClassId)
1045 {
1046     TRACE ("(%p)\n", this);
1047 
1048     if (!lpClassId)
1049         return E_POINTER;
1050 
1051     *lpClassId = CLSID_MyComputer;
1052     return S_OK;
1053 }
1054 
1055 /************************************************************************
1056  *    CDrivesFolder::Initialize
1057  *
1058  * NOTES: it makes no sense to change the pidl
1059  */
1060 HRESULT WINAPI CDrivesFolder::Initialize(PCIDLIST_ABSOLUTE pidl)
1061 {
1062     return S_OK;
1063 }
1064 
1065 /**************************************************************************
1066  *    CDrivesFolder::GetCurFolder
1067  */
1068 HRESULT WINAPI CDrivesFolder::GetCurFolder(PIDLIST_ABSOLUTE *pidl)
1069 {
1070     TRACE("(%p)->(%p)\n", this, pidl);
1071 
1072     if (!pidl)
1073         return E_INVALIDARG; /* xp doesn't have this check and crashes on NULL */
1074 
1075     *pidl = ILClone(pidlRoot);
1076     return S_OK;
1077 }
1078 
1079 /************************************************************************/
1080 /* IContextMenuCB interface */
1081 
1082 HRESULT WINAPI CDrivesFolder::CallBack(IShellFolder *psf, HWND hwndOwner, IDataObject *pdtobj, UINT uMsg, WPARAM wParam, LPARAM lParam)
1083 {
1084     if (uMsg != DFM_MERGECONTEXTMENU && uMsg != DFM_INVOKECOMMAND)
1085         return S_OK;
1086 
1087     /* no data object means no selection */
1088     if (!pdtobj)
1089     {
1090         if (uMsg == DFM_INVOKECOMMAND && wParam == 1)   // #1
1091         {
1092             // "System" properties
1093             ShellExecuteW(hwndOwner,
1094                           NULL,
1095                           L"rundll32.exe",
1096                           L"shell32.dll,Control_RunDLL sysdm.cpl",
1097                           NULL,
1098                           SW_SHOWNORMAL);
1099         }
1100         else if (uMsg == DFM_MERGECONTEXTMENU)
1101         {
1102             QCMINFO *pqcminfo = (QCMINFO *)lParam;
1103             HMENU hpopup = CreatePopupMenu();
1104             _InsertMenuItemW(hpopup, 0, TRUE, 0, MFT_SEPARATOR, NULL, MFS_ENABLED); // #0
1105             _InsertMenuItemW(hpopup, 1, TRUE, 1, MFT_STRING, MAKEINTRESOURCEW(IDS_PROPERTIES), MFS_ENABLED); // #1
1106             Shell_MergeMenus(pqcminfo->hmenu, hpopup, pqcminfo->indexMenu++, pqcminfo->idCmdFirst, pqcminfo->idCmdLast, MM_ADDSEPARATOR);
1107             DestroyMenu(hpopup);
1108         }
1109 
1110         return S_OK;
1111     }
1112 
1113     if (uMsg != DFM_INVOKECOMMAND || wParam != DFM_CMD_PROPERTIES)
1114         return S_OK;
1115 
1116     return Shell_DefaultContextMenuCallBack(this, pdtobj);
1117 }
1118