/*
 * COPYRIGHT:        GPL, see COPYING in the top level directory
 * PROJECT:          ReactOS win32 kernel mode subsystem server
 * PURPOSE:          System parameters functions
 * FILE:             subsystems/win32/win32k/ntuser/sysparams.c
 * PROGRAMER:        Timo Kreuzer (timo.kreuzer@reactos.org)
 */

// TODO:
// - Check all values that are in Winsta in ROS.
// - Does setting invalid fonts work?
// - Save appropriate text metrics.

#include <win32k.h>
DBG_DEFAULT_CHANNEL(UserSysparams);

SPIVALUES gspv;
BOOL gbSpiInitialized = FALSE;
BOOL g_PaintDesktopVersion = FALSE;

// HACK! We initialize SPI before we have a proper surface to get this from.
#define dpi 96
//(pPrimarySurface->GDIInfo.ulLogPixelsY)
#define REG2METRIC(reg) (reg > 0 ? reg : ((-(reg) * dpi + 720) / 1440))
#define METRIC2REG(met) (-((((met) * 1440)- 0) / dpi))

#define REQ_INTERACTIVE_WINSTA(err) \
    if (GetW32ProcessInfo()->prpwinsta != InputWindowStation) \
    { \
        if (GetW32ProcessInfo()->prpwinsta == NULL) \
        { \
            ERR("NtUserSystemParametersInfo called without active window station, and it requires an interactive one\n"); \
        } \
        else \
        { \
            ERR("NtUserSystemParametersInfo requires interactive window station (current is %wZ)\n", &GetW32ProcessInfo()->prpwinsta->Name); \
        } \
        EngSetLastError(err); \
        return 0; \
    }

static const WCHAR* KEY_MOUSE = L"Control Panel\\Mouse";
static const WCHAR* VAL_MOUSE1 = L"MouseThreshold1";
static const WCHAR* VAL_MOUSE2 = L"MouseThreshold2";
static const WCHAR* VAL_MOUSE3 = L"MouseSpeed";
static const WCHAR* VAL_MOUSETRAILS = L"MouseTrails";
static const WCHAR* VAL_DBLCLKWIDTH = L"DoubleClickWidth";
static const WCHAR* VAL_DBLCLKHEIGHT = L"DoubleClickHeight";
static const WCHAR* VAL_DBLCLKTIME = L"DoubleClickSpeed";
static const WCHAR* VAL_SNAPDEFBTN = L"SnapToDefaultButton";
static const WCHAR* VAL_SWAP = L"SwapMouseButtons";
static const WCHAR* VAL_HOVERTIME = L"MouseHoverTime";
static const WCHAR* VAL_HOVERWIDTH = L"MouseHoverWidth";
static const WCHAR* VAL_HOVERHEIGHT = L"MouseHoverHeight";
static const WCHAR* VAL_SENSITIVITY = L"MouseSensitivity";

static const WCHAR* KEY_DESKTOP = L"Control Panel\\Desktop";
static const WCHAR* VAL_SCRTO = L"ScreenSaveTimeOut";
static const WCHAR* VAL_SCRNSV = L"SCRNSAVE.EXE";
static const WCHAR* VAL_SCRACT = L"ScreenSaveActive";
static const WCHAR* VAL_GRID = L"GridGranularity";
static const WCHAR* VAL_DRAG = L"DragFullWindows";
static const WCHAR* VAL_DRAGHEIGHT = L"DragHeight";
static const WCHAR* VAL_DRAGWIDTH = L"DragWidth";
static const WCHAR* VAL_FNTSMOOTH = L"FontSmoothing";
static const WCHAR* VAL_SCRLLLINES = L"WheelScrollLines";
static const WCHAR* VAL_CLICKLOCKTIME = L"ClickLockTime";
static const WCHAR* VAL_PAINTDESKVER = L"PaintDesktopVersion";
static const WCHAR* VAL_CARETRATE = L"CursorBlinkRate";
#if (_WIN32_WINNT >= 0x0600)
static const WCHAR* VAL_SCRLLCHARS = L"WheelScrollChars";
#endif
static const WCHAR* VAL_USERPREFMASK = L"UserPreferencesMask";

static const WCHAR* KEY_MDALIGN = L"Software\\Microsoft\\Windows NT\\CurrentVersion\\Windows";
static const WCHAR* VAL_MDALIGN = L"MenuDropAlignment";

static const WCHAR* KEY_METRIC = L"Control Panel\\Desktop\\WindowMetrics";
static const WCHAR* VAL_BORDER = L"BorderWidth";
static const WCHAR* VAL_ICONSPC = L"IconSpacing";
static const WCHAR* VAL_ICONVSPC = L"IconVerticalspacing";
static const WCHAR* VAL_ITWRAP = L"IconTitleWrap";

static const WCHAR* KEY_SOUND = L"Control Panel\\Sound";
static const WCHAR* VAL_BEEP = L"Beep";

static const WCHAR* KEY_KBD = L"Control Panel\\Keyboard";
static const WCHAR* VAL_KBDSPD = L"KeyboardSpeed";
static const WCHAR* VAL_KBDDELAY = L"KeyboardDelay";

static const WCHAR* KEY_SHOWSNDS = L"Control Panel\\Accessibility\\ShowSounds";
static const WCHAR* KEY_KDBPREF = L"Control Panel\\Accessibility\\Keyboard Preference";
static const WCHAR* KEY_SCRREAD = L"Control Panel\\Accessibility\\Blind Access";
static const WCHAR* VAL_ON = L"On";



/** Loading the settings ******************************************************/

static
INT
SpiLoadDWord(PCWSTR pwszKey, PCWSTR pwszValue, INT iValue)
{
    DWORD Result;
    if (!RegReadUserSetting(pwszKey, pwszValue, REG_DWORD, &Result, sizeof(Result)))
    {
        return iValue;
    }
    return Result;
}

static
INT
SpiLoadInt(PCWSTR pwszKey, PCWSTR pwszValue, INT iValue)
{
    WCHAR awcBuffer[12];
    ULONG cbSize;

    cbSize = sizeof(awcBuffer);
    if (!RegReadUserSetting(pwszKey, pwszValue, REG_SZ, awcBuffer, cbSize))
    {
        return iValue;
    }
    return _wtoi(awcBuffer);
}

static
DWORD
SpiLoadUserPrefMask(DWORD dValue)
{
    DWORD Result;
    if (!RegReadUserSetting(KEY_DESKTOP, VAL_USERPREFMASK, REG_BINARY, &Result, sizeof(Result)))
    {
        return dValue;
    }
    return Result;
}

static
DWORD
SpiLoadTimeOut(VOID)
{   // Must have the string!
    WCHAR szApplicationName[MAX_PATH];
    RtlZeroMemory(&szApplicationName, sizeof(szApplicationName));
    if (!RegReadUserSetting(KEY_DESKTOP, VAL_SCRNSV, REG_SZ, &szApplicationName, sizeof(szApplicationName)))
    {
        return 0;
    }
    if (wcslen(szApplicationName) == 0) return 0;
    return SpiLoadInt(KEY_DESKTOP, VAL_SCRTO, 0);
}

static
INT
SpiLoadMouse(PCWSTR pwszValue, INT iValue)
{
    return SpiLoadInt(KEY_MOUSE, pwszValue, iValue);
}

static
INT
SpiLoadMetric(PCWSTR pwszValue, INT iValue)
{
    INT iRegVal;

    iRegVal = SpiLoadInt(KEY_METRIC, pwszValue, METRIC2REG(iValue));
    TRACE("Loaded metric setting '%S', iValue=%d(reg:%d), ret=%d(reg:%d)\n",
           pwszValue, iValue, METRIC2REG(iValue), REG2METRIC(iRegVal), iRegVal);
    return REG2METRIC(iRegVal);
}

static
VOID
SpiLoadFont(PLOGFONTW plfOut, LPWSTR pwszValueName, PLOGFONTW plfDefault)
{
    BOOL bResult;

    bResult = RegReadUserSetting(KEY_METRIC,
                                 pwszValueName,
                                 REG_BINARY,
                                 plfOut,
                                 sizeof(LOGFONTW));
    if (!bResult)
        *plfOut = *plfDefault;
}

static
VOID
SpiFixupValues()
{
    /* Fixup values */
    gspv.ncm.iCaptionWidth = max(gspv.ncm.iCaptionWidth, 8);
    gspv.ncm.iBorderWidth = max(gspv.ncm.iBorderWidth, 1);
    gspv.ncm.iScrollWidth = max(gspv.ncm.iScrollWidth, 8);
    gspv.ncm.iScrollHeight = max(gspv.ncm.iScrollHeight, 8);
//    gspv.ncm.iMenuHeight = max(gspv.ncm.iMenuHeight, gspv.tmMenuFont.tmHeight);
//    gspv.ncm.iMenuHeight = max(gspv.ncm.iMenuHeight,
//                               2 + gspv.tmMenuFont.tmHeight +
//                               gspv.tmMenuFont.tmExternalLeading);
    if (gspv.iDblClickTime == 0) gspv.iDblClickTime = 500;

    // FIXME: Hack!!!
    gspv.tmMenuFont.tmHeight = 11;
    gspv.tmMenuFont.tmExternalLeading = 2;

    gspv.tmCaptionFont.tmHeight = 11;
    gspv.tmCaptionFont.tmExternalLeading = 2;

}

static
VOID
SpiUpdatePerUserSystemParameters()
{
    static LOGFONTW lf1 = {-11, 0, 0, 0, FW_NORMAL, FALSE, FALSE,
                           FALSE, ANSI_CHARSET, 0, 0, DEFAULT_QUALITY,
                           VARIABLE_PITCH | FF_SWISS, L"MS Sans Serif"};
    static LOGFONTW lf2 = {-11, 0, 0, 0, FW_BOLD, FALSE, FALSE,
                           FALSE, ANSI_CHARSET, 0, 0, DEFAULT_QUALITY,
                           VARIABLE_PITCH | FF_SWISS, L"MS Sans Serif"};

    TRACE("Enter SpiUpdatePerUserSystemParameters\n");

    /* Clear the structure */
    memset(&gspv, 0, sizeof(gspv));

    /* Load mouse settings */
    gspv.caiMouse.FirstThreshold = SpiLoadMouse(VAL_MOUSE1, 6);
    gspv.caiMouse.SecondThreshold = SpiLoadMouse(VAL_MOUSE2, 10);
    gspv.caiMouse.Acceleration = SpiLoadMouse(VAL_MOUSE3, 1);
    gspv.iMouseSpeed = SpiLoadMouse(VAL_SENSITIVITY, 10);
    gspv.bMouseBtnSwap = SpiLoadMouse(VAL_SWAP, 0);
    gspv.bSnapToDefBtn = SpiLoadMouse(VAL_SNAPDEFBTN, 0);
    gspv.iMouseTrails = SpiLoadMouse(VAL_MOUSETRAILS, 0);
    gspv.iDblClickTime = SpiLoadMouse(VAL_DBLCLKTIME, 500);
    gspv.iDblClickWidth = SpiLoadMouse(VAL_DBLCLKWIDTH, 4);
    gspv.iDblClickHeight = SpiLoadMouse(VAL_DBLCLKHEIGHT, 4);
    gspv.iMouseHoverTime = SpiLoadMouse(VAL_HOVERTIME, 400);
    gspv.iMouseHoverWidth = SpiLoadMouse(VAL_HOVERWIDTH, 4);
    gspv.iMouseHoverHeight = SpiLoadMouse(VAL_HOVERHEIGHT, 4);

    /* Load NONCLIENTMETRICS */
    gspv.ncm.cbSize = sizeof(NONCLIENTMETRICSW);
    gspv.ncm.iBorderWidth = SpiLoadMetric(VAL_BORDER, 1);
    gspv.ncm.iScrollWidth = SpiLoadMetric(L"ScrollWidth", 16);
    gspv.ncm.iScrollHeight = SpiLoadMetric(L"ScrollHeight", 16);
    gspv.ncm.iCaptionWidth = SpiLoadMetric(L"CaptionWidth", 19);
    gspv.ncm.iCaptionHeight = SpiLoadMetric(L"CaptionHeight", 19);
    gspv.ncm.iSmCaptionWidth = SpiLoadMetric(L"SmCaptionWidth", 12);
    gspv.ncm.iSmCaptionHeight =  SpiLoadMetric(L"SmCaptionHeight", 14);
    gspv.ncm.iMenuWidth = SpiLoadMetric(L"MenuWidth", 18);
    gspv.ncm.iMenuHeight = SpiLoadMetric(L"MenuHeight", 18);
#if (WINVER >= 0x0600)
    gspv.ncm.iPaddedBorderWidth = SpiLoadMetric(L"PaddedBorderWidth", 18);
#endif
    SpiLoadFont(&gspv.ncm.lfCaptionFont, L"CaptionFont", &lf2);
    SpiLoadFont(&gspv.ncm.lfSmCaptionFont, L"SmCaptionFont", &lf1);
    SpiLoadFont(&gspv.ncm.lfMenuFont, L"MenuFont", &lf1);
    SpiLoadFont(&gspv.ncm.lfStatusFont, L"StatusFont", &lf1);
    SpiLoadFont(&gspv.ncm.lfMessageFont, L"MessageFont", &lf1);

    /* Load MINIMIZEDMETRICS */
    gspv.mm.cbSize = sizeof(MINIMIZEDMETRICS);
    gspv.mm.iWidth = SpiLoadMetric(L"MinWidth", 160);
    gspv.mm.iHorzGap = SpiLoadMetric(L"MinHorzGap", 160);
    gspv.mm.iVertGap = SpiLoadMetric(L"MinVertGap", 24);
    gspv.mm.iArrange = SpiLoadInt(KEY_METRIC, L"MinArrange", ARW_HIDE);

    /* Load ICONMETRICS */
    gspv.im.cbSize = sizeof(ICONMETRICSW);
    gspv.im.iHorzSpacing = SpiLoadMetric(VAL_ICONSPC, 64);
    gspv.im.iVertSpacing = SpiLoadMetric(VAL_ICONVSPC, 64);
    gspv.im.iTitleWrap = SpiLoadMetric(VAL_ITWRAP, 0);
    SpiLoadFont(&gspv.im.lfFont, L"IconFont", &lf1);

    /* Load desktop settings */
    gspv.bDragFullWindows = SpiLoadInt(KEY_DESKTOP, VAL_DRAG, 0);
    gspv.iWheelScrollLines = SpiLoadInt(KEY_DESKTOP, VAL_SCRLLLINES, 3);
    gspv.dwMouseClickLockTime = SpiLoadDWord(KEY_DESKTOP, VAL_CLICKLOCKTIME, 1200);
    gpsi->dtCaretBlink = SpiLoadInt(KEY_DESKTOP, VAL_CARETRATE, 530);
    gspv.dwUserPrefMask = SpiLoadUserPrefMask(UPM_DEFAULT);
    gspv.bMouseClickLock = (gspv.dwUserPrefMask & UPM_CLICKLOCK) != 0;
    gspv.bMouseCursorShadow = (gspv.dwUserPrefMask & UPM_CURSORSHADOW) != 0;
#if (_WIN32_WINNT >= 0x0600)
    gspv.iWheelScrollChars = SpiLoadInt(KEY_DESKTOP, VAL_SCRLLCHARS, 3);
#endif

    /* Some hardcoded values for now */

    gspv.tmCaptionFont.tmAveCharWidth = 6;
    gspv.bBeep = TRUE;
    gspv.bFlatMenu = FALSE;
    gspv.uiFocusBorderWidth = 1;
    gspv.uiFocusBorderHeight = 1;
    gspv.bMenuDropAlign = 1;
    gspv.bDropShadow = 1;
    gspv.dwMenuShowDelay = 100;

    gspv.iScrSaverTimeout = SpiLoadTimeOut();
    gspv.bScrSaverActive = FALSE;
    gspv.bScrSaverRunning = FALSE;
#if(WINVER >= 0x0600)
    gspv.bScrSaverSecure = FALSE;
#endif

    /* Make sure we don't use broken values */
    SpiFixupValues();

    /* Update SystemMetrics */
    InitMetrics();

    if (gbSpiInitialized && gpsi)
    {
       if (gspv.bKbdPref) gpsi->dwSRVIFlags |= SRVINFO_KBDPREF;
       if (SPITESTPREF(UPM_KEYBOARDCUES)) gpsi->PUSIFlags |= PUSIF_KEYBOARDCUES;
       if (SPITESTPREF(UPM_COMBOBOXANIMATION)) gpsi->PUSIFlags |= PUSIF_COMBOBOXANIMATION;
       if (SPITESTPREF(UPM_LISTBOXSMOOTHSCROLLING)) gpsi->PUSIFlags |= PUSIF_LISTBOXSMOOTHSCROLLING;
    }
}

BOOL
InitSysParams()
{
    SpiUpdatePerUserSystemParameters();
    gbSpiInitialized = TRUE;
    return TRUE;
}


BOOL
APIENTRY
NtUserUpdatePerUserSystemParameters(
    DWORD dwReserved,
    BOOL bEnable)
{
    BOOL bResult;

    TRACE("Enter NtUserUpdatePerUserSystemParameters\n");
    UserEnterExclusive();

    SpiUpdatePerUserSystemParameters();
    if(bEnable)
        g_PaintDesktopVersion = SpiLoadDWord(KEY_DESKTOP, VAL_PAINTDESKVER, 0);
    else
        g_PaintDesktopVersion = FALSE;
    bResult = TRUE;

    TRACE("Leave NtUserUpdatePerUserSystemParameters, returning %d\n", bResult);
    UserLeave();

    return bResult;
}


/** Storing the settings ******************************************************/

static
VOID
SpiStoreDWord(PCWSTR pwszKey, PCWSTR pwszValue, DWORD Value)
{
    RegWriteUserSetting(pwszKey,
                        pwszValue,
                        REG_DWORD,
                        &Value,
                        sizeof(Value));
}

static
VOID
SpiStoreSz(PCWSTR pwszKey, PCWSTR pwszValue, PCWSTR pwsz)
{
    RegWriteUserSetting(pwszKey,
                        pwszValue,
                        REG_SZ,
                        (PWSTR)pwsz,
                        wcslen(pwsz) * sizeof(WCHAR));
}

static
VOID
SpiStoreSzInt(PCWSTR pwszKey, PCWSTR pwszValue, INT iValue)
{
    WCHAR awcBuffer[15];

    _itow(iValue, awcBuffer, 10);
    RegWriteUserSetting(pwszKey,
                        pwszValue,
                        REG_SZ,
                        awcBuffer,
                        (wcslen(awcBuffer) + 1) * sizeof(WCHAR));
}

static
VOID
SpiStoreMetric(LPCWSTR pwszValue, INT iValue)
{
    SpiStoreSzInt(KEY_METRIC, pwszValue, METRIC2REG(iValue));
}

static
VOID
SpiStoreFont(PCWSTR pwszValue, LOGFONTW* plogfont)
{
    RegWriteUserSetting(KEY_METRIC,
                        pwszValue,
                        REG_BINARY,
                        plogfont,
                        sizeof(LOGFONTW));
}


/** Get/Set value *************************************************************/

// FIXME: get rid of the flags and only use this from um. kernel can access data directly.
static
UINT_PTR
SpiMemCopy(PVOID pvDst, PVOID pvSrc, ULONG cbSize, BOOL bProtect, BOOL bToUser)
{
    NTSTATUS Status = STATUS_SUCCESS;

    if (bProtect)
    {
        _SEH2_TRY
        {
            if (bToUser)
            {
                ProbeForWrite(pvDst, cbSize, 1);
            }
            else
            {
                ProbeForRead(pvSrc, cbSize, 1);
            }
            memcpy(pvDst, pvSrc, cbSize);
        }
        _SEH2_EXCEPT(EXCEPTION_EXECUTE_HANDLER)
        {
            Status = _SEH2_GetExceptionCode();
        }
        _SEH2_END
    }
    else
    {
        memcpy(pvDst, pvSrc, cbSize);
    }

    if (!NT_SUCCESS(Status))
    {
        SetLastNtError(Status);
        ERR("SpiMemCopy failed, pvDst=%p, pvSrc=%p, bProtect=%d, bToUser=%d\n", pvDst, pvSrc, bProtect, bToUser);
    }
    return NT_SUCCESS(Status);
}

static inline
UINT_PTR
SpiGet(PVOID pvParam, PVOID pvData, ULONG cbSize, FLONG fl)
{
    REQ_INTERACTIVE_WINSTA(ERROR_ACCESS_DENIED);
    return SpiMemCopy(pvParam, pvData, cbSize, fl & SPIF_PROTECT, TRUE);
}

static inline
UINT_PTR
SpiSet(PVOID pvData, PVOID pvParam, ULONG cbSize, FLONG fl)
{
    REQ_INTERACTIVE_WINSTA(ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION);
    return SpiMemCopy(pvData, pvParam, cbSize, fl & SPIF_PROTECT, FALSE);
}

static inline
UINT_PTR
SpiGetEx(PVOID pvParam, PVOID pvData, ULONG cbSize, FLONG fl)
{
    ULONG cbBufSize;
    /* Get the cbSite member from UM memory */
    if (!SpiSet(&cbBufSize, pvParam, sizeof(ULONG), fl))
        return 0;
    /* Verify the correct size */
    if (cbBufSize != cbSize)
        return 0;
    return SpiGet(pvParam, pvData, cbSize, fl);
}

static inline
UINT_PTR
SpiGetInt(PVOID pvParam, PVOID piValue, FLONG fl)
{
    return SpiGet(pvParam, piValue, sizeof(INT), fl);
}

static inline
UINT_PTR
SpiSetYesNo(BOOL *pbData, BOOL bValue, PCWSTR pwszKey, PCWSTR pwszValue, FLONG fl)
{
    REQ_INTERACTIVE_WINSTA(ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION);
    *pbData = bValue ? TRUE : FALSE;
    if (fl & SPIF_UPDATEINIFILE)
    {
        SpiStoreSz(pwszKey, pwszValue, bValue ? L"Yes" : L"No");
    }
    return (UINT_PTR)pwszKey;
}

static inline
UINT_PTR
SpiSetBool(BOOL *pbData, INT iValue, PCWSTR pwszKey, PCWSTR pwszValue, FLONG fl)
{
    REQ_INTERACTIVE_WINSTA(ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION);
    *pbData = iValue ? TRUE : FALSE;
    if (fl & SPIF_UPDATEINIFILE)
    {
        SpiStoreSzInt(pwszKey, pwszValue, iValue);
    }
    return (UINT_PTR)pwszKey;
}

static inline
UINT_PTR
SpiSetDWord(PVOID pvData, INT iValue, PCWSTR pwszKey, PCWSTR pwszValue, FLONG fl)
{
    REQ_INTERACTIVE_WINSTA(ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION);
    *(INT*)pvData = iValue;
    if (fl & SPIF_UPDATEINIFILE)
    {
        SpiStoreDWord(pwszKey, pwszValue, iValue);
    }
    return (UINT_PTR)pwszKey;
}

static inline
UINT_PTR
SpiSetInt(PVOID pvData, INT iValue, PCWSTR pwszKey, PCWSTR pwszValue, FLONG fl)
{
    REQ_INTERACTIVE_WINSTA(ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION);
    *(INT*)pvData = iValue;
    if (fl & SPIF_UPDATEINIFILE)
    {
        SpiStoreSzInt(pwszKey, pwszValue, iValue);
    }
    return (UINT_PTR)pwszKey;
}

static inline
UINT_PTR
SpiSetMetric(PVOID pvData, INT iValue, PCWSTR pwszValue, FLONG fl)
{
    REQ_INTERACTIVE_WINSTA(ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION);
    *(INT*)pvData = iValue;
    if (fl & SPIF_UPDATEINIFILE)
    {
        SpiStoreMetric(pwszValue, iValue);
    }
    return (UINT_PTR)KEY_METRIC;
}

static inline
UINT_PTR
SpiSetUserPref(DWORD dwMask, PVOID pvValue, FLONG fl)
{
    DWORD dwRegMask;
    BOOL bValue = (BOOL)pvValue;

    REQ_INTERACTIVE_WINSTA(ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION);

    /* Set or clear bit according to bValue */
    gspv.dwUserPrefMask = bValue ? gspv.dwUserPrefMask | dwMask :
                                   gspv.dwUserPrefMask & ~dwMask;

    if (fl & SPIF_UPDATEINIFILE)
    {
        /* Read current value */
        RegReadUserSetting(KEY_DESKTOP,
                           VAL_USERPREFMASK,
                           REG_BINARY,
                           &dwRegMask,
                           sizeof(DWORD));

        /* Set or clear bit according to bValue */
        dwRegMask = bValue ? dwRegMask | dwMask : dwRegMask & ~dwMask;

        /* write back value */
        RegWriteUserSetting(KEY_DESKTOP,
                            VAL_USERPREFMASK,
                            REG_BINARY,
                            &dwRegMask,
                            sizeof(DWORD));
    }

    return (UINT_PTR)KEY_DESKTOP;
}

static inline
UINT_PTR
SpiGetUserPref(DWORD dwMask, PVOID pvParam, FLONG fl)
{
    INT iValue = gspv.dwUserPrefMask & dwMask ? 1 : 0;
    return SpiGetInt(pvParam, &iValue, fl);
}

static
UINT_PTR
SpiSetWallpaper(PVOID pvParam, FLONG fl)
{
    UNICODE_STRING ustr;
    WCHAR awc[MAX_PATH];
    BOOL bResult;
    HBITMAP hbmp, hOldBitmap;
    SURFACE *psurfBmp;
    ULONG ulTile, ulStyle;

    REQ_INTERACTIVE_WINSTA(ERROR_REQUIRES_INTERACTIVE_WINDOWSTATION);

    if (!pvParam)
    {
        /* FIXME: Reset Wallpaper to registry value */
        return (UINT_PTR)KEY_DESKTOP;
    }

    /* Capture UNICODE_STRING */
    bResult = SpiMemCopy(&ustr, pvParam, sizeof(ustr), fl & SPIF_PROTECT, 0);
    if (!bResult) return 0;
    if (ustr.Length > MAX_PATH * sizeof(WCHAR))
        return 0;

    /* Copy the string buffer name */
    bResult = SpiMemCopy(gspv.awcWallpaper, ustr.Buffer, ustr.Length, fl & SPIF_PROTECT, 0);
    if (!bResult) return 0;

    /* Update the UNICODE_STRING */
    gspv.ustrWallpaper.Buffer = gspv.awcWallpaper;
    gspv.ustrWallpaper.MaximumLength = MAX_PATH * sizeof(WCHAR);
    gspv.ustrWallpaper.Length = ustr.Length;
    gspv.awcWallpaper[ustr.Length / sizeof(WCHAR)] = 0;

    TRACE("SpiSetWallpaper, name=%S\n", gspv.awcWallpaper);

    /* Update registry */
    if (fl & SPIF_UPDATEINIFILE)
    {
        SpiStoreSz(KEY_DESKTOP, L"Wallpaper", gspv.awcWallpaper);
    }

    /* Got a filename? */
    if (gspv.awcWallpaper[0] != 0)
    {
        /* Convert file name to nt file name */
        ustr.Buffer = awc;
        ustr.MaximumLength = MAX_PATH * sizeof(WCHAR);
        ustr.Length = 0;
        if (!W32kDosPathNameToNtPathName(gspv.awcWallpaper, &ustr))
        {
            ERR("RtlDosPathNameToNtPathName_U failed\n");
            return 0;
        }

        /* Load the Bitmap */
        hbmp = UserLoadImage(ustr.Buffer);
        if (!hbmp)
        {
            ERR("UserLoadImage failed\n");
            return 0;
        }

        /* Try to get the size of the wallpaper */
        if(!(psurfBmp = SURFACE_ShareLockSurface(hbmp)))
        {
            GreDeleteObject(hbmp);
            return 0;
        }

        gspv.cxWallpaper = psurfBmp->SurfObj.sizlBitmap.cx;
        gspv.cyWallpaper = psurfBmp->SurfObj.sizlBitmap.cy;
        gspv.WallpaperMode = wmCenter;

        SURFACE_ShareUnlockSurface(psurfBmp);

        /* Change the bitmap's ownership */
        GreSetObjectOwner(hbmp, GDI_OBJ_HMGR_PUBLIC);

        /* Yes, Windows really loads the current setting from the registry. */
        ulTile = SpiLoadInt(KEY_DESKTOP, L"TileWallpaper", 0);
        ulStyle = SpiLoadInt(KEY_DESKTOP, L"WallpaperStyle", 0);
        TRACE("SpiSetWallpaper: ulTile=%lu, ulStyle=%lu\n", ulTile, ulStyle);

        /* Check the values we found in the registry */
        if(ulTile && !ulStyle)
        {
            gspv.WallpaperMode = wmTile;
        }
        else if(!ulTile && ulStyle == 2)
        {
            gspv.WallpaperMode = wmStretch;
        }
    }
    else
    {
        /* Remove wallpaper */
        gspv.cxWallpaper = 0;
        gspv.cyWallpaper = 0;
        hbmp = 0;
    }

    /* Take care of the old wallpaper, if any */
    hOldBitmap = gspv.hbmWallpaper;
    if(hOldBitmap != NULL)
    {
        /* Delete the old wallpaper */
        GreSetObjectOwner(hOldBitmap, GDI_OBJ_HMGR_POWNED);
        GreDeleteObject(hOldBitmap);
    }

    /* Set the new wallpaper */
    gspv.hbmWallpaper = hbmp;

    NtUserRedrawWindow(UserGetShellWindow(), NULL, NULL, RDW_INVALIDATE | RDW_ERASE);


    return (UINT_PTR)KEY_DESKTOP;
}

static BOOL
SpiNotifyNCMetricsChanged()
{
    PWND pwndDesktop, pwndCurrent;
    HWND *ahwnd;
    USER_REFERENCE_ENTRY Ref;
    int i;

    pwndDesktop = UserGetDesktopWindow();
    ASSERT(pwndDesktop);

    ahwnd = IntWinListChildren(pwndDesktop);
    if(!ahwnd)
        return FALSE;

    for (i = 0; ahwnd[i]; i++)
    {
        pwndCurrent = UserGetWindowObject(ahwnd[i]);
        if(!pwndCurrent)
            continue;

        UserRefObjectCo(pwndCurrent, &Ref);
        co_WinPosSetWindowPos(pwndCurrent, 0, pwndCurrent->rcWindow.left,pwndCurrent->rcWindow.top,
                                              pwndCurrent->rcWindow.right-pwndCurrent->rcWindow.left
                                              ,pwndCurrent->rcWindow.bottom - pwndCurrent->rcWindow.top,
                              SWP_FRAMECHANGED|SWP_NOACTIVATE|SWP_NOCOPYBITS|
                              SWP_NOMOVE|SWP_NOZORDER|SWP_NOREDRAW);
        UserDerefObjectCo(pwndCurrent);
    }

    ExFreePoolWithTag(ahwnd, USERTAG_WINDOWLIST);

    return TRUE;
}

static
UINT_PTR
SpiGetSet(UINT uiAction, UINT uiParam, PVOID pvParam, FLONG fl)
{
    switch (uiAction)
    {
        case SPI_GETBEEP:
            return SpiGetInt(pvParam, &gspv.bBeep, fl);

        case SPI_SETBEEP:
            return SpiSetYesNo(&gspv.bBeep, uiParam, KEY_SOUND, VAL_BEEP, fl);

        case SPI_GETMOUSE:
            return SpiGet(pvParam, &gspv.caiMouse, 3 * sizeof(INT), fl);

        case SPI_SETMOUSE:
            if (!SpiSet(&gspv.caiMouse, pvParam, 3 * sizeof(INT), fl))
                return 0;
            if (fl & SPIF_UPDATEINIFILE)
            {
                SpiStoreSzInt(KEY_MOUSE, VAL_MOUSE1, gspv.caiMouse.FirstThreshold);
                SpiStoreSzInt(KEY_MOUSE, VAL_MOUSE2, gspv.caiMouse.SecondThreshold);
                SpiStoreSzInt(KEY_MOUSE, VAL_MOUSE3, gspv.caiMouse.Acceleration);
            }
            return (UINT_PTR)KEY_MOUSE;

        case SPI_GETBORDER:
            return SpiGetInt(pvParam, &gspv.ncm.iBorderWidth, fl);

        case SPI_SETBORDER:
            uiParam = max(uiParam, 1);
            return SpiSetInt(&gspv.ncm.iBorderWidth, uiParam, KEY_METRIC, VAL_BORDER, fl);

        case SPI_GETKEYBOARDSPEED:
            return SpiGetInt(pvParam, &gspv.dwKbdSpeed, fl);

        case SPI_SETKEYBOARDSPEED:
            return SpiSetInt(&gspv.dwKbdSpeed, uiParam, KEY_KBD, VAL_KBDSPD, fl);

        case SPI_LANGDRIVER:
            ERR("SPI_LANGDRIVER is unimplemented\n");
            break;

        case SPI_GETSCREENSAVETIMEOUT:
            return SpiGetInt(pvParam, &gspv.iScrSaverTimeout, fl);

        case SPI_SETSCREENSAVETIMEOUT:
            return SpiSetInt(&gspv.iScrSaverTimeout, uiParam, KEY_DESKTOP, VAL_SCRTO, fl);

        case SPI_GETSCREENSAVEACTIVE:
            return SpiGetInt(pvParam, &gspv.bScrSaverActive, fl);

        case SPI_SETSCREENSAVEACTIVE:
            return SpiSetInt(&gspv.bScrSaverActive, uiParam, KEY_DESKTOP, VAL_SCRACT, fl);

        case SPI_GETGRIDGRANULARITY:
            return SpiGetInt(pvParam, &gspv.uiGridGranularity, fl);

        case SPI_SETGRIDGRANULARITY:
            return SpiSetInt(&gspv.uiGridGranularity, uiParam, KEY_DESKTOP, VAL_GRID, fl);

        case SPI_GETDESKWALLPAPER:
            uiParam = min(uiParam, gspv.ustrWallpaper.Length + 1UL);
            return SpiGet(pvParam, gspv.awcWallpaper, uiParam, fl);

        case SPI_SETDESKWALLPAPER:
            return SpiSetWallpaper(pvParam, fl);

        case SPI_SETDESKPATTERN:
            ERR("SPI_SETDESKPATTERN is unimplemented\n");
            break;

        case SPI_GETKEYBOARDDELAY:
            return SpiGetInt(pvParam, &gspv.iKbdDelay, fl);

        case SPI_SETKEYBOARDDELAY:
            return SpiSetInt(&gspv.iKbdDelay, uiParam, KEY_KBD, VAL_KBDDELAY, fl);

        case SPI_ICONHORIZONTALSPACING:
            if (pvParam)
            {
                return SpiGetInt(pvParam, &gspv.im.iHorzSpacing, fl);
            }
            uiParam = max(uiParam, 32);
            return SpiSetMetric(&gspv.im.iHorzSpacing, uiParam, VAL_ICONSPC, fl);

        case SPI_ICONVERTICALSPACING:
            if (pvParam)
            {
                return SpiGetInt(pvParam, &gspv.im.iVertSpacing, fl);
            }
            uiParam = max(uiParam, 32);
            return SpiSetMetric(&gspv.im.iVertSpacing, uiParam, VAL_ICONVSPC, fl);

        case SPI_GETICONTITLEWRAP:
            return SpiGetInt(pvParam, &gspv.im.iTitleWrap, fl);

        case SPI_SETICONTITLEWRAP:
            return SpiSetInt(&gspv.im.iTitleWrap, uiParam, KEY_METRIC, VAL_ITWRAP, fl);

        case SPI_GETMENUDROPALIGNMENT:
            return SpiGetInt(pvParam, &gspv.bMenuDropAlign, fl);

        case SPI_SETMENUDROPALIGNMENT:
            return SpiSetBool(&gspv.bMenuDropAlign, uiParam, KEY_MDALIGN, VAL_MDALIGN, fl);

        case SPI_SETDOUBLECLKWIDTH:
            return SpiSetInt(&gspv.iDblClickWidth, uiParam, KEY_MOUSE, VAL_DBLCLKWIDTH, fl);

        case SPI_SETDOUBLECLKHEIGHT:
            return SpiSetInt(&gspv.iDblClickHeight, uiParam, KEY_MOUSE, VAL_DBLCLKHEIGHT, fl);

        case SPI_GETICONTITLELOGFONT:
            return SpiGet(pvParam, &gspv.im.lfFont, sizeof(LOGFONTW), fl);

        case SPI_SETICONTITLELOGFONT:
            if (!SpiSet(&gspv.im.lfFont, pvParam, sizeof(LOGFONTW), fl))
                return 0;
            if (fl & SPIF_UPDATEINIFILE)
            {
                SpiStoreFont(L"IconFont", &gspv.im.lfFont);
             }
            return (UINT_PTR)KEY_METRIC;

        case SPI_SETDOUBLECLICKTIME:
            return SpiSetInt(&gspv.iDblClickTime, uiParam, KEY_MOUSE, VAL_DBLCLKTIME, fl);

        case SPI_SETMOUSEBUTTONSWAP:
            return SpiSetInt(&gspv.bMouseBtnSwap, uiParam, KEY_MOUSE, VAL_SWAP, fl);

        case SPI_GETFASTTASKSWITCH:
            return SpiGetInt(pvParam, &gspv.bFastTaskSwitch, fl);

        case SPI_SETFASTTASKSWITCH:
            /* According to Winetest this one is unimplemented */
            return 0;

        case SPI_GETDRAGFULLWINDOWS:
            return SpiGetInt(pvParam, &gspv.bDragFullWindows, fl);

        case SPI_SETDRAGFULLWINDOWS:
            return SpiSetInt(&gspv.bDragFullWindows, uiParam, KEY_DESKTOP, VAL_DRAG, fl);

        case SPI_GETNONCLIENTMETRICS:
            return SpiGet(pvParam, &gspv.ncm, sizeof(NONCLIENTMETRICSW), fl);

        case SPI_SETNONCLIENTMETRICS:
            if (!SpiSet(&gspv.ncm, pvParam, sizeof(NONCLIENTMETRICSW), fl))
                return 0;
            if (fl & SPIF_UPDATEINIFILE)
            {
                SpiStoreMetric(VAL_BORDER, gspv.ncm.iBorderWidth);
                SpiStoreMetric(L"ScrollWidth", gspv.ncm.iScrollWidth);
                SpiStoreMetric(L"ScrollHeight", gspv.ncm.iScrollHeight);
                SpiStoreMetric(L"CaptionWidth", gspv.ncm.iCaptionWidth);
                SpiStoreMetric(L"CaptionHeight", gspv.ncm.iCaptionHeight);
                SpiStoreMetric(L"SmCaptionWidth", gspv.ncm.iSmCaptionWidth);
                SpiStoreMetric(L"SmCaptionHeight", gspv.ncm.iSmCaptionHeight);
                SpiStoreMetric(L"MenuWidth", gspv.ncm.iMenuWidth);
                SpiStoreMetric(L"MenuHeight", gspv.ncm.iMenuHeight);
#if (WINVER >= 0x0600)
                SpiStoreMetric(L"PaddedBorderWidth", gspv.ncm.iPaddedBorderWidth);
#endif
                SpiStoreFont(L"CaptionFont", &gspv.ncm.lfCaptionFont);
                SpiStoreFont(L"SmCaptionFont", &gspv.ncm.lfSmCaptionFont);
                SpiStoreFont(L"MenuFont", &gspv.ncm.lfMenuFont);
                SpiStoreFont(L"StatusFont", &gspv.ncm.lfStatusFont);
                SpiStoreFont(L"MessageFont", &gspv.ncm.lfMessageFont);
            }
            if(!SpiNotifyNCMetricsChanged())
                return 0;
            return (UINT_PTR)KEY_METRIC;

        case SPI_GETMINIMIZEDMETRICS:
            return SpiGet(pvParam, &gspv.mm, sizeof(MINIMIZEDMETRICS), fl);

        case SPI_SETMINIMIZEDMETRICS:
            if (!SpiSet(&gspv.mm, pvParam, sizeof(MINIMIZEDMETRICS), fl))
                return 0;
            gspv.mm.iWidth = max(0, gspv.mm.iWidth);
            gspv.mm.iHorzGap = max(0, gspv.mm.iHorzGap);
            gspv.mm.iVertGap = max(0, gspv.mm.iVertGap);
            gspv.mm.iArrange = gspv.mm.iArrange & 0xf;
            if (fl & SPIF_UPDATEINIFILE)
            {
                SpiStoreMetric(L"MinWidth", gspv.mm.iWidth);
                SpiStoreMetric(L"MinHorzGap", gspv.mm.iHorzGap);
                SpiStoreMetric(L"MinVertGap", gspv.mm.iVertGap);
                SpiStoreMetric(L"MinArrange", gspv.mm.iArrange);
            }
            return (UINT_PTR)KEY_METRIC;

        case SPI_GETICONMETRICS:
            return SpiGet(pvParam, &gspv.im, sizeof(ICONMETRICS), fl);

        case SPI_SETICONMETRICS:
            if (!SpiSet(&gspv.im, pvParam, sizeof(ICONMETRICS), fl))
                return 0;
            if (fl & SPIF_UPDATEINIFILE)
            {
                SpiStoreMetric(VAL_ICONSPC, gspv.im.iHorzSpacing);
                SpiStoreMetric(VAL_ICONVSPC, gspv.im.iVertSpacing);
                SpiStoreMetric(VAL_ITWRAP, gspv.im.iTitleWrap);
                SpiStoreFont(L"IconFont", &gspv.im.lfFont);
            }
            return (UINT_PTR)KEY_METRIC;

        case SPI_GETWORKAREA:
        {
            PMONITOR pmonitor = UserGetPrimaryMonitor();

            if(!pmonitor)
                return 0;

            return SpiGet(pvParam, &pmonitor->rcWork, sizeof(RECTL), fl);
        }

        case SPI_SETWORKAREA:
        {
            /* FIXME: We should set the work area of the monitor
                      that contains the specified rectangle */
            PMONITOR pmonitor = UserGetPrimaryMonitor();
            RECT rcWorkArea;

            if(!pmonitor)
                return 0;

            if (!SpiSet(&rcWorkArea, pvParam, sizeof(RECTL), fl))
                return 0;

            /* Verify the new values */
            if (rcWorkArea.left < 0 ||
                rcWorkArea.top < 0 ||
                rcWorkArea.right > gpsi->aiSysMet[SM_CXSCREEN] ||
                rcWorkArea.bottom > gpsi->aiSysMet[SM_CYSCREEN] ||
                rcWorkArea.right <= rcWorkArea.left ||
                rcWorkArea.bottom <= rcWorkArea.top)
                return 0;

            pmonitor->rcWork = rcWorkArea;
            if (fl & SPIF_UPDATEINIFILE)
            {
                // FIXME: What to do?
            }
            return (UINT_PTR)KEY_DESKTOP;
        }

        case SPI_SETPENWINDOWS:
            ERR("SPI_SETPENWINDOWS is unimplemented\n");
            break;

        case SPI_GETFILTERKEYS:
            return SpiGet(pvParam, &gspv.filterkeys, sizeof(FILTERKEYS), fl);

        case SPI_SETFILTERKEYS:
            if (!SpiSet(&gspv.filterkeys, pvParam, sizeof(FILTERKEYS), fl))
                return 0;
            if (fl & SPIF_UPDATEINIFILE)
            {
                // FIXME: What to do?
            }
            return (UINT_PTR)KEY_DESKTOP;

        case SPI_GETTOGGLEKEYS:
            return SpiGet(pvParam, &gspv.togglekeys, sizeof(TOGGLEKEYS), fl);

        case SPI_SETTOGGLEKEYS:
            if (!SpiSet(&gspv.togglekeys, pvParam, sizeof(TOGGLEKEYS), fl))
                return 0;
            if (fl & SPIF_UPDATEINIFILE)
            {
                // FIXME: What to do?
            }
            return (UINT_PTR)KEY_DESKTOP;

        case SPI_GETMOUSEKEYS:
            return SpiGet(pvParam, &gspv.mousekeys, sizeof(MOUSEKEYS), fl);

        case SPI_SETMOUSEKEYS:
            if (!SpiSet(&gspv.mousekeys, pvParam, sizeof(MOUSEKEYS), fl))
                return 0;
            if (fl & SPIF_UPDATEINIFILE)
            {
                // FIXME: What to do?
            }
            return (UINT_PTR)KEY_DESKTOP;

        case SPI_GETSHOWSOUNDS:
            return SpiGetInt(pvParam, &gspv.bShowSounds, fl);

        case SPI_SETSHOWSOUNDS:
            return SpiSetBool(&gspv.bShowSounds, uiParam, KEY_SHOWSNDS, VAL_ON, fl);

        case SPI_GETSTICKYKEYS:
            if (uiParam != sizeof(STICKYKEYS))
                return 0;
            return SpiGetEx(pvParam, &gspv.stickykeys, sizeof(STICKYKEYS), fl);

        case SPI_SETSTICKYKEYS:
            if (!SpiSet(&gspv.stickykeys, pvParam, sizeof(STICKYKEYS), fl))
                return 0;
            if (fl & SPIF_UPDATEINIFILE)
            {
                // FIXME: What to do?
            }
            return (UINT_PTR)KEY_DESKTOP;

        case SPI_GETACCESSTIMEOUT:
            if (uiParam != 0 && uiParam != sizeof(ACCESSTIMEOUT))
                return 0;
            return SpiGetEx(pvParam, &gspv.accesstimeout, sizeof(ACCESSTIMEOUT), fl);

        case SPI_SETACCESSTIMEOUT:
            if (!SpiSet(&gspv.accesstimeout, pvParam, sizeof(ACCESSTIMEOUT), fl))
                return 0;
            if (fl & SPIF_UPDATEINIFILE)
            {
                // FIXME: What to do?
            }
            return (UINT_PTR)KEY_DESKTOP;

        case SPI_GETSERIALKEYS:
            return SpiGet(pvParam, &gspv.serialkeys, sizeof(SERIALKEYS), fl);

        case SPI_SETSERIALKEYS:
            if (!SpiSet(&gspv.serialkeys, pvParam, sizeof(SERIALKEYS), fl))
                return 0;
            if (fl & SPIF_UPDATEINIFILE)
            {
                // FIXME: What to do?
            }
            return (UINT_PTR)KEY_DESKTOP;

        case SPI_GETSOUNDSENTRY:
            return SpiGet(pvParam, &gspv.soundsentry, sizeof(SOUNDSENTRY), fl);

        case SPI_SETSOUNDSENTRY:
            if (!SpiSet(&gspv.soundsentry, pvParam, sizeof(SOUNDSENTRY), fl))
                return 0;
            if (fl & SPIF_UPDATEINIFILE)
            {
                // FIXME: What to do?
            }
            return (UINT_PTR)KEY_DESKTOP;

        case SPI_GETHIGHCONTRAST:
            return SpiGet(pvParam, &gspv.highcontrast, sizeof(HIGHCONTRAST), fl);

        case SPI_SETHIGHCONTRAST:
            if (!SpiSet(&gspv.highcontrast, pvParam, sizeof(HIGHCONTRAST), fl))
                return 0;
            if (fl & SPIF_UPDATEINIFILE)
            {
                // FIXME: What to do?
            }
            return (UINT_PTR)KEY_DESKTOP;

        case SPI_GETKEYBOARDPREF:
            return SpiGetInt(pvParam, &gspv.bKbdPref, fl);

        case SPI_SETKEYBOARDPREF:
            return SpiSetBool(&gspv.bKbdPref, uiParam, KEY_KDBPREF, VAL_ON, fl);

        case SPI_GETSCREENREADER:
            return SpiGetInt(pvParam, &gspv.bScreenReader, fl);

        case SPI_SETSCREENREADER:
            return SpiSetBool(&gspv.bScreenReader, uiParam, KEY_SCRREAD, VAL_ON, fl);

        case SPI_GETANIMATION:
            return SpiGet(pvParam, &gspv.animationinfo, sizeof(ANIMATIONINFO), fl);

        case SPI_SETANIMATION:
            if (!SpiSet(&gspv.animationinfo, pvParam, sizeof(ANIMATIONINFO), fl))
                return 0;
            if (fl & SPIF_UPDATEINIFILE)
            {
                // FIXME: What to do?
            }
            return (UINT_PTR)KEY_DESKTOP;

        case SPI_GETFONTSMOOTHING:
            return SpiGetInt(pvParam, &gspv.bFontSmoothing, fl);

        case SPI_SETFONTSMOOTHING:
            gspv.bFontSmoothing = uiParam ? TRUE : FALSE;
            if (fl & SPIF_UPDATEINIFILE)
            {
                SpiStoreSzInt(KEY_DESKTOP, VAL_FNTSMOOTH, uiParam ? 2 : 0);
            }
            return (UINT_PTR)KEY_DESKTOP;

        case SPI_SETDRAGWIDTH:
            return SpiSetInt(&gspv.iDragWidth, uiParam, KEY_DESKTOP, VAL_DRAGWIDTH, fl);

        case SPI_SETDRAGHEIGHT:
            return SpiSetInt(&gspv.iDragHeight, uiParam, KEY_DESKTOP, VAL_DRAGHEIGHT, fl);

        case SPI_SETHANDHELD:
            return SpiSetBool(&gspv.bHandHeld, uiParam, KEY_DESKTOP, L"HandHeld", fl);

        case SPI_GETLOWPOWERTIMEOUT:
            return SpiGetInt(pvParam, &gspv.iLowPwrTimeout, fl);

        case SPI_GETPOWEROFFTIMEOUT:
            return SpiGetInt(pvParam, &gspv.iPwrOffTimeout, fl);

        case SPI_SETLOWPOWERTIMEOUT:
            return SpiSetInt(&gspv.iLowPwrTimeout, uiParam, KEY_DESKTOP, L"LowPowerTimeOut", fl);

        case SPI_SETPOWEROFFTIMEOUT:
            return SpiSetInt(&gspv.iPwrOffTimeout, uiParam, KEY_DESKTOP, L"PowerOffTimeOut", fl);

        case SPI_GETLOWPOWERACTIVE:
            return SpiGetInt(pvParam, &gspv.iPwrOffTimeout, fl);

        case SPI_GETPOWEROFFACTIVE:
            return SpiGetInt(pvParam, &gspv.bPwrOffActive, fl);

        case SPI_SETLOWPOWERACTIVE:
            return SpiSetBool(&gspv.bLowPwrActive, uiParam, KEY_DESKTOP, L"LowPowerActive", fl);

        case SPI_SETPOWEROFFACTIVE:
            return SpiSetBool(&gspv.bPwrOffActive, uiParam, KEY_DESKTOP, L"PowerOffActive", fl);

        case SPI_SETCURSORS:
            ERR("SPI_SETCURSORS is unimplemented\n");
            break;

        case SPI_SETICONS:
            ERR("SPI_SETICONS is unimplemented\n");
            break;

        case SPI_GETDEFAULTINPUTLANG:
            if (!gspklBaseLayout)
                return FALSE;

            return SpiGet(pvParam, &gspklBaseLayout->hkl, sizeof(HKL), fl);

        case SPI_SETDEFAULTINPUTLANG:
        {
            HKL hkl;

            /* Note: SPIF_UPDATEINIFILE is not supported */
            if ((fl & SPIF_UPDATEINIFILE) || !SpiSet(&hkl, pvParam, sizeof(hkl), fl))
                return FALSE;

            return UserSetDefaultInputLang(hkl);
        }

        case SPI_SETLANGTOGGLE:
            ERR("SPI_SETLANGTOGGLE is unimplemented\n");
            break;

        case SPI_GETWINDOWSEXTENSION:
            ERR("SPI_GETWINDOWSEXTENSION is unimplemented\n");
            break;

        case SPI_GETMOUSETRAILS:
            return SpiGetInt(pvParam, &gspv.iMouseTrails, fl);

        case SPI_SETMOUSETRAILS:
            return SpiSetInt(&gspv.iMouseTrails, uiParam, KEY_MOUSE, VAL_MOUSETRAILS, fl);

        case SPI_GETSNAPTODEFBUTTON:
            return SpiGetInt(pvParam, &gspv.bSnapToDefBtn, fl);

        case SPI_SETSNAPTODEFBUTTON:
            return SpiSetBool(&gspv.bSnapToDefBtn, uiParam, KEY_MOUSE, VAL_SNAPDEFBTN, fl);

        case SPI_GETMOUSEHOVERWIDTH:
            return SpiGetInt(pvParam, &gspv.iMouseHoverWidth, fl);

        case SPI_SETMOUSEHOVERWIDTH:
            return SpiSetInt(&gspv.iMouseHoverWidth, uiParam, KEY_MOUSE, VAL_HOVERWIDTH, fl);

        case SPI_GETMOUSEHOVERHEIGHT:
            return SpiGetInt(pvParam, &gspv.iMouseHoverHeight, fl);

        case SPI_SETMOUSEHOVERHEIGHT:
            return SpiSetInt(&gspv.iMouseHoverHeight, uiParam, KEY_MOUSE, VAL_HOVERHEIGHT, fl);

        case SPI_GETMOUSEHOVERTIME:
            return SpiGetInt(pvParam, &gspv.iMouseHoverTime, fl);

        case SPI_SETMOUSEHOVERTIME:
           /* See http://msdn2.microsoft.com/en-us/library/ms724947.aspx
            * copy text from it, if some agument why xp and 2003 behovir diffent
            * only if they do not have SP install
            * " Windows Server 2003 and Windows XP: The operating system does not
            *   enforce the use of USER_TIMER_MAXIMUM and USER_TIMER_MINIMUM until
            *   Windows Server 2003 SP1 and Windows XP SP2 "
            */
            return SpiSetInt(&gspv.iMouseHoverTime, uiParam, KEY_MOUSE, VAL_HOVERTIME, fl);

        case SPI_GETWHEELSCROLLLINES:
            return SpiGetInt(pvParam, &gspv.iWheelScrollLines, fl);

        case SPI_SETWHEELSCROLLLINES:
            return SpiSetInt(&gspv.iWheelScrollLines, uiParam, KEY_DESKTOP, VAL_SCRLLLINES, fl);

        case SPI_GETMENUSHOWDELAY:
            return SpiGetInt(pvParam, &gspv.dwMenuShowDelay, fl);

        case SPI_SETMENUSHOWDELAY:
            return SpiSetInt(&gspv.dwMenuShowDelay, uiParam, KEY_DESKTOP, L"MenuShowDelay", fl);

#if (_WIN32_WINNT >= 0x0600)
        case SPI_GETWHEELSCROLLCHARS:
            return SpiGetInt(pvParam, &gspv.uiWheelScrollChars, fl);

        case SPI_SETWHEELSCROLLCHARS:
            return SpiSetInt(&gspv.uiWheelScrollChars, uiParam, KEY_DESKTOP, VAL_SCRLLCHARS, fl);
#endif
        case SPI_GETSHOWIMEUI:
            return SpiGetInt(pvParam, &gspv.bShowImeUi, fl);

        case SPI_SETSHOWIMEUI:
            return SpiSetBool(&gspv.bShowImeUi, uiParam, KEY_DESKTOP, L"", fl);

        case SPI_GETMOUSESPEED:
            return SpiGetInt(pvParam, &gspv.iMouseSpeed, fl);

        case SPI_SETMOUSESPEED:
        {
            /* Allowed range is [1:20] */
            if ((INT_PTR)pvParam < 1 || (INT_PTR)pvParam > 20)
                return 0;
            else
                return SpiSetInt(&gspv.iMouseSpeed, (INT_PTR)pvParam, KEY_MOUSE, VAL_SENSITIVITY, fl);
        }

        case SPI_GETSCREENSAVERRUNNING:
            return SpiGetInt(pvParam, &gspv.bScrSaverRunning, fl);

        case SPI_SETSCREENSAVERRUNNING:
            // FIXME: also return value?
            return SpiSetBool(&gspv.bScrSaverRunning, uiParam, KEY_MOUSE, L"", fl);

#if(WINVER >= 0x0600)
        case SPI_GETAUDIODESCRIPTION:
            return SpiGet(pvParam, &gspv.audiodesription, sizeof(AUDIODESCRIPTION), fl);

        case SPI_SETAUDIODESCRIPTION:
            ERR("SPI_SETAUDIODESCRIPTION is unimplemented\n");
            break;

        case SPI_GETSCREENSAVESECURE:
            return SpiGetInt(pvParam, &gspv.bScrSaverSecure, fl);

        case SPI_SETSCREENSAVESECURE:
            return SpiSetBool(&gspv.bScrSaverSecure, uiParam, KEY_DESKTOP, L"ScreenSaverIsSecure", fl);
#endif

        case SPI_GETACTIVEWINDOWTRACKING:
            return SpiGetUserPref(UPM_ACTIVEWINDOWTRACKING, pvParam, fl);

        case SPI_SETACTIVEWINDOWTRACKING:
            return SpiSetUserPref(UPM_ACTIVEWINDOWTRACKING, pvParam, fl);

        case SPI_GETMENUANIMATION:
            return SpiGetUserPref(UPM_MENUANIMATION, pvParam, fl);

        case SPI_SETMENUANIMATION:
            return SpiSetUserPref(UPM_MENUANIMATION, pvParam, fl);

        case SPI_GETCOMBOBOXANIMATION:
            return SpiGetUserPref(UPM_COMBOBOXANIMATION, pvParam, fl);

        case SPI_SETCOMBOBOXANIMATION:
            return SpiSetUserPref(UPM_COMBOBOXANIMATION, pvParam, fl);

        case SPI_GETLISTBOXSMOOTHSCROLLING:
            return SpiGetUserPref(UPM_LISTBOXSMOOTHSCROLLING, pvParam, fl);

        case SPI_SETLISTBOXSMOOTHSCROLLING:
            return SpiSetUserPref(UPM_LISTBOXSMOOTHSCROLLING, pvParam, fl);

        case SPI_GETGRADIENTCAPTIONS:
            return SpiGetUserPref(UPM_GRADIENTCAPTIONS, pvParam, fl);

        case SPI_SETGRADIENTCAPTIONS:
            return SpiSetUserPref(UPM_GRADIENTCAPTIONS, pvParam, fl);

        case SPI_GETKEYBOARDCUES:
            return SpiGetUserPref(UPM_KEYBOARDCUES, pvParam, fl);

        case SPI_SETKEYBOARDCUES:
            return SpiSetUserPref(UPM_KEYBOARDCUES, pvParam, fl);

        case SPI_GETACTIVEWNDTRKZORDER:
            return SpiGetUserPref(UPM_ACTIVEWNDTRKZORDER, pvParam, fl);

        case SPI_SETACTIVEWNDTRKZORDER:
            return SpiSetUserPref(UPM_ACTIVEWNDTRKZORDER, pvParam, fl);

        case SPI_GETHOTTRACKING:
            return SpiGetUserPref(UPM_HOTTRACKING, pvParam, fl);

        case SPI_SETHOTTRACKING:
            return SpiSetUserPref(UPM_HOTTRACKING, pvParam, fl);

        case SPI_GETMENUFADE:
            return SpiGetUserPref(UPM_MENUFADE, pvParam, fl);

        case SPI_SETMENUFADE:
            return SpiSetUserPref(UPM_MENUFADE, pvParam, fl);

        case SPI_GETSELECTIONFADE:
            return SpiGetUserPref(UPM_SELECTIONFADE, pvParam, fl);

        case SPI_SETSELECTIONFADE:
            return SpiSetUserPref(UPM_SELECTIONFADE, pvParam, fl);

        case SPI_GETTOOLTIPANIMATION:
            return SpiGetUserPref(UPM_TOOLTIPANIMATION, pvParam, fl);

        case SPI_SETTOOLTIPANIMATION:
            return SpiSetUserPref(UPM_TOOLTIPANIMATION, pvParam, fl);

        case SPI_GETTOOLTIPFADE:
            return SpiGetUserPref(UPM_TOOLTIPFADE, pvParam, fl);

        case SPI_SETTOOLTIPFADE:
            return SpiSetUserPref(UPM_TOOLTIPFADE, pvParam, fl);

        case SPI_GETCURSORSHADOW:
            return SpiGetUserPref(UPM_CURSORSHADOW, pvParam, fl);

        case SPI_SETCURSORSHADOW:
            gspv.bMouseCursorShadow = (BOOL)pvParam;
            return SpiSetUserPref(UPM_CURSORSHADOW, pvParam, fl);

        case SPI_GETUIEFFECTS:
            return SpiGetUserPref(UPM_UIEFFECTS, pvParam, fl);

        case SPI_SETUIEFFECTS:
            return SpiSetUserPref(UPM_UIEFFECTS, pvParam, fl);

        case SPI_GETMOUSESONAR:
            return SpiGetInt(pvParam, &gspv.bMouseSonar, fl);

        case SPI_SETMOUSESONAR:
            return SpiSetBool(&gspv.bMouseSonar, uiParam, KEY_MOUSE, L"", fl);

        case SPI_GETMOUSECLICKLOCK:
            return SpiGetUserPref(UPM_CLICKLOCK, pvParam, fl);

        case SPI_SETMOUSECLICKLOCK:
            gspv.bMouseClickLock = (BOOL)pvParam;
            return SpiSetUserPref(UPM_CLICKLOCK, pvParam, fl);

        case SPI_GETMOUSEVANISH:
            return SpiGetInt(pvParam, &gspv.bMouseVanish, fl);

        case SPI_SETMOUSEVANISH:
            return SpiSetBool(&gspv.bMouseVanish, uiParam, KEY_MOUSE, L"", fl);

        case SPI_GETFLATMENU:
            return SpiGetInt(pvParam, &gspv.bFlatMenu, fl);

        case SPI_SETFLATMENU:
            return SpiSetBool(&gspv.bFlatMenu, uiParam, KEY_MOUSE, L"", fl);

        case SPI_GETDROPSHADOW:
            return SpiGetInt(pvParam, &gspv.bDropShadow, fl);

        case SPI_SETDROPSHADOW:
            return SpiSetBool(&gspv.bDropShadow, uiParam, KEY_MOUSE, L"", fl);

        case SPI_GETBLOCKSENDINPUTRESETS:
            return SpiGetInt(pvParam, &gspv.bBlockSendInputResets, fl);

        case SPI_SETBLOCKSENDINPUTRESETS:
            return SpiSetBool(&gspv.bBlockSendInputResets, uiParam, KEY_MOUSE, L"", fl);

#if(_WIN32_WINNT >= 0x0600)
        case SPI_GETDISABLEOVERLAPPEDCONTENT:
            return SpiGetInt(pvParam, &gspv.bDisableOverlappedContent, fl);

        case SPI_SETDISABLEOVERLAPPEDCONTENT:
            return SpiSetBool(&gspv.bDisableOverlappedContent, uiParam, KEY_MOUSE, L"", fl);

        case SPI_GETCLIENTAREAANIMATION:
            return SpiGetInt(pvParam, &gspv.bClientAnimation, fl);

        case SPI_SETCLIENTAREAANIMATION:
            return SpiSetBool(&gspv.bClientAnimation, uiParam, KEY_MOUSE, L"", fl);

        case SPI_GETCLEARTYPE:
            return SpiGetInt(pvParam, &gspv.bClearType, fl);

        case SPI_SETCLEARTYPE:
            return SpiSetBool(&gspv.bClearType, uiParam, KEY_MOUSE, L"", fl);

        case SPI_GETSPEECHRECOGNITION:
            return SpiGetInt(pvParam, &gspv.bSpeechRecognition, fl);

        case SPI_SETSPEECHRECOGNITION:
            return SpiSetBool(&gspv.bSpeechRecognition, uiParam, KEY_MOUSE, L"", fl);
#endif

        case SPI_GETFOREGROUNDLOCKTIMEOUT:
            return SpiGetInt(pvParam, &gspv.dwForegroundLockTimeout, fl);

        case SPI_SETFOREGROUNDLOCKTIMEOUT:
            return SpiSetInt(&gspv.dwForegroundLockTimeout, uiParam, KEY_MOUSE, L"", fl);

        case SPI_GETACTIVEWNDTRKTIMEOUT:
            return SpiGetInt(pvParam, &gspv.dwActiveTrackingTimeout, fl);

        case SPI_SETACTIVEWNDTRKTIMEOUT:
            return SpiSetInt(&gspv.dwActiveTrackingTimeout, uiParam, KEY_MOUSE, L"", fl);

        case SPI_GETFOREGROUNDFLASHCOUNT:
            return SpiGetInt(pvParam, &gspv.dwForegroundFlashCount, fl);

        case SPI_SETFOREGROUNDFLASHCOUNT:
            return SpiSetInt(&gspv.dwForegroundFlashCount, uiParam, KEY_MOUSE, L"", fl);

        case SPI_GETCARETWIDTH:
            return SpiGetInt(pvParam, &gspv.dwCaretWidth, fl);

        case SPI_SETCARETWIDTH:
            return SpiSetInt(&gspv.dwCaretWidth, uiParam, KEY_MOUSE, L"", fl);

        case SPI_GETMOUSECLICKLOCKTIME:
            return SpiGetInt(pvParam, &gspv.dwMouseClickLockTime, fl);

        case SPI_SETMOUSECLICKLOCKTIME:
            return SpiSetDWord(&gspv.dwMouseClickLockTime, uiParam, KEY_DESKTOP, VAL_CLICKLOCKTIME, fl);

        case SPI_GETFONTSMOOTHINGTYPE:
            return SpiGetInt(pvParam, &gspv.uiFontSmoothingType, fl);

        case SPI_SETFONTSMOOTHINGTYPE:
            return SpiSetInt(&gspv.uiFontSmoothingType, uiParam, KEY_MOUSE, L"", fl);

        case SPI_GETFONTSMOOTHINGCONTRAST:
            return SpiGetInt(pvParam, &gspv.uiFontSmoothingContrast, fl);

        case SPI_SETFONTSMOOTHINGCONTRAST:
            return SpiSetInt(&gspv.uiFontSmoothingContrast, uiParam, KEY_MOUSE, L"", fl);

        case SPI_GETFOCUSBORDERWIDTH:
            return SpiGetInt(pvParam, &gspv.uiFocusBorderWidth, fl);

        case SPI_SETFOCUSBORDERWIDTH:
            return SpiSetInt(&gspv.uiFocusBorderWidth, uiParam, KEY_MOUSE, L"", fl);

        case SPI_GETFOCUSBORDERHEIGHT:
            return SpiGetInt(pvParam, &gspv.uiFocusBorderHeight, fl);

        case SPI_SETFOCUSBORDERHEIGHT:
            return SpiSetInt(&gspv.uiFocusBorderHeight, uiParam, KEY_MOUSE, L"", fl);

        case SPI_GETFONTSMOOTHINGORIENTATION:
            return SpiGetInt(pvParam, &gspv.uiFontSmoothingOrientation, fl);

        case SPI_SETFONTSMOOTHINGORIENTATION:
            return SpiSetInt(&gspv.uiFontSmoothingOrientation, uiParam, KEY_MOUSE, L"", fl);

        /* The following are undocumented, but valid SPI values */
        case 0x1010:
        case 0x1011:
        case 0x1028:
        case 0x1029:
        case 0x102A:
        case 0x102B:
        case 0x102C:
        case 0x102D:
        case 0x102E:
        case 0x102F:
        case 0x1030:
        case 0x1031:
        case 0x1032:
        case 0x1033:
        case 0x1034:
        case 0x1035:
        case 0x1036:
        case 0x1037:
        case 0x1038:
        case 0x1039:
        case 0x103A:
        case 0x103B:
        case 0x103C:
        case 0x103D:
            ERR("Undocumented SPI value %x is unimplemented\n", uiAction);
            break;

        default:
            ERR("Invalid SPI value: %u\n", uiAction);
            EngSetLastError(ERROR_INVALID_PARAMETER);
            return 0;
    }

    return 0;
}

BOOL
FASTCALL
UserSystemParametersInfo(
    UINT uiAction,
    UINT uiParam,
    PVOID pvParam,
    UINT fWinIni)
{
    ULONG_PTR ulResult;
    PPROCESSINFO ppi = PsGetCurrentProcessWin32Process();

    ASSERT(ppi);

    if (!gbSpiInitialized)
    {
        KeRosDumpStackFrames(NULL, 20);
        //ASSERT(FALSE);
        return FALSE;
    }

    /* Get a pointer to the current Windowstation */
    if (!ppi->prpwinsta)
    {
        ERR("UserSystemParametersInfo called without active window station.\n");
        //ASSERT(FALSE);
        //return FALSE;
    }

    /* Do the actual operation */
    ulResult = SpiGetSet(uiAction, uiParam, pvParam, fWinIni);

    /* Did we change something? */
    if (ulResult > 1)
    {
        SpiFixupValues();

        /* Update system metrics */
        InitMetrics();

        /* Send notification to toplevel windows, if requested */
        if (fWinIni & (SPIF_SENDCHANGE | SPIF_SENDWININICHANGE))
        {
            /* Send WM_SETTINGCHANGE to all toplevel windows */
            co_IntSendMessageTimeout(HWND_BROADCAST,
                                     WM_SETTINGCHANGE,
                                     (WPARAM)uiAction,
                                     (LPARAM)ulResult,
                                     SMTO_NORMAL,
                                     100,
                                     &ulResult);
        }
        ulResult = 1;
    }

    return ulResult;
}

BOOL
APIENTRY
NtUserSystemParametersInfo(
    UINT uiAction,
    UINT uiParam,
    PVOID pvParam,
    UINT fWinIni)
{
    BOOL bResult;

    TRACE("Enter NtUserSystemParametersInfo(%u)\n", uiAction);
    UserEnterExclusive();

    // FIXME: Get rid of the flags and only use this from um. kernel can access data directly.
    /* Set UM memory protection flag */
    fWinIni |= SPIF_PROTECT;

    /* Call internal function */
    bResult = UserSystemParametersInfo(uiAction, uiParam, pvParam, fWinIni);

    TRACE("Leave NtUserSystemParametersInfo, returning %d\n", bResult);
    UserLeave();

    return bResult;
}

/* EOF */
