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 (_ILIsSpecialFolder(pidl))
540         return m_regFolder->BindToObject(pidl, pbcReserved, riid, ppvOut);
541 
542     CHAR* pchDrive = _ILGetDataPointer(pidl)->u.drive.szDriveName;
543 
544     PERSIST_FOLDER_TARGET_INFO pfti = {0};
545     pfti.dwAttributes = -1;
546     pfti.csidl = -1;
547     pfti.szTargetParsingName[0] = *pchDrive;
548     pfti.szTargetParsingName[1] = L':';
549     pfti.szTargetParsingName[2] = L'\\';
550 
551     HRESULT hr = SHELL32_BindToSF(pidlRoot,
552                                   &pfti,
553                                   pidl,
554                                   &CLSID_ShellFSFolder,
555                                   riid,
556                                   ppvOut);
557     if (FAILED_UNEXPECTEDLY(hr))
558         return hr;
559 
560     return S_OK;
561 }
562 
563 /**************************************************************************
564 *    CDrivesFolder::BindToStorage
565 */
566 HRESULT WINAPI CDrivesFolder::BindToStorage(PCUIDLIST_RELATIVE pidl, LPBC pbcReserved, REFIID riid, LPVOID *ppvOut)
567 {
568     FIXME("(%p)->(pidl=%p,%p,%s,%p) stub\n", this,
569           pidl, pbcReserved, shdebugstr_guid (&riid), ppvOut);
570 
571     *ppvOut = NULL;
572     return E_NOTIMPL;
573 }
574 
575 /**************************************************************************
576 *     CDrivesFolder::CompareIDs
577 */
578 
579 HRESULT WINAPI CDrivesFolder::CompareIDs(LPARAM lParam, PCUIDLIST_RELATIVE pidl1, PCUIDLIST_RELATIVE pidl2)
580 {
581     HRESULT hres;
582 
583     if (!pidl1 || !pidl2)
584     {
585         ERR("Got null pidl pointer (%Ix %p %p)!\n", lParam, pidl1, pidl2);
586         return E_INVALIDARG;
587     }
588 
589     if (_ILIsSpecialFolder(pidl1) || _ILIsSpecialFolder(pidl2))
590         return m_regFolder->CompareIDs(lParam, pidl1, pidl2);
591 
592     if (!_ILIsDrive(pidl1) || !_ILIsDrive(pidl2) || LOWORD(lParam) >= MYCOMPUTERSHELLVIEWCOLUMNS)
593         return E_INVALIDARG;
594 
595     CHAR* pszDrive1 = _ILGetDataPointer(pidl1)->u.drive.szDriveName;
596     CHAR* pszDrive2 = _ILGetDataPointer(pidl2)->u.drive.szDriveName;
597 
598     int result;
599     switch(LOWORD(lParam))
600     {
601         case 0:        /* name */
602         {
603             result = stricmp(pszDrive1, pszDrive2);
604             hres = MAKE_COMPARE_HRESULT(result);
605             break;
606         }
607         case 1:        /* comments */
608             hres = MAKE_COMPARE_HRESULT(0);
609             break;
610         case 2:        /* Type */
611         {
612             /* We want to return immediately because SHELL32_CompareDetails also compares children. */
613             return SHELL32_CompareDetails(this, lParam, pidl1, pidl2);
614         }
615         case 3:       /* Size */
616         case 4:       /* Size Available */
617         {
618             ULARGE_INTEGER Drive1Available, Drive1Total, Drive2Available, Drive2Total;
619 
620             if (GetVolumeInformationA(pszDrive1, NULL, 0, NULL, NULL, NULL, NULL, 0))
621                 GetDiskFreeSpaceExA(pszDrive1, &Drive1Available, &Drive1Total, NULL);
622             else
623                 Drive1Available.QuadPart = Drive1Total.QuadPart = 0;
624 
625             if (GetVolumeInformationA(pszDrive2, NULL, 0, NULL, NULL, NULL, NULL, 0))
626                 GetDiskFreeSpaceExA(pszDrive2, &Drive2Available, &Drive2Total, NULL);
627             else
628                 Drive2Available.QuadPart = Drive2Total.QuadPart = 0;
629 
630             LARGE_INTEGER Diff;
631             if (lParam == 3) /* Size */
632                 Diff.QuadPart = Drive1Total.QuadPart - Drive2Total.QuadPart;
633             else /* Size available */
634                 Diff.QuadPart = Drive1Available.QuadPart - Drive2Available.QuadPart;
635 
636             hres = MAKE_COMPARE_HRESULT(Diff.QuadPart);
637             break;
638         }
639         default:
640             return E_INVALIDARG;
641     }
642 
643     if (HRESULT_CODE(hres) == 0)
644         return SHELL32_CompareChildren(this, lParam, pidl1, pidl2);
645 
646     return hres;
647 }
648 
649 /**************************************************************************
650 *    CDrivesFolder::CreateViewObject
651 */
652 HRESULT WINAPI CDrivesFolder::CreateViewObject(HWND hwndOwner, REFIID riid, LPVOID * ppvOut)
653 {
654     CComPtr<IShellView> pShellView;
655     HRESULT hr = E_INVALIDARG;
656 
657     TRACE("(%p)->(hwnd=%p,%s,%p)\n", this,
658           hwndOwner, shdebugstr_guid (&riid), ppvOut);
659 
660     if (!ppvOut)
661         return hr;
662 
663     *ppvOut = NULL;
664 
665     if (IsEqualIID(riid, IID_IDropTarget))
666     {
667         WARN("IDropTarget not implemented\n");
668         hr = E_NOTIMPL;
669     }
670     else if (IsEqualIID(riid, IID_IContextMenu))
671     {
672         HKEY hKeys[16];
673         UINT cKeys = 0;
674         AddClassKeyToArray(L"Directory\\Background", hKeys, &cKeys);
675 
676         DEFCONTEXTMENU dcm;
677         dcm.hwnd = hwndOwner;
678         dcm.pcmcb = this;
679         dcm.pidlFolder = pidlRoot;
680         dcm.psf = this;
681         dcm.cidl = 0;
682         dcm.apidl = NULL;
683         dcm.cKeys = cKeys;
684         dcm.aKeys = hKeys;
685         dcm.punkAssociationInfo = NULL;
686         hr = SHCreateDefaultContextMenu(&dcm, riid, ppvOut);
687     }
688     else if (IsEqualIID(riid, IID_IShellView))
689     {
690             SFV_CREATE sfvparams = {sizeof(SFV_CREATE), this};
691             hr = SHCreateShellFolderView(&sfvparams, (IShellView**)ppvOut);
692     }
693     TRACE ("-- (%p)->(interface=%p)\n", this, ppvOut);
694     return hr;
695 }
696 
697 static BOOL _ILIsControlPanel(LPCITEMIDLIST pidl)
698 {
699     GUID *guid = _ILGetGUIDPointer(pidl);
700 
701     TRACE("(%p)\n", pidl);
702 
703     if (guid)
704         return IsEqualIID(*guid, CLSID_ControlPanel);
705     return FALSE;
706 }
707 
708 /**************************************************************************
709 *  CDrivesFolder::GetAttributesOf
710 */
711 HRESULT WINAPI CDrivesFolder::GetAttributesOf(UINT cidl, PCUITEMID_CHILD_ARRAY apidl, DWORD * rgfInOut)
712 {
713     TRACE ("(%p)->(cidl=%d apidl=%p mask=%p (0x%08x))\n",
714            this, cidl, apidl, rgfInOut, rgfInOut ? *rgfInOut : 0);
715 
716     if (cidl && !apidl)
717         return E_INVALIDARG;
718 
719     if (*rgfInOut == 0)
720         *rgfInOut = ~0;
721 
722     /* FIXME: always add SFGAO_CANLINK */
723     if(cidl == 0)
724         *rgfInOut &= dwComputerAttributes;
725     else
726     {
727         for (UINT i = 0; i < cidl; ++i)
728         {
729             if (_ILIsDrive(apidl[i]))
730                 *rgfInOut &= dwDriveAttributes;
731             else if (_ILIsControlPanel(apidl[i]))
732                 *rgfInOut &= dwControlPanelAttributes;
733             else if (_ILIsSpecialFolder(*apidl))
734                 m_regFolder->GetAttributesOf(1, &apidl[i], rgfInOut);
735             else
736                 ERR("Got unknown pidl type!\n");
737         }
738     }
739 
740     /* make sure SFGAO_VALIDATE is cleared, some apps depend on that */
741     *rgfInOut &= ~SFGAO_VALIDATE;
742 
743     TRACE ("-- result=0x%08x\n", *rgfInOut);
744     return S_OK;
745 }
746 
747 /**************************************************************************
748 *    CDrivesFolder::GetUIObjectOf
749 *
750 * PARAMETERS
751 *  hwndOwner [in]  Parent window for any output
752 *  cidl      [in]  array size
753 *  apidl     [in]  simple pidl array
754 *  riid      [in]  Requested Interface
755 *  prgfInOut [   ] reserved
756 *  ppvObject [out] Resulting Interface
757 *
758 */
759 HRESULT WINAPI CDrivesFolder::GetUIObjectOf(HWND hwndOwner,
760     UINT cidl, PCUITEMID_CHILD_ARRAY apidl,
761     REFIID riid, UINT *prgfInOut, LPVOID *ppvOut)
762 {
763     LPVOID pObj = NULL;
764     HRESULT hr = E_INVALIDARG;
765 
766     TRACE("(%p)->(%p,%u,apidl=%p,%s,%p,%p)\n", this,
767           hwndOwner, cidl, apidl, shdebugstr_guid (&riid), prgfInOut, ppvOut);
768 
769     if (!ppvOut)
770         return hr;
771 
772     *ppvOut = NULL;
773 
774     if (IsEqualIID (riid, IID_IContextMenu) && (cidl >= 1))
775     {
776         if (_ILIsDrive(apidl[0]))
777             hr = CDrivesContextMenu_CreateInstance(pidlRoot, hwndOwner, cidl, apidl, static_cast<IShellFolder*>(this), (IContextMenu**)&pObj);
778         else
779             hr = m_regFolder->GetUIObjectOf(hwndOwner, cidl, apidl, riid, prgfInOut, &pObj);
780     }
781     else if (IsEqualIID (riid, IID_IDataObject) && (cidl >= 1))
782     {
783         hr = IDataObject_Constructor (hwndOwner,
784                                       pidlRoot, apidl, cidl, (IDataObject **)&pObj);
785     }
786     else if ((IsEqualIID (riid, IID_IExtractIconA) || IsEqualIID (riid, IID_IExtractIconW)) && (cidl == 1))
787     {
788         if (_ILIsDrive(apidl[0]))
789             hr = CDrivesExtractIcon_CreateInstance(this, apidl[0], riid, &pObj);
790         else
791             hr = m_regFolder->GetUIObjectOf(hwndOwner, cidl, apidl, riid, prgfInOut, &pObj);
792     }
793     else if (IsEqualIID (riid, IID_IDropTarget) && (cidl == 1))
794     {
795         CComPtr<IShellFolder> psfChild;
796         hr = this->BindToObject(apidl[0], NULL, IID_PPV_ARG(IShellFolder, &psfChild));
797         if (FAILED_UNEXPECTEDLY(hr))
798             return hr;
799 
800         return psfChild->CreateViewObject(NULL, riid, ppvOut);
801     }
802     else
803         hr = E_NOINTERFACE;
804 
805     if (SUCCEEDED(hr) && !pObj)
806         hr = E_OUTOFMEMORY;
807 
808     *ppvOut = pObj;
809     TRACE ("(%p)->hr=0x%08x\n", this, hr);
810     return hr;
811 }
812 
813 /**************************************************************************
814 *    CDrivesFolder::GetDisplayNameOf
815 */
816 HRESULT WINAPI CDrivesFolder::GetDisplayNameOf(PCUITEMID_CHILD pidl, DWORD dwFlags, LPSTRRET strRet)
817 {
818     LPWSTR pszPath;
819     HRESULT hr = S_OK;
820 
821     TRACE ("(%p)->(pidl=%p,0x%08x,%p)\n", this, pidl, dwFlags, strRet);
822     pdump (pidl);
823 
824     if (!strRet)
825         return E_INVALIDARG;
826 
827     if (!_ILIsPidlSimple (pidl))
828     {
829         return SHELL32_GetDisplayNameOfChild(this, pidl, dwFlags, strRet);
830     }
831     else if (_ILIsSpecialFolder(pidl))
832     {
833         return m_regFolder->GetDisplayNameOf(pidl, dwFlags, strRet);
834     }
835     else if (!_ILIsDrive(pidl))
836     {
837         ERR("Wrong pidl type\n");
838         return E_INVALIDARG;
839     }
840 
841     pszPath = (LPWSTR)CoTaskMemAlloc((MAX_PATH + 1) * sizeof(WCHAR));
842     if (!pszPath)
843         return E_OUTOFMEMORY;
844 
845     pszPath[0] = 0;
846 
847     _ILSimpleGetTextW(pidl, pszPath, MAX_PATH);    /* append my own path */
848     /* long view "lw_name (C:)" */
849     if (!(dwFlags & SHGDN_FORPARSING))
850     {
851         WCHAR wszDrive[18] = {0};
852         DWORD dwVolumeSerialNumber, dwMaximumComponentLength, dwFileSystemFlags;
853         static const WCHAR wszOpenBracket[] = {' ', '(', 0};
854         static const WCHAR wszCloseBracket[] = {')', 0};
855 
856         lstrcpynW(wszDrive, pszPath, 4);
857         pszPath[0] = L'\0';
858         GetVolumeInformationW(wszDrive, pszPath,
859                                 MAX_PATH - 7,
860                                 &dwVolumeSerialNumber,
861                                 &dwMaximumComponentLength, &dwFileSystemFlags, NULL, 0);
862         pszPath[MAX_PATH-1] = L'\0';
863         if (!wcslen(pszPath))
864         {
865             UINT DriveType, ResourceId;
866             DriveType = GetDriveTypeW(wszDrive);
867             switch(DriveType)
868             {
869                 case DRIVE_FIXED:
870                     ResourceId = IDS_DRIVE_FIXED;
871                     break;
872                 case DRIVE_REMOTE:
873                     ResourceId = IDS_DRIVE_NETWORK;
874                     break;
875                 case DRIVE_CDROM:
876                     ResourceId = IDS_DRIVE_CDROM;
877                     break;
878                 default:
879                     ResourceId = 0;
880             }
881             if (ResourceId)
882             {
883                 dwFileSystemFlags = LoadStringW(shell32_hInstance, ResourceId, pszPath, MAX_PATH);
884                 if (dwFileSystemFlags > MAX_PATH - 7)
885                     pszPath[MAX_PATH-7] = L'\0';
886             }
887         }
888         wcscat (pszPath, wszOpenBracket);
889         wszDrive[2] = L'\0';
890         wcscat (pszPath, wszDrive);
891         wcscat (pszPath, wszCloseBracket);
892     }
893 
894     if (SUCCEEDED(hr))
895     {
896         strRet->uType = STRRET_WSTR;
897         strRet->pOleStr = pszPath;
898     }
899     else
900         CoTaskMemFree(pszPath);
901 
902     TRACE("-- (%p)->(%s)\n", this, strRet->uType == STRRET_CSTR ? strRet->cStr : debugstr_w(strRet->pOleStr));
903     return hr;
904 }
905 
906 /**************************************************************************
907 *  CDrivesFolder::SetNameOf
908 *  Changes the name of a file object or subfolder, possibly changing its item
909 *  identifier in the process.
910 *
911 * PARAMETERS
912 *  hwndOwner  [in]   Owner window for output
913 *  pidl       [in]   simple pidl of item to change
914 *  lpszName   [in]   the items new display name
915 *  dwFlags    [in]   SHGNO formatting flags
916 *  ppidlOut   [out]  simple pidl returned
917 */
918 HRESULT WINAPI CDrivesFolder::SetNameOf(HWND hwndOwner, PCUITEMID_CHILD pidl,
919                                         LPCOLESTR lpName, DWORD dwFlags, PITEMID_CHILD *pPidlOut)
920 {
921     WCHAR szName[30];
922 
923     if (_ILIsDrive(pidl))
924     {
925         if (_ILSimpleGetTextW(pidl, szName, _countof(szName)))
926             SetVolumeLabelW(szName, lpName);
927         if (pPidlOut)
928             *pPidlOut = _ILCreateDrive(szName);
929         return S_OK;
930     }
931 
932     return m_regFolder->SetNameOf(hwndOwner, pidl, lpName, dwFlags, pPidlOut);
933 }
934 
935 HRESULT WINAPI CDrivesFolder::GetDefaultSearchGUID(GUID * pguid)
936 {
937     FIXME ("(%p)\n", this);
938     return E_NOTIMPL;
939 }
940 
941 HRESULT WINAPI CDrivesFolder::EnumSearches(IEnumExtraSearch ** ppenum)
942 {
943     FIXME ("(%p)\n", this);
944     return E_NOTIMPL;
945 }
946 
947 HRESULT WINAPI CDrivesFolder::GetDefaultColumn (DWORD dwRes, ULONG *pSort, ULONG *pDisplay)
948 {
949     TRACE ("(%p)\n", this);
950 
951     if (pSort)
952         *pSort = 0;
953     if (pDisplay)
954         *pDisplay = 0;
955     return S_OK;
956 }
957 
958 HRESULT WINAPI CDrivesFolder::GetDefaultColumnState(UINT iColumn, DWORD * pcsFlags)
959 {
960     TRACE ("(%p)\n", this);
961 
962     if (!pcsFlags || iColumn >= MYCOMPUTERSHELLVIEWCOLUMNS)
963         return E_INVALIDARG;
964     *pcsFlags = MyComputerSFHeader[iColumn].pcsFlags;
965     return S_OK;
966 }
967 
968 HRESULT WINAPI CDrivesFolder::GetDetailsEx(PCUITEMID_CHILD pidl, const SHCOLUMNID * pscid, VARIANT * pv)
969 {
970     FIXME ("(%p)\n", this);
971     return E_NOTIMPL;
972 }
973 
974 HRESULT WINAPI CDrivesFolder::GetDetailsOf(PCUITEMID_CHILD pidl, UINT iColumn, SHELLDETAILS *psd)
975 {
976     HRESULT hr;
977 
978     TRACE ("(%p)->(%p %i %p)\n", this, pidl, iColumn, psd);
979 
980     if (!psd || iColumn >= MYCOMPUTERSHELLVIEWCOLUMNS)
981         return E_INVALIDARG;
982 
983     if (!pidl)
984     {
985         psd->fmt = MyComputerSFHeader[iColumn].fmt;
986         psd->cxChar = MyComputerSFHeader[iColumn].cxChar;
987         return SHSetStrRet(&psd->str, MyComputerSFHeader[iColumn].colnameid);
988     }
989     else if (!_ILIsDrive(pidl))
990     {
991         return m_regFolder->GetDetailsOf(pidl, iColumn, psd);
992     }
993     else
994     {
995         ULARGE_INTEGER ulTotalBytes, ulFreeBytes;
996         CHAR* pszDrive = _ILGetDataPointer(pidl)->u.drive.szDriveName;
997         UINT DriveType = GetDriveTypeA(pszDrive);
998         if (DriveType > DRIVE_RAMDISK)
999             DriveType = DRIVE_FIXED;
1000 
1001         switch (iColumn)
1002         {
1003             case 0:        /* name */
1004                 hr = GetDisplayNameOf(pidl, SHGDN_NORMAL | SHGDN_INFOLDER, &psd->str);
1005                 break;
1006             case 1:                /* FIXME: comments */
1007                 hr = SHSetStrRet(&psd->str, "");
1008                 break;
1009             case 2:        /* type */
1010                 hr = SHSetStrRet(&psd->str, iDriveTypeIds[DriveType]);
1011                 break;
1012             case 3:        /* total size */
1013             case 4:        /* free size */
1014                 psd->str.cStr[0] = 0x00;
1015                 psd->str.uType = STRRET_CSTR;
1016                 if (GetVolumeInformationA(pszDrive, NULL, 0, NULL, NULL, NULL, NULL, 0))
1017                 {
1018                     GetDiskFreeSpaceExA(pszDrive, &ulFreeBytes, &ulTotalBytes, NULL);
1019                     if (iColumn == 3)
1020                         StrFormatByteSize64A(ulTotalBytes.QuadPart, psd->str.cStr, MAX_PATH);
1021                     else
1022                         StrFormatByteSize64A(ulFreeBytes.QuadPart, psd->str.cStr, MAX_PATH);
1023                 }
1024                 hr = S_OK;
1025                 break;
1026         }
1027     }
1028 
1029     return hr;
1030 }
1031 
1032 HRESULT WINAPI CDrivesFolder::MapColumnToSCID(UINT column, SHCOLUMNID * pscid)
1033 {
1034     FIXME("(%p)\n", this);
1035     return E_NOTIMPL;
1036 }
1037 
1038 /************************************************************************
1039  *    CDrivesFolder::GetClassID
1040  */
1041 HRESULT WINAPI CDrivesFolder::GetClassID(CLSID *lpClassId)
1042 {
1043     TRACE ("(%p)\n", this);
1044 
1045     if (!lpClassId)
1046         return E_POINTER;
1047 
1048     *lpClassId = CLSID_MyComputer;
1049     return S_OK;
1050 }
1051 
1052 /************************************************************************
1053  *    CDrivesFolder::Initialize
1054  *
1055  * NOTES: it makes no sense to change the pidl
1056  */
1057 HRESULT WINAPI CDrivesFolder::Initialize(LPCITEMIDLIST pidl)
1058 {
1059     return S_OK;
1060 }
1061 
1062 /**************************************************************************
1063  *    CDrivesFolder::GetCurFolder
1064  */
1065 HRESULT WINAPI CDrivesFolder::GetCurFolder(LPITEMIDLIST *pidl)
1066 {
1067     TRACE("(%p)->(%p)\n", this, pidl);
1068 
1069     if (!pidl)
1070         return E_INVALIDARG; /* xp doesn't have this check and crashes on NULL */
1071 
1072     *pidl = ILClone(pidlRoot);
1073     return S_OK;
1074 }
1075 
1076 /************************************************************************/
1077 /* IContextMenuCB interface */
1078 
1079 HRESULT WINAPI CDrivesFolder::CallBack(IShellFolder *psf, HWND hwndOwner, IDataObject *pdtobj, UINT uMsg, WPARAM wParam, LPARAM lParam)
1080 {
1081     if (uMsg != DFM_MERGECONTEXTMENU && uMsg != DFM_INVOKECOMMAND)
1082         return S_OK;
1083 
1084     /* no data object means no selection */
1085     if (!pdtobj)
1086     {
1087         if (uMsg == DFM_INVOKECOMMAND && wParam == 1)   // #1
1088         {
1089             // "System" properties
1090             ShellExecuteW(hwndOwner, NULL, L"rundll32.exe shell32.dll,Control_RunDLL sysdm.cpl", NULL, NULL, SW_SHOWNORMAL);
1091         }
1092         else if (uMsg == DFM_MERGECONTEXTMENU)
1093         {
1094             QCMINFO *pqcminfo = (QCMINFO *)lParam;
1095             HMENU hpopup = CreatePopupMenu();
1096             _InsertMenuItemW(hpopup, 0, TRUE, 0, MFT_SEPARATOR, NULL, MFS_ENABLED); // #0
1097             _InsertMenuItemW(hpopup, 1, TRUE, 1, MFT_STRING, MAKEINTRESOURCEW(IDS_PROPERTIES), MFS_ENABLED); // #1
1098             Shell_MergeMenus(pqcminfo->hmenu, hpopup, pqcminfo->indexMenu++, pqcminfo->idCmdFirst, pqcminfo->idCmdLast, MM_ADDSEPARATOR);
1099             DestroyMenu(hpopup);
1100         }
1101 
1102         return S_OK;
1103     }
1104 
1105     if (uMsg != DFM_INVOKECOMMAND || wParam != DFM_CMD_PROPERTIES)
1106         return S_OK;
1107 
1108     return Shell_DefaultContextMenuCallBack(this, pdtobj);
1109 }
1110