reactos/dll/cpl/desk/desk.c
Hermès Bélusca-Maïto b3a25bcf8b
[DESK] Reimplement InstallScreenSaverW to behave similarly to Windows' one (#5766)
CORE-6812 ; see also commit ff0951356 (r66688)

This function "just" changes the per-user SCRNSAVE.EXE registry value
to point to the new specified file, changes the SPI values and opens
the desk.cpl "Screensaver" property tab for letting the user change
the new selected screensaver properties.

In particular, it does *NOT* copy the specified file to System32 or
anything else (and doesn't verify that it is a valid PE executable).

Our previous implementation did none of that, and was also relying on
some private setupapi functions.

We now behave closer to Windows' desk.cpl.

Additionally:

- ReactOS-specific feature (compile-time define) disabled by default:
  Verify that the specified file actually exists, before changing
  the screensaver.
- Use NDEBUG, disabling DPRINT by default;
- Improve InstallScreenSaver[A,W] spec entries;
- Remove NUM_APPLETS define and use _countof() instead.
2023-10-18 21:45:39 +02:00

480 lines
13 KiB
C

/*
* COPYRIGHT: See COPYING in the top level directory
* PROJECT: ReactOS Display Control Panel
* FILE: dll/cpl/desk/desk.c
* PURPOSE: ReactOS Display Control Panel
*
* PROGRAMMERS: Trevor McCort (lycan359@gmail.com)
*/
#include "desk.h"
#include <shellapi.h>
#include <cplext.h>
#include <winnls.h>
#define NDEBUG
#include <debug.h>
/* Enable this for InstallScreenSaverW() to determine a possible full path
* to the specified screensaver file, verify its existence and use it.
* (NOTE: This is not Windows desk.cpl-compatible.) */
// #define CHECK_SCR_FULL_PATH
VOID WINAPI Control_RunDLLW(HWND hWnd, HINSTANCE hInst, LPCWSTR cmd, DWORD nCmdShow);
static LONG APIENTRY DisplayApplet(HWND hwnd, UINT uMsg, LPARAM wParam, LPARAM lParam);
INT_PTR CALLBACK ThemesPageProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
INT_PTR CALLBACK BackgroundPageProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
INT_PTR CALLBACK ScreenSaverPageProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
INT_PTR CALLBACK AppearancePageProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
INT_PTR CALLBACK SettingsPageProc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
UINT CALLBACK SettingsPageCallbackProc(HWND hwnd, UINT uMsg, LPPROPSHEETPAGE ppsp);
HINSTANCE hApplet = NULL;
HWND hCPLWindow = NULL;
/* Applets */
APPLET Applets[] =
{
{
IDC_DESK_ICON,
IDS_CPLNAME,
IDS_CPLDESCRIPTION,
DisplayApplet
}
};
HMENU
LoadPopupMenu(IN HINSTANCE hInstance,
IN LPCTSTR lpMenuName)
{
HMENU hMenu, hSubMenu = NULL;
hMenu = LoadMenu(hInstance,
lpMenuName);
if (hMenu != NULL)
{
hSubMenu = GetSubMenu(hMenu,
0);
if (hSubMenu != NULL &&
!RemoveMenu(hMenu,
0,
MF_BYPOSITION))
{
hSubMenu = NULL;
}
DestroyMenu(hMenu);
}
return hSubMenu;
}
static BOOL CALLBACK
DisplayAppletPropSheetAddPage(HPROPSHEETPAGE hpage, LPARAM lParam)
{
PROPSHEETHEADER *ppsh = (PROPSHEETHEADER *)lParam;
if (ppsh != NULL && ppsh->nPages < MAX_DESK_PAGES)
{
ppsh->phpage[ppsh->nPages++] = hpage;
return TRUE;
}
return FALSE;
}
static BOOL
InitPropSheetPage(PROPSHEETHEADER *ppsh, WORD idDlg, DLGPROC DlgProc, LPFNPSPCALLBACK pfnCallback)
{
HPROPSHEETPAGE hPage;
PROPSHEETPAGE psp;
if (ppsh->nPages < MAX_DESK_PAGES)
{
ZeroMemory(&psp, sizeof(psp));
psp.dwSize = sizeof(psp);
psp.dwFlags = PSP_DEFAULT;
if (pfnCallback != NULL)
psp.dwFlags |= PSP_USECALLBACK;
psp.hInstance = hApplet;
psp.pszTemplate = MAKEINTRESOURCE(idDlg);
psp.pfnDlgProc = DlgProc;
psp.pfnCallback = pfnCallback;
hPage = CreatePropertySheetPage(&psp);
if (hPage != NULL)
{
return DisplayAppletPropSheetAddPage(hPage, (LPARAM)ppsh);
}
}
return FALSE;
}
static const struct
{
WORD idDlg;
DLGPROC DlgProc;
LPFNPSPCALLBACK Callback;
LPWSTR Name;
} PropPages[] =
{
/* { IDD_THEMES, ThemesPageProc, NULL, L"Themes" }, */ /* TODO: */
{ IDD_BACKGROUND, BackgroundPageProc, NULL, L"Desktop" },
{ IDD_SCREENSAVER, ScreenSaverPageProc, NULL, L"Screen Saver" },
{ IDD_APPEARANCE, AppearancePageProc, NULL, L"Appearance" },
{ IDD_SETTINGS, SettingsPageProc, SettingsPageCallbackProc, L"Settings" },
};
static int CALLBACK
PropSheetProc(HWND hwndDlg, UINT uMsg, LPARAM lParam)
{
// NOTE: This callback is needed to set large icon correctly.
HICON hIcon;
switch (uMsg)
{
case PSCB_INITIALIZED:
{
hIcon = LoadIconW(hApplet, MAKEINTRESOURCEW(IDC_DESK_ICON));
SendMessageW(hwndDlg, WM_SETICON, ICON_BIG, (LPARAM)hIcon);
break;
}
}
return 0;
}
/* Display Applet */
static LONG APIENTRY
DisplayApplet(HWND hwnd, UINT uMsg, LPARAM wParam, LPARAM lParam)
{
HPROPSHEETPAGE hpsp[MAX_DESK_PAGES];
PROPSHEETHEADER psh;
HPSXA hpsxa = NULL;
UINT i;
LPWSTR *argv = NULL;
LPCWSTR pwszSelectedTab = NULL;
LPCWSTR pwszFile = NULL;
LPCWSTR pwszAction = NULL;
INT nPage = 0;
BITMAP bitmap;
UNREFERENCED_PARAMETER(wParam);
hCPLWindow = hwnd;
if (uMsg == CPL_STARTWPARMSW && lParam)
{
int argc;
int i;
nPage = _wtoi((PWSTR)lParam);
#if 0
argv = CommandLineToArgvW((LPCWSTR)lParam, &argc);
#else
argv = CommandLineToArgvW(GetCommandLineW(), &argc);
#endif
if (argv && argc)
{
for (i = 0; i<argc; i++)
{
#if 0
if (argv[i][0] == L'@')
pwszSelectedTab = &argv[i][1];
#else
if (wcsncmp(argv[i], L"desk,@", 6) == 0)
pwszSelectedTab = &argv[i][6];
#endif
else if (wcsncmp(argv[i], L"/Action:", 8) == 0)
pwszAction = &argv[i][8];
else if (wcsncmp(argv[i], L"/file:", 6) == 0)
pwszFile = &argv[i][6];
}
}
}
if(pwszAction && wcsncmp(pwszAction, L"ActivateMSTheme", 15) == 0)
{
ActivateThemeFile(pwszFile);
goto cleanup;
}
g_GlobalData.pwszFile = pwszFile;
g_GlobalData.pwszAction = pwszAction;
g_GlobalData.desktop_color = GetSysColor(COLOR_DESKTOP);
/* Initialize the monitor preview bitmap, used on multiple pages */
g_GlobalData.hMonitorBitmap = LoadBitmapW(hApplet, MAKEINTRESOURCEW(IDC_MONITOR));
if (g_GlobalData.hMonitorBitmap != NULL)
{
GetObjectW(g_GlobalData.hMonitorBitmap, sizeof(bitmap), &bitmap);
g_GlobalData.bmMonWidth = bitmap.bmWidth;
g_GlobalData.bmMonHeight = bitmap.bmHeight;
}
ZeroMemory(&psh, sizeof(psh));
psh.dwSize = sizeof(psh);
psh.dwFlags = PSH_USECALLBACK | PSH_PROPTITLE | PSH_USEICONID;
psh.hwndParent = hCPLWindow;
psh.hInstance = hApplet;
psh.pszIcon = MAKEINTRESOURCEW(IDC_DESK_ICON);
psh.pszCaption = MAKEINTRESOURCEW(IDS_CPLNAME);
psh.nPages = 0;
psh.nStartPage = 0;
psh.phpage = hpsp;
psh.pfnCallback = PropSheetProc;
/* Allow shell extensions to replace the background page */
hpsxa = SHCreatePropSheetExtArray(HKEY_LOCAL_MACHINE, REGSTR_PATH_CONTROLSFOLDER TEXT("\\Desk"), MAX_DESK_PAGES - psh.nPages);
for (i = 0; i < _countof(PropPages); i++)
{
if (pwszSelectedTab && wcsicmp(pwszSelectedTab, PropPages[i].Name) == 0)
psh.nStartPage = i;
/* Override the background page if requested by a shell extension */
if (PropPages[i].idDlg == IDD_BACKGROUND && hpsxa != NULL &&
SHReplaceFromPropSheetExtArray(hpsxa, CPLPAGE_DISPLAY_BACKGROUND, DisplayAppletPropSheetAddPage, (LPARAM)&psh) != 0)
{
/* The shell extension added one or more pages to replace the background page.
Don't create the built-in page anymore! */
continue;
}
InitPropSheetPage(&psh, PropPages[i].idDlg, PropPages[i].DlgProc, PropPages[i].Callback);
}
/* NOTE: Don't call SHAddFromPropSheetExtArray here because this applet only allows
replacing the background page but not extending the applet by more pages */
if (nPage != 0 && psh.nStartPage == 0)
psh.nStartPage = nPage;
PropertySheet(&psh);
cleanup:
DeleteObject(g_GlobalData.hMonitorBitmap);
if (hpsxa != NULL)
SHDestroyPropSheetExtArray(hpsxa);
if (argv)
LocalFree(argv);
return TRUE;
}
/* Control Panel Callback */
LONG CALLBACK
CPlApplet(HWND hwndCPl, UINT uMsg, LPARAM lParam1, LPARAM lParam2)
{
UINT i = (UINT)lParam1;
switch (uMsg)
{
case CPL_INIT:
return TRUE;
case CPL_GETCOUNT:
return _countof(Applets);
case CPL_INQUIRE:
if (i < _countof(Applets))
{
CPLINFO *CPlInfo = (CPLINFO*)lParam2;
CPlInfo->lData = 0;
CPlInfo->idIcon = Applets[i].idIcon;
CPlInfo->idName = Applets[i].idName;
CPlInfo->idInfo = Applets[i].idDescription;
}
else
{
return TRUE;
}
break;
case CPL_DBLCLK:
if (i < _countof(Applets))
Applets[i].AppletProc(hwndCPl, uMsg, lParam1, lParam2);
else
return TRUE;
break;
case CPL_STARTWPARMSW:
if (i < _countof(Applets))
return Applets[i].AppletProc(hwndCPl, uMsg, lParam1, lParam2);
break;
}
return FALSE;
}
void
WINAPI
InstallScreenSaverW(
IN HWND hWindow,
IN HANDLE hInstance,
IN LPCWSTR pszFile,
IN UINT nCmdShow)
{
LRESULT rc;
HKEY regKey;
INT Timeout;
#ifdef CHECK_SCR_FULL_PATH
HANDLE hFile;
WIN32_FIND_DATAW fdFile;
#endif
DWORD dwLen;
WCHAR szFullPath[MAX_PATH];
if (!pszFile)
{
DPRINT1("InstallScreenSaver() null file\n");
SetLastError(ERROR_INVALID_PARAMETER);
return;
}
DPRINT("InstallScreenSaver() Installing screensaver %ls\n", pszFile);
#ifdef CHECK_SCR_FULL_PATH
/* Retrieve the actual path to the file and verify whether it exists */
dwLen = GetFullPathNameW(pszFile, _countof(szFullPath), szFullPath, NULL);
if (dwLen == 0 || dwLen > _countof(szFullPath))
{
DPRINT1("InstallScreenSaver() File %ls not accessible\n", pszFile);
return;
}
hFile = FindFirstFile(szFullPath, &fdFile);
if (hFile == INVALID_HANDLE_VALUE)
{
DPRINT1("InstallScreenSaver() File %ls not found\n", pszFile);
return;
}
FindClose(hFile);
/* Use the full file path from now on */
pszFile = szFullPath;
#endif
rc = RegOpenKeyExW(HKEY_CURRENT_USER,
L"Control Panel\\Desktop",
0,
KEY_SET_VALUE,
&regKey);
if (rc == ERROR_SUCCESS)
{
/* Set the screensaver */
SIZE_T Length = (wcslen(pszFile) + 1) * sizeof(WCHAR);
rc = RegSetValueExW(regKey,
L"SCRNSAVE.EXE",
0,
REG_SZ,
(PBYTE)pszFile,
(DWORD)Length);
RegCloseKey(regKey);
}
if (rc != ERROR_SUCCESS)
{
DPRINT1("InstallScreenSaver() Could not change the current screensaver\n");
return;
}
SystemParametersInfoW(SPI_SETSCREENSAVEACTIVE, TRUE, 0, SPIF_UPDATEINIFILE);
/* If no screensaver timeout is present, default to 10 minutes (600 seconds) */
Timeout = 0;
if (!SystemParametersInfoW(SPI_GETSCREENSAVETIMEOUT, 0, &Timeout, 0) || (Timeout <= 0))
SystemParametersInfoW(SPI_SETSCREENSAVETIMEOUT, 600, 0, SPIF_UPDATEINIFILE);
/* Retrieve the name of this current instance of desk.cpl */
dwLen = GetModuleFileNameW(hApplet, szFullPath, _countof(szFullPath));
if ((dwLen == 0) || (GetLastError() == ERROR_INSUFFICIENT_BUFFER))
{
/* We failed, copy the default value */
StringCchCopyW(szFullPath, _countof(szFullPath), L"desk.cpl");
}
/* Build the desk.cpl command-line to start the ScreenSaver page.
* Equivalent to: "desk.cpl,ScreenSaver,@ScreenSaver" */
rc = StringCchCatW(szFullPath, _countof(szFullPath), L",,1");
if (FAILED(rc))
return;
/* Open the ScreenSaver page in this desk.cpl instance */
DPRINT("InstallScreenSaver() Starting '%ls'\n", szFullPath);
Control_RunDLLW(hWindow, hInstance, szFullPath, nCmdShow);
}
void
WINAPI
InstallScreenSaverA(
IN HWND hWindow,
IN HANDLE hInstance,
IN LPCSTR pszFile,
IN UINT nCmdShow)
{
LPWSTR lpwString;
int nLength;
if (!pszFile)
{
DPRINT1("InstallScreenSaver() null file\n");
SetLastError(ERROR_INVALID_PARAMETER);
return;
}
/* Convert the string to unicode */
lpwString = NULL;
nLength = MultiByteToWideChar(CP_ACP, 0, pszFile, -1, NULL, 0);
if (nLength != 0)
{
lpwString = LocalAlloc(LMEM_FIXED, nLength * sizeof(WCHAR));
if (lpwString)
{
if (!MultiByteToWideChar(CP_ACP, 0, pszFile, -1, lpwString, nLength))
{
LocalFree(lpwString);
lpwString = NULL;
}
}
}
if (!lpwString)
{
DPRINT1("InstallScreenSaver() not enough memory to convert string to unicode\n");
return;
}
/* Call the unicode function */
InstallScreenSaverW(hWindow, hInstance, lpwString, nCmdShow);
LocalFree(lpwString);
}
BOOL WINAPI
DllMain(HINSTANCE hInstDLL, DWORD dwReason, LPVOID lpvReserved)
{
UNREFERENCED_PARAMETER(lpvReserved);
switch (dwReason)
{
case DLL_PROCESS_ATTACH:
CoInitializeEx(NULL, COINIT_APARTMENTTHREADED);
RegisterPreviewControl(hInstDLL);
// case DLL_THREAD_ATTACH:
hApplet = hInstDLL;
break;
case DLL_PROCESS_DETACH:
UnregisterPreviewControl(hInstDLL);
CoUninitialize();
break;
}
return TRUE;
}