xref: /reactos/dll/cpl/desk/desktop.c (revision 9c21d0c1)
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