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 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 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 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 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 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 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 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 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 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