1 /*
2 * PROJECT: ReactOS Display Control Panel
3 * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
4 * PURPOSE: Desktop customization property page
5 * COPYRIGHT: Copyright 2018-2022 Stanislav Motylkov <x86corez@gmail.com>
6 */
7
8 #include "desk.h"
9
10 #include <shlwapi.h>
11 #include <shellapi.h>
12
13 /* From shresdef.h */
14 #define FCIDM_DESKBROWSER_REFRESH 0xA220
15
16 #define IDS_TITLE_MYCOMP 30386
17 #define IDS_TITLE_MYNET 30387
18 #define IDS_TITLE_BIN_1 30388
19 #define IDS_TITLE_BIN_0 30389
20
21 /* Workaround:
22 * There's no special fallback icon title string
23 * for My Documents in shell32.dll, so use IDS_PERSONAL.
24 *
25 * Windows does this in some different way.
26 */
27 #define IDS_PERSONAL 9227
28
29 static const TCHAR szHideDesktopIcons[] = TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\HideDesktopIcons\\");
30 static const TCHAR szClassicStartMenu[] = TEXT("ClassicStartMenu");
31 static const TCHAR szNewStartPanel[] = TEXT("NewStartPanel");
32
33 struct
34 {
35 LPCTSTR CLSID;
36 UINT Checkbox;
37 } DesktopIcons[NUM_DESKTOP_ICONS] = {
38 {TEXT("{450D8FBA-AD25-11D0-98A8-0800361B1103}"), IDC_ICONS_MYDOCS}, /* My Documents */
39 {TEXT("{208D2C60-3AEA-1069-A2D7-08002B30309D}"), IDC_ICONS_MYNET}, /* My Network Places */
40 {TEXT("{20D04FE0-3AEA-1069-A2D8-08002B30309D}"), IDC_ICONS_MYCOMP}, /* My Computer */
41 {TEXT("{871C5380-42A0-1069-A2EA-08002B30309D}"), IDC_ICONS_INTERNET}, /* Internet Browser */
42 };
43
44 static const TCHAR szUserClass[] = TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\CLSID\\");
45 static const TCHAR szSysClass[] = TEXT("CLSID\\");
46 static const TCHAR szDefaultIcon[] = TEXT("\\DefaultIcon");
47 static const TCHAR szFallbackIcon[] = TEXT("%SystemRoot%\\system32\\shell32.dll,0");
48
49 struct
50 {
51 LPCTSTR CLSID;
52 UINT TitleId;
53 LPCTSTR IconName;
54 } IconChange[NUM_CHANGE_ICONS] = {
55 {TEXT("{20D04FE0-3AEA-1069-A2D8-08002B30309D}"), IDS_TITLE_MYCOMP, NULL}, /* My Computer */
56 {TEXT("{450D8FBA-AD25-11D0-98A8-0800361B1103}"), IDS_PERSONAL, NULL}, /* My Documents */
57 {TEXT("{208D2C60-3AEA-1069-A2D7-08002B30309D}"), IDS_TITLE_MYNET, NULL}, /* My Network Places */
58 {TEXT("{645FF040-5081-101B-9F08-00AA002F954E}"), IDS_TITLE_BIN_1, TEXT("Full")}, /* Recycle Bin (full) */
59 {TEXT("{645FF040-5081-101B-9F08-00AA002F954E}"), IDS_TITLE_BIN_0, TEXT("Empty")}, /* Recycle Bin (empty) */
60 };
61
62 VOID
InitDesktopSettings(PDESKTOP_DATA pData)63 InitDesktopSettings(PDESKTOP_DATA pData)
64 {
65 UINT i;
66 TCHAR regPath[MAX_PATH];
67
68 /* Load desktop icon settings from the registry */
69 StringCchCopy(regPath, _countof(regPath), szHideDesktopIcons);
70 StringCchCat(regPath, _countof(regPath), szClassicStartMenu);
71
72 for (i = 0; i < _countof(pData->optIcons); i++)
73 {
74 pData->optIcons[i].bHideClassic = SHRegGetBoolUSValue(regPath, DesktopIcons[i].CLSID, FALSE, FALSE);
75 }
76
77 StringCchCopy(regPath, _countof(regPath), szHideDesktopIcons);
78 StringCchCat(regPath, _countof(regPath), szNewStartPanel);
79
80 for (i = 0; i < _countof(pData->optIcons); i++)
81 {
82 pData->optIcons[i].bHideNewStart = SHRegGetBoolUSValue(regPath, DesktopIcons[i].CLSID, FALSE, TRUE);
83 }
84
85 for (i = 0; i < _countof(IconChange); i++)
86 {
87 DWORD cbData, dwType;
88 TCHAR szData[MAX_PATH];
89
90 /* Current icons */
91 StringCchCopy(regPath, _countof(regPath), szUserClass);
92 StringCchCat(regPath, _countof(regPath), IconChange[i].CLSID);
93 StringCchCat(regPath, _countof(regPath), szDefaultIcon);
94 cbData = sizeof(szData);
95
96 if (SHGetValue(HKEY_CURRENT_USER, regPath, IconChange[i].IconName, &dwType,
97 &szData, &cbData) == ERROR_SUCCESS &&
98 (dwType == REG_SZ || dwType == REG_EXPAND_SZ))
99 {
100 StringCchCopy(pData->Icon[i].szPath, _countof(pData->Icon[i].szPath), szData);
101 }
102
103 /* Default icons */
104 /* FIXME: Get default icons from theme data, fallback to CLSID data on error. */
105 StringCchCopy(regPath, _countof(regPath), szSysClass);
106 StringCchCat(regPath, _countof(regPath), IconChange[i].CLSID);
107 StringCchCat(regPath, _countof(regPath), szDefaultIcon);
108 cbData = sizeof(szData);
109
110 if (SHGetValue(HKEY_CLASSES_ROOT, regPath, IconChange[i].IconName, &dwType,
111 &szData, &cbData) == ERROR_SUCCESS &&
112 (dwType == REG_SZ || dwType == REG_EXPAND_SZ))
113 {
114 StringCchCopy(pData->DefIcon[i].szPath, _countof(pData->DefIcon[i].szPath), szData);
115 }
116
117 /* Emergency fallback */
118 if (lstrlen(pData->DefIcon[i].szPath) == 0)
119 StringCchCopy(pData->DefIcon[i].szPath, _countof(pData->DefIcon[i].szPath), szFallbackIcon);
120
121 if (lstrlen(pData->Icon[i].szPath) == 0)
122 StringCchCopy(pData->Icon[i].szPath, _countof(pData->Icon[i].szPath), pData->DefIcon[i].szPath);
123 }
124 }
125
126 BOOL
SaveDesktopSettings(PDESKTOP_DATA pData)127 SaveDesktopSettings(PDESKTOP_DATA pData)
128 {
129 UINT i;
130
131 if (!pData->bLocalSettingsChanged)
132 return FALSE;
133
134 for (i = 0; i < _countof(DesktopIcons); i++)
135 {
136 if (!pData->bLocalHideChanged[i])
137 continue;
138
139 pData->optIcons[i].bHideClassic =
140 pData->optIcons[i].bHideNewStart = pData->bLocalHideIcon[i];
141 pData->bHideChanged[i] = TRUE;
142 }
143
144 for (i = 0; i < _countof(IconChange); i++)
145 {
146 if (!pData->bLocalIconChanged[i])
147 continue;
148
149 StringCchCopy(pData->Icon[i].szPath, _countof(pData->Icon[i].szPath), pData->LocalIcon[i].szPath);
150 pData->bIconChanged[i] = TRUE;
151 }
152
153 pData->bSettingsChanged = TRUE;
154 return TRUE;
155 }
156
157 static BOOL
GetCurrentValue(UINT i,BOOL bNewStart)158 GetCurrentValue(UINT i, BOOL bNewStart)
159 {
160 TCHAR regPath[MAX_PATH];
161
162 StringCchCopy(regPath, _countof(regPath), szHideDesktopIcons);
163 StringCchCat(regPath, _countof(regPath), bNewStart ? szNewStartPanel : szClassicStartMenu);
164
165 return SHRegGetBoolUSValue(regPath, DesktopIcons[i].CLSID, FALSE, bNewStart);
166 }
167
168 static VOID
SetCurrentValue(UINT i,BOOL bNewStart,BOOL bValue)169 SetCurrentValue(UINT i, BOOL bNewStart, BOOL bValue)
170 {
171 TCHAR regPath[MAX_PATH];
172
173 StringCchCopy(regPath, _countof(regPath), szHideDesktopIcons);
174 StringCchCat(regPath, _countof(regPath), bNewStart ? szNewStartPanel : szClassicStartMenu);
175
176 SHSetValue(HKEY_CURRENT_USER, regPath, DesktopIcons[i].CLSID, REG_DWORD,
177 (LPBYTE)&bValue, sizeof(bValue));
178 }
179
180 VOID
SetDesktopSettings(PDESKTOP_DATA pData)181 SetDesktopSettings(PDESKTOP_DATA pData)
182 {
183 UINT i;
184
185 for (i = 0; i < _countof(DesktopIcons); i++)
186 {
187 if (!pData->bHideChanged[i])
188 continue;
189
190 if (GetCurrentValue(i, FALSE) != pData->optIcons[i].bHideClassic)
191 SetCurrentValue(i, FALSE, pData->optIcons[i].bHideClassic);
192
193 if (GetCurrentValue(i, TRUE) != pData->optIcons[i].bHideNewStart)
194 SetCurrentValue(i, TRUE, pData->optIcons[i].bHideNewStart);
195
196 pData->bHideChanged[i] = FALSE;
197 }
198
199 for (i = 0; i < _countof(IconChange); i++)
200 {
201 TCHAR iconPath[MAX_PATH];
202 DWORD dwType = (pData->Icon[i].szPath[0] == TEXT('%') ? REG_EXPAND_SZ : REG_SZ);
203
204 if (!pData->bIconChanged[i])
205 continue;
206
207 StringCchCopy(iconPath, _countof(iconPath), szUserClass);
208 StringCchCat(iconPath, _countof(iconPath), IconChange[i].CLSID);
209 StringCchCat(iconPath, _countof(iconPath), szDefaultIcon);
210
211 SHSetValue(HKEY_CURRENT_USER, iconPath, IconChange[i].IconName, dwType,
212 pData->Icon[i].szPath, sizeof(pData->Icon[i].szPath));
213 if (IconChange[i].TitleId == IDS_TITLE_BIN_0)
214 {
215 /* Also apply to the root value */
216 SHSetValue(HKEY_CURRENT_USER, iconPath, NULL, dwType,
217 pData->Icon[i].szPath, sizeof(pData->Icon[i].szPath));
218 }
219 pData->bIconChanged[i] = FALSE;
220 }
221
222 pData->bSettingsChanged = FALSE;
223
224 /* Refresh the desktop */
225 PostMessage(GetShellWindow(), WM_COMMAND, FCIDM_DESKBROWSER_REFRESH, 0);
226 }
227
228 static HICON
GetIconFromLocation(LPTSTR szIconPath)229 GetIconFromLocation(LPTSTR szIconPath)
230 {
231 INT iIndex;
232 TCHAR szPath[MAX_PATH];
233
234 ExpandEnvironmentStrings(szIconPath, szPath, _countof(szPath));
235 iIndex = PathParseIconLocation(szPath);
236 return ExtractIcon(hApplet, szPath, iIndex);
237 }
238
239 static VOID
DesktopOnInitDialog(IN HWND hwndDlg,IN PDESKTOP_DATA pData)240 DesktopOnInitDialog(IN HWND hwndDlg, IN PDESKTOP_DATA pData)
241 {
242 UINT i;
243 SHELLSTATE ss = {0};
244 HWND hwndList;
245
246 SHGetSetSettings(&ss, SSF_STARTPANELON, FALSE);
247
248 for (i = 0; i < _countof(pData->optIcons); i++)
249 {
250 BOOL bHide;
251
252 if (ss.fStartPanelOn)
253 bHide = pData->optIcons[i].bHideNewStart;
254 else
255 bHide = pData->optIcons[i].bHideClassic;
256
257 CheckDlgButton(hwndDlg,
258 DesktopIcons[i].Checkbox,
259 bHide ? BST_UNCHECKED : BST_CHECKED);
260
261 pData->bLocalHideIcon[i] = bHide;
262 pData->bLocalHideChanged[i] = FALSE;
263 }
264
265 pData->iLocalCurIcon = 0;
266 hwndList = GetDlgItem(hwndDlg, IDC_ICONS_LISTVIEW);
267 pData->hLocalImageList = ImageList_Create(GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), ILC_COLOR32 | ILC_MASK, 1, 1);
268 ListView_SetImageList(hwndList, pData->hLocalImageList, LVSIL_NORMAL);
269
270 for (i = 0; i < _countof(IconChange); i++)
271 {
272 TCHAR szClassPath[MAX_PATH];
273 DWORD dwType, cbData;
274 LVITEM lvitem = {0};
275 HICON hIcon;
276
277 StringCchCopy(pData->LocalIcon[i].szPath, _countof(pData->LocalIcon[i].szPath), pData->Icon[i].szPath);
278 pData->bLocalIconChanged[i] = FALSE;
279
280 /* Try loading user-defined desktop icon title */
281 StringCchCopy(szClassPath, _countof(szClassPath), szUserClass);
282 StringCchCat(szClassPath, _countof(szClassPath), IconChange[i].CLSID);
283 cbData = sizeof(pData->LocalIcon[i].szTitle);
284
285 if (SHGetValue(HKEY_CURRENT_USER, szClassPath, IconChange[i].IconName, &dwType,
286 pData->LocalIcon[i].szTitle, &cbData) != ERROR_SUCCESS || dwType != REG_SZ)
287 {
288 /* Fallback to predefined strings */
289 LoadString(GetModuleHandle(TEXT("shell32.dll")),
290 IconChange[i].TitleId,
291 pData->LocalIcon[i].szTitle,
292 _countof(pData->LocalIcon[i].szTitle));
293 }
294
295 hIcon = GetIconFromLocation(pData->LocalIcon[i].szPath);
296
297 lvitem.mask = LVIF_TEXT | LVIF_IMAGE | LVIF_PARAM;
298 lvitem.iItem = i;
299 lvitem.iSubItem = 0;
300 lvitem.pszText = pData->LocalIcon[i].szTitle;
301 lvitem.lParam = (LPARAM)i;
302
303 if (hIcon)
304 {
305 if (pData->hLocalImageList)
306 lvitem.iImage = ImageList_AddIcon(pData->hLocalImageList, hIcon);
307 DestroyIcon(hIcon);
308 }
309
310 if (ListView_InsertItem(hwndList, &lvitem) < 0)
311 continue;
312
313 if (i > 0)
314 continue;
315
316 lvitem.state = LVIS_FOCUSED | LVIS_SELECTED;
317 lvitem.stateMask = LVIS_FOCUSED | LVIS_SELECTED;
318 SendMessage(hwndList, LVM_SETITEMSTATE, 0, (LPARAM)&lvitem);
319 }
320
321 pData->bLocalSettingsChanged = FALSE;
322 }
323
324 static VOID
DesktopOnDestroyDialog(IN HWND hwndDlg,IN PDESKTOP_DATA pData)325 DesktopOnDestroyDialog(IN HWND hwndDlg, IN PDESKTOP_DATA pData)
326 {
327 if (pData->hLocalImageList)
328 {
329 ListView_SetImageList(GetDlgItem(hwndDlg, IDC_ICONS_LISTVIEW), NULL, LVSIL_NORMAL);
330 ImageList_Destroy(pData->hLocalImageList);
331 }
332
333 SetWindowLongPtr(hwndDlg, DWLP_USER, 0);
334 }
335
336 /* Property page dialog callback */
337 INT_PTR CALLBACK
DesktopPageProc(IN HWND hwndDlg,IN UINT uMsg,IN WPARAM wParam,IN LPARAM lParam)338 DesktopPageProc(IN HWND hwndDlg, IN UINT uMsg, IN WPARAM wParam, IN LPARAM lParam)
339 {
340 PDESKTOP_DATA pData;
341
342 pData = (PDESKTOP_DATA)GetWindowLongPtr(hwndDlg, DWLP_USER);
343
344 switch (uMsg)
345 {
346 case WM_INITDIALOG:
347 {
348 LPPROPSHEETPAGE ppsp = (LPPROPSHEETPAGE)lParam;
349 pData = (PDESKTOP_DATA)ppsp->lParam;
350
351 SetWindowLongPtr(hwndDlg, DWLP_USER, (LONG_PTR)pData);
352 DesktopOnInitDialog(hwndDlg, pData);
353 break;
354 }
355
356 case WM_DESTROY:
357 {
358 DesktopOnDestroyDialog(hwndDlg, pData);
359 break;
360 }
361
362 case WM_COMMAND:
363 {
364 DWORD controlId = LOWORD(wParam);
365 DWORD command = HIWORD(wParam);
366
367 if (command == BN_CLICKED)
368 {
369 UINT i;
370 BOOL bUpdateIcon = FALSE;
371
372 for (i = 0; i < _countof(DesktopIcons); i++)
373 {
374 if (DesktopIcons[i].Checkbox == controlId)
375 {
376 pData->bLocalHideIcon[i] =
377 (IsDlgButtonChecked(hwndDlg, DesktopIcons[i].Checkbox) == BST_UNCHECKED);
378
379 pData->bLocalSettingsChanged =
380 pData->bLocalHideChanged[i] = TRUE;
381
382 PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
383 break;
384 }
385 }
386
387 if (controlId == IDC_ICONS_CHANGEICON)
388 {
389 TCHAR szPath[MAX_PATH];
390 INT iIndex;
391 i = pData->iLocalCurIcon;
392
393 ExpandEnvironmentStrings(pData->LocalIcon[i].szPath, szPath, _countof(szPath));
394 iIndex = PathParseIconLocation(szPath);
395
396 if (PickIconDlg(hwndDlg, szPath, _countof(szPath), &iIndex))
397 {
398 StringCchCopy(pData->LocalIcon[i].szPath, _countof(pData->LocalIcon[i].szPath), szPath);
399 PathUnExpandEnvStrings(pData->LocalIcon[i].szPath, szPath, _countof(szPath));
400
401 StringCchPrintf(pData->LocalIcon[i].szPath, _countof(pData->LocalIcon[i].szPath), TEXT("%s,%d"), szPath, iIndex);
402 bUpdateIcon = TRUE;
403 }
404 }
405 else if (controlId == IDC_ICONS_SETDEFAULT)
406 {
407 i = pData->iLocalCurIcon;
408
409 StringCchCopy(pData->LocalIcon[i].szPath, _countof(pData->LocalIcon[i].szPath), pData->DefIcon[i].szPath);
410 bUpdateIcon = TRUE;
411 }
412
413 if (bUpdateIcon)
414 {
415 HWND hwndList = GetDlgItem(hwndDlg, IDC_ICONS_LISTVIEW);
416 HICON hIcon;
417
418 hIcon = GetIconFromLocation(pData->LocalIcon[i].szPath);
419
420 if (hIcon)
421 {
422 if (pData->hLocalImageList)
423 ImageList_ReplaceIcon(pData->hLocalImageList, i, hIcon);
424 DestroyIcon(hIcon);
425 }
426
427 pData->bLocalSettingsChanged =
428 pData->bLocalIconChanged[i] = TRUE;
429
430 InvalidateRect(hwndList, NULL, TRUE);
431 SetFocus(hwndList);
432 PropSheet_Changed(GetParent(hwndDlg), hwndDlg);
433 }
434 }
435 break;
436 }
437
438 case WM_NOTIFY:
439 {
440 LPNMLISTVIEW nm = (LPNMLISTVIEW)lParam;
441
442 switch (nm->hdr.code)
443 {
444 case LVN_ITEMCHANGED:
445 {
446 if ((nm->uNewState & LVIS_SELECTED) == 0)
447 return FALSE;
448
449 pData->iLocalCurIcon = nm->iItem;
450 break;
451 }
452 }
453 break;
454 }
455 }
456
457 return FALSE;
458 }
459