1 /*
2  * Trash virtual folder support. The trashing engine is implemented in trash.c
3  *
4  * Copyright (C) 2006 Mikolaj Zalewski
5  * Copyright (C) 2009 Andrew Hill
6  *
7  * This library is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 2.1 of the License, or (at your option) any later version.
11  *
12  * This library is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public
18  * License along with this library; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
20  */
21 
22 #include <precomp.h>
23 
24 WINE_DEFAULT_DEBUG_CHANNEL(CRecycleBin);
25 
26 typedef struct
27 {
28     ULARGE_INTEGER FreeBytesAvailable;
29     DWORD dwSerial;
30     DWORD dwMaxCapacity;
31     DWORD dwNukeOnDelete;
32 } DRIVE_ITEM_CONTEXT, *PDRIVE_ITEM_CONTEXT;
33 
34 static void toggleNukeOnDeleteOption(HWND hwndDlg, BOOL bEnable)
35 {
36     if (bEnable)
37     {
38         SendDlgItemMessage(hwndDlg, 14001, BM_SETCHECK, BST_UNCHECKED, 0);
39         EnableWindow(GetDlgItem(hwndDlg, 14002), FALSE);
40         SendDlgItemMessage(hwndDlg, 14003, BM_SETCHECK, BST_CHECKED, 0);
41     }
42     else
43     {
44         SendDlgItemMessage(hwndDlg, 14001, BM_SETCHECK, BST_CHECKED, 0);
45         EnableWindow(GetDlgItem(hwndDlg, 14002), TRUE);
46         SendDlgItemMessage(hwndDlg, 14003, BM_SETCHECK, BST_UNCHECKED, 0);
47     }
48 }
49 
50 static VOID
51 InitializeRecycleBinDlg(HWND hwndDlg, WCHAR DefaultDrive)
52 {
53     WCHAR szDrive[] = L"A:\\";
54     DWORD dwDrives;
55     WCHAR szName[100];
56     WCHAR szVolume[100];
57     DWORD MaxComponent, Flags;
58     DWORD dwSerial;
59     LVCOLUMNW lc;
60     HWND hDlgCtrl;
61     LVITEMW li;
62     INT itemCount;
63     ULARGE_INTEGER TotalNumberOfFreeBytes, TotalNumberOfBytes, FreeBytesAvailable;
64     RECT rect;
65     int columnSize;
66     int defIndex = 0;
67     DWORD dwSize;
68     PDRIVE_ITEM_CONTEXT pItem = NULL, pDefault = NULL, pFirst = NULL;
69 
70     hDlgCtrl = GetDlgItem(hwndDlg, 14000);
71 
72     if (!LoadStringW(shell32_hInstance, IDS_RECYCLEBIN_LOCATION, szVolume, _countof(szVolume)))
73         szVolume[0] = 0;
74 
75     GetClientRect(hDlgCtrl, &rect);
76 
77     ZeroMemory(&lc, sizeof(lc));
78     lc.mask = LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM | LVCF_FMT;
79 
80     columnSize = 140; //FIXME
81     lc.iSubItem   = 0;
82     lc.fmt = LVCFMT_FIXED_WIDTH;
83     lc.cx         = columnSize;
84     lc.cchTextMax = wcslen(szVolume);
85     lc.pszText    = szVolume;
86     (void)SendMessageW(hDlgCtrl, LVM_INSERTCOLUMNW, 0, (LPARAM)&lc);
87 
88     if (!LoadStringW(shell32_hInstance, IDS_RECYCLEBIN_DISKSPACE, szVolume, _countof(szVolume)))
89         szVolume[0] = 0;
90 
91     lc.iSubItem   = 1;
92     lc.cx         = rect.right - rect.left - columnSize;
93     lc.cchTextMax = wcslen(szVolume);
94     lc.pszText    = szVolume;
95     (void)SendMessageW(hDlgCtrl, LVM_INSERTCOLUMNW, 1, (LPARAM)&lc);
96 
97     dwDrives = GetLogicalDrives();
98     itemCount = 0;
99     do
100     {
101         if (dwDrives & 0x1)
102         {
103             UINT Type = GetDriveTypeW(szDrive);
104             if (Type == DRIVE_FIXED) //FIXME
105             {
106                 if (!GetVolumeInformationW(szDrive, szName, _countof(szName), &dwSerial, &MaxComponent, &Flags, NULL, 0))
107                 {
108                     szName[0] = 0;
109                     dwSerial = -1;
110                 }
111 
112                 swprintf(szVolume, L"%s (%c:)", szName, szDrive[0]);
113                 ZeroMemory(&li, sizeof(li));
114                 li.mask = LVIF_TEXT | LVIF_PARAM;
115                 li.iSubItem = 0;
116                 li.pszText = szVolume;
117                 li.iItem = itemCount;
118                 SendMessageW(hDlgCtrl, LVM_INSERTITEMW, 0, (LPARAM)&li);
119                 if (GetDiskFreeSpaceExW(szDrive, &FreeBytesAvailable, &TotalNumberOfBytes, &TotalNumberOfFreeBytes))
120                 {
121                     if (StrFormatByteSizeW(TotalNumberOfFreeBytes.QuadPart, szVolume, _countof(szVolume)))
122                     {
123                         pItem = (DRIVE_ITEM_CONTEXT *)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(DRIVE_ITEM_CONTEXT));
124                         if (pItem)
125                         {
126                             pItem->FreeBytesAvailable = FreeBytesAvailable;
127                             pItem->dwSerial = dwSerial;
128 
129                             swprintf(szName, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\BitBucket\\Volume\\%04X-%04X", LOWORD(dwSerial), HIWORD(dwSerial));
130 
131                             dwSize = sizeof(DWORD);
132                             RegGetValueW(HKEY_CURRENT_USER, szName, L"MaxCapacity", RRF_RT_DWORD, NULL, &pItem->dwMaxCapacity, &dwSize);
133 
134                             /* Check if the maximum capacity doesn't exceed the available disk space (in megabytes), and truncate it if needed */
135                             FreeBytesAvailable.QuadPart = (FreeBytesAvailable.QuadPart / (1024 * 1024));
136                             pItem->dwMaxCapacity = min(pItem->dwMaxCapacity, FreeBytesAvailable.LowPart);
137 
138                             dwSize = sizeof(DWORD);
139                             RegGetValueW(HKEY_CURRENT_USER, szName, L"NukeOnDelete", RRF_RT_DWORD, NULL, &pItem->dwNukeOnDelete, &dwSize);
140 
141                             li.mask = LVIF_PARAM;
142                             li.lParam = (LPARAM)pItem;
143                             (void)SendMessageW(hDlgCtrl, LVM_SETITEMW, 0, (LPARAM)&li);
144                             if (szDrive[0] == DefaultDrive)
145                             {
146                                 defIndex = itemCount;
147                                 pDefault = pItem;
148                             }
149                         }
150                         if (!pFirst)
151                             pFirst = pItem;
152 
153                         li.mask = LVIF_TEXT;
154                         li.iSubItem = 1;
155                         li.pszText = szVolume;
156                         li.iItem = itemCount;
157                         (void)SendMessageW(hDlgCtrl, LVM_SETITEMW, 0, (LPARAM)&li);
158                     }
159                 }
160                 itemCount++;
161             }
162         }
163         szDrive[0]++;
164         dwDrives = (dwDrives >> 1);
165     } while (dwDrives);
166 
167     if (!pDefault)
168         pDefault = pFirst;
169     if (pDefault)
170     {
171         toggleNukeOnDeleteOption(hwndDlg, pDefault->dwNukeOnDelete);
172         SetDlgItemInt(hwndDlg, 14002, pDefault->dwMaxCapacity, FALSE);
173     }
174     ZeroMemory(&li, sizeof(li));
175     li.mask = LVIF_STATE;
176     li.stateMask = (UINT)-1;
177     li.state = LVIS_FOCUSED | LVIS_SELECTED;
178     li.iItem = defIndex;
179     (void)SendMessageW(hDlgCtrl, LVM_SETITEMW, 0, (LPARAM)&li);
180 }
181 
182 static BOOL StoreDriveSettings(HWND hwndDlg)
183 {
184     int iCount, iIndex;
185     HWND hDlgCtrl = GetDlgItem(hwndDlg, 14000);
186     LVITEMW li;
187     PDRIVE_ITEM_CONTEXT pItem;
188     HKEY hKey, hSubKey;
189     WCHAR szSerial[20];
190     DWORD dwSize;
191 
192     if (RegCreateKeyExW(HKEY_CURRENT_USER, L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\BitBucket\\Volume", 0, NULL, 0, KEY_WRITE, NULL, &hKey, NULL) != ERROR_SUCCESS)
193         return FALSE;
194 
195     iCount = ListView_GetItemCount(hDlgCtrl);
196 
197     ZeroMemory(&li, sizeof(li));
198     li.mask = LVIF_PARAM;
199 
200     for (iIndex = 0; iIndex < iCount; iIndex++)
201     {
202         li.iItem = iIndex;
203         if (SendMessageW(hDlgCtrl, LVM_GETITEMW, 0, (LPARAM)&li))
204         {
205             pItem = (PDRIVE_ITEM_CONTEXT)li.lParam;
206             swprintf(szSerial, L"%04X-%04X", LOWORD(pItem->dwSerial), HIWORD(pItem->dwSerial));
207             if (RegCreateKeyExW(hKey, szSerial, 0, NULL, 0, KEY_WRITE, NULL, &hSubKey, NULL) == ERROR_SUCCESS)
208             {
209                 dwSize = sizeof(DWORD);
210                 RegSetValueExW(hSubKey, L"MaxCapacity", 0, REG_DWORD, (LPBYTE)&pItem->dwMaxCapacity, dwSize);
211                 dwSize = sizeof(DWORD);
212                 RegSetValueExW(hSubKey, L"NukeOnDelete", 0, REG_DWORD, (LPBYTE)&pItem->dwNukeOnDelete, dwSize);
213                 RegCloseKey(hSubKey);
214             }
215         }
216     }
217     RegCloseKey(hKey);
218     return TRUE;
219 }
220 
221 static VOID FreeDriveItemContext(HWND hwndDlg)
222 {
223     int iCount, iIndex;
224     HWND hDlgCtrl = GetDlgItem(hwndDlg, 14000);
225     LVITEMW li;
226 
227     iCount = ListView_GetItemCount(hDlgCtrl);
228 
229     ZeroMemory(&li, sizeof(li));
230     li.mask = LVIF_PARAM;
231 
232     for (iIndex = 0; iIndex < iCount; iIndex++)
233     {
234         li.iItem = iIndex;
235         if (SendMessageW(hDlgCtrl, LVM_GETITEMW, 0, (LPARAM)&li))
236         {
237             HeapFree(GetProcessHeap(), 0, (PVOID)li.lParam);
238         }
239     }
240 }
241 
242 static INT
243 GetDefaultItem(HWND hwndDlg, LVITEMW* li)
244 {
245     HWND hDlgCtrl;
246     UINT iItemCount, iIndex;
247 
248     hDlgCtrl = GetDlgItem(hwndDlg, 14000);
249     if (!hDlgCtrl)
250         return -1;
251 
252     iItemCount = ListView_GetItemCount(hDlgCtrl);
253     if (!iItemCount)
254         return -1;
255 
256     ZeroMemory(li, sizeof(LVITEMW));
257     li->mask = LVIF_PARAM | LVIF_STATE;
258     li->stateMask = (UINT)-1;
259     for (iIndex = 0; iIndex < iItemCount; iIndex++)
260     {
261         li->iItem = iIndex;
262         if (SendMessageW(hDlgCtrl, LVM_GETITEMW, 0, (LPARAM)li))
263         {
264             if (li->state & LVIS_SELECTED)
265                 return iIndex;
266         }
267     }
268     return -1;
269 }
270 
271 static INT_PTR CALLBACK
272 RecycleBinDlg(
273     HWND hwndDlg,
274     UINT uMsg,
275     WPARAM wParam,
276     LPARAM lParam)
277 {
278     LPPSHNOTIFY lppsn;
279     LPNMLISTVIEW lppl;
280     LVITEMW li;
281     PDRIVE_ITEM_CONTEXT pItem;
282     BOOL bSuccess;
283     UINT uResult;
284     PROPSHEETPAGE * page;
285     DWORD dwStyle;
286     ULARGE_INTEGER FreeBytesAvailable;
287 
288     switch(uMsg)
289     {
290         case WM_INITDIALOG:
291             page = (PROPSHEETPAGE*)lParam;
292             InitializeRecycleBinDlg(hwndDlg, (WCHAR)page->lParam);
293             SendDlgItemMessage(hwndDlg, 14004, BM_SETCHECK, !SHELL_GetSetting(SSF_NOCONFIRMRECYCLE, fNoConfirmRecycle), 0);
294             dwStyle = (DWORD) SendDlgItemMessage(hwndDlg, 14000, LVM_GETEXTENDEDLISTVIEWSTYLE, 0, 0);
295             dwStyle = dwStyle | LVS_EX_FULLROWSELECT;
296             SendDlgItemMessage(hwndDlg, 14000, LVM_SETEXTENDEDLISTVIEWSTYLE, 0, dwStyle);
297             if (GetDlgCtrlID((HWND)wParam) != 14000)
298             {
299                 SetFocus(GetDlgItem(hwndDlg, 14000));
300                 return FALSE;
301             }
302             return TRUE;
303         case WM_COMMAND:
304             switch(LOWORD(wParam))
305             {
306                 case 14001:
307                     toggleNukeOnDeleteOption(hwndDlg, FALSE);
308                     PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
309                     break;
310                 case 14002:
311                     if (HIWORD(wParam) == EN_CHANGE)
312                         PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
313                     break;
314                 case 14003:
315                     toggleNukeOnDeleteOption(hwndDlg, TRUE);
316                     PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
317                     break;
318                 case 14004:
319                     PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
320                     break;
321             }
322             break;
323         case WM_NOTIFY:
324             lppsn = (LPPSHNOTIFY) lParam;
325             lppl = (LPNMLISTVIEW) lParam;
326             if (lppsn->hdr.code == PSN_APPLY)
327             {
328                 SHELLSTATE ss;
329                 ss.fNoConfirmRecycle = SendDlgItemMessage(hwndDlg, 14004, BM_GETCHECK, 0, 0) == BST_UNCHECKED;
330                 SHGetSetSettings(&ss, SSF_NOCONFIRMRECYCLE, TRUE);
331 
332                 if (GetDefaultItem(hwndDlg, &li) > -1)
333                 {
334                     pItem = (PDRIVE_ITEM_CONTEXT)li.lParam;
335                     if (pItem)
336                     {
337                         uResult = GetDlgItemInt(hwndDlg, 14002, &bSuccess, FALSE);
338                         if (bSuccess)
339                         {
340                             /* Check if the maximum capacity doesn't exceed the available disk space (in megabytes), and truncate it if needed */
341                             FreeBytesAvailable = pItem->FreeBytesAvailable;
342                             FreeBytesAvailable.QuadPart = (FreeBytesAvailable.QuadPart / (1024 * 1024));
343                             pItem->dwMaxCapacity = min(uResult, FreeBytesAvailable.LowPart);
344                             SetDlgItemInt(hwndDlg, 14002, pItem->dwMaxCapacity, FALSE);
345                         }
346                         if (SendDlgItemMessageW(hwndDlg, 14003, BM_GETCHECK, 0, 0) == BST_CHECKED)
347                             pItem->dwNukeOnDelete = TRUE;
348                         else
349                             pItem->dwNukeOnDelete = FALSE;
350                     }
351                 }
352                 if (StoreDriveSettings(hwndDlg))
353                 {
354                     SetWindowLongPtr( hwndDlg, DWLP_MSGRESULT, PSNRET_NOERROR );
355                     return TRUE;
356                 }
357             }
358             else if (lppl->hdr.code == LVN_ITEMCHANGING)
359             {
360                 ZeroMemory(&li, sizeof(li));
361                 li.mask = LVIF_PARAM;
362                 li.iItem = lppl->iItem;
363                 if (!SendMessageW(lppl->hdr.hwndFrom, LVM_GETITEMW, 0, (LPARAM)&li))
364                     return TRUE;
365 
366                 pItem = (PDRIVE_ITEM_CONTEXT)li.lParam;
367                 if (!pItem)
368                     return TRUE;
369 
370                 if (!(lppl->uOldState & LVIS_FOCUSED) && (lppl->uNewState & LVIS_FOCUSED))
371                 {
372                     /* new focused item */
373                     toggleNukeOnDeleteOption(lppl->hdr.hwndFrom, pItem->dwNukeOnDelete);
374                     SetDlgItemInt(hwndDlg, 14002, pItem->dwMaxCapacity, FALSE);
375                 }
376                 else if ((lppl->uOldState & LVIS_FOCUSED) && !(lppl->uNewState & LVIS_FOCUSED))
377                 {
378                     /* kill focus */
379                     uResult = GetDlgItemInt(hwndDlg, 14002, &bSuccess, FALSE);
380                     if (bSuccess)
381                     {
382                         /* Check if the maximum capacity doesn't exceed the available disk space (in megabytes), and truncate it if needed */
383                         FreeBytesAvailable = pItem->FreeBytesAvailable;
384                         FreeBytesAvailable.QuadPart = (FreeBytesAvailable.QuadPart / (1024 * 1024));
385                         pItem->dwMaxCapacity = min(uResult, FreeBytesAvailable.LowPart);
386                         SetDlgItemInt(hwndDlg, 14002, pItem->dwMaxCapacity, FALSE);
387                     }
388                     if (SendDlgItemMessageW(hwndDlg, 14003, BM_GETCHECK, 0, 0) == BST_CHECKED)
389                         pItem->dwNukeOnDelete = TRUE;
390                     else
391                         pItem->dwNukeOnDelete = FALSE;
392                 }
393                 return TRUE;
394 
395             }
396             break;
397         case WM_DESTROY:
398             FreeDriveItemContext(hwndDlg);
399             break;
400     }
401     return FALSE;
402 }
403 
404 BOOL SH_ShowRecycleBinProperties(WCHAR sDrive)
405 {
406     HPROPSHEETPAGE hpsp[1];
407     PROPSHEETHEADERW psh;
408     HPROPSHEETPAGE hprop;
409     INT_PTR ret;
410 
411     ZeroMemory(&psh, sizeof(psh));
412     psh.dwSize = sizeof(psh);
413     psh.dwFlags = PSP_DEFAULT | PSH_PROPTITLE;
414     psh.pszCaption = MAKEINTRESOURCEW(IDS_RECYCLEBIN_FOLDER_NAME);
415     psh.hwndParent = NULL;
416     psh.phpage = hpsp;
417     psh.hInstance = shell32_hInstance;
418 
419     hprop = SH_CreatePropertySheetPage(IDD_RECYCLE_BIN_PROPERTIES, RecycleBinDlg, (LPARAM)sDrive, NULL);
420     if (!hprop)
421     {
422         ERR("Failed to create property sheet\n");
423         return FALSE;
424     }
425     hpsp[psh.nPages] = hprop;
426     psh.nPages++;
427 
428     ret = PropertySheetW(&psh);
429     return (ret >= 0);
430 }
431