/****************************** Module Header ******************************\
* Module Name: wow.c
*
* Copyright (c) 1985 - 1999, Microsoft Corporation
*
* This module contains shared code between USER32 and USER16
* No New CODE should be added to this file, unless its shared
* with USER16.
*
* History:
* 29-DEC-93 NanduriR      shared user32/user16 code.
\***************************************************************************/

#include "precomp.h"
#pragma hdrstop


/***************************************************************************\
* ValidateHwnd
*
* Verify that the handle is valid.  If the handle is invalid or access
* cannot be granted fail.
*
* History:
* 03-18-92 DarrinM      Created from pieces of misc server-side funcs.
\***************************************************************************/
PWND FASTCALL ValidateHwnd(
    HWND hwnd)
{
    PCLIENTINFO pci = GetClientInfo();

    /*
     * Attempt fast window validation.
     */
    if (hwnd != NULL && hwnd == pci->CallbackWnd.hwnd) {
        return pci->CallbackWnd.pwnd;
    }

    /*
     * Validate that the handle is of the proper type.
     */
    return HMValidateHandle(hwnd, TYPE_WINDOW);
}


PWND FASTCALL ValidateHwndNoRip(
    HWND hwnd)
{
    PCLIENTINFO pci = GetClientInfo();

    /*
     * Attempt fast window validation.
     */
    if (hwnd != NULL && hwnd == pci->CallbackWnd.hwnd) {
        return pci->CallbackWnd.pwnd;
    }

    /*
     * Validate the handle is of the proper type.
     */
    return HMValidateHandleNoRip(hwnd, TYPE_WINDOW);
}




FUNCLOG3(LOG_GENERAL, int, WINAPI, GetClassNameA, HWND, hwnd, LPSTR, lpClassName, int, nMaxCount)
int WINAPI GetClassNameA(
    HWND hwnd,
    LPSTR lpClassName,
    int nMaxCount)
{
    PCLS pcls;
    LPSTR lpszClassNameSrc;
    PWND pwnd;
    int cchSrc;

    pwnd = ValidateHwnd(hwnd);

    if (pwnd == NULL)
        return FALSE;

    try {
        if (nMaxCount != 0) {
            pcls = (PCLS)REBASEALWAYS(pwnd, pcls);
            lpszClassNameSrc = REBASEPTR(pwnd, pcls->lpszAnsiClassName);
            cchSrc = lstrlenA(lpszClassNameSrc);
            nMaxCount = min(cchSrc, nMaxCount - 1);
            RtlCopyMemory(lpClassName, lpszClassNameSrc, nMaxCount);
            lpClassName[nMaxCount] = '\0';
        }
    } except (W32ExceptionHandler(FALSE, RIP_WARNING)) {
        nMaxCount = 0;
    }

    return nMaxCount;
}

/***************************************************************************\
* _GetDesktopWindow (API)
*
*
*
* History:
* 11-07-90 darrinm      Implemented.
\***************************************************************************/
PWND _GetDesktopWindow(void)
{
    PCLIENTINFO pci;

    ConnectIfNecessary(0);

    pci = GetClientInfo();
    return (PWND)((KERNEL_ULONG_PTR)pci->pDeskInfo->spwnd -
            pci->ulClientDelta);
}



HWND GetDesktopWindow(void)
{
    PWND        pwnd;
    PCLIENTINFO pci;

    pwnd = _GetDesktopWindow();

    pci = GetClientInfo();

    /*
     * validate the parent window's handle if a restricted process
     */
    if (pci && (pci->dwTIFlags & TIF_RESTRICTED)) {
        if (ValidateHwnd(HW(pwnd)) == NULL) {
            return NULL;
        }
    }
    return HW(pwnd);
}


PWND _GetDlgItem(
    PWND pwnd,
    int id)
{
    if (pwnd != NULL) {
        pwnd = REBASEPWND(pwnd, spwndChild);
        while (pwnd != NULL) {
            if (PtrToLong(pwnd->spmenu) == id)
                break;
            pwnd = REBASEPWND(pwnd, spwndNext);
        }
    }

    return pwnd;
}



FUNCLOG2(LOG_GENERAL, HWND, DUMMYCALLINGTYPE, GetDlgItem, HWND, hwnd, int, id)
HWND GetDlgItem(
    HWND hwnd,
    int id)
{
    PWND pwnd;
    HWND hwndRet;

    pwnd = ValidateHwnd(hwnd);
    if (pwnd == NULL)
        return NULL;

    pwnd = _GetDlgItem(pwnd, id);

    hwndRet = HW(pwnd);

    if (hwndRet == (HWND)0)
        RIPERR0(ERROR_CONTROL_ID_NOT_FOUND, RIP_VERBOSE, "");

    return hwndRet;
}



FUNCLOG1(LOG_GENERAL, HMENU, DUMMYCALLINGTYPE, GetMenu, HWND, hwnd)
HMENU GetMenu(
    HWND hwnd)
{
    PWND pwnd;
    PMENU pmenu;

    pwnd = ValidateHwnd(hwnd);

    if (pwnd == NULL)
        return 0;

    /*
     * Some ill-behaved apps use GetMenu to get the child id, so
     * only map to the handle for non-child windows.
     */
    if (!TestwndChild(pwnd)) {
        pmenu = REBASE(pwnd, spmenu);
        return (HMENU)PtoH(pmenu);
    } else {
        return (HMENU)KPVOID_TO_PVOID(pwnd->spmenu);
    }
}


/***************************************************************************\
* GetMenuItemCount
*
* Returns a count of the number of items in the menu. Returns -1 if
* invalid menu.
*
* History:
\***************************************************************************/


FUNCLOG1(LOG_GENERAL, int, DUMMYCALLINGTYPE, GetMenuItemCount, HMENU, hMenu)
int GetMenuItemCount(
    HMENU hMenu)
{
    PMENU pMenu;

    pMenu = VALIDATEHMENU(hMenu);

    if (pMenu == NULL)
        return -1;

    return pMenu->cItems;
}

/***************************************************************************\
* GetMenuItemID
*
* Return the ID of a menu item at the specified position.
*
* History:
\***************************************************************************/


FUNCLOG2(LOG_GENERAL, UINT, DUMMYCALLINGTYPE, GetMenuItemID, HMENU, hMenu, int, nPos)
UINT GetMenuItemID(
    HMENU hMenu,
    int nPos)
{
    PMENU pMenu;
    PITEM pItem;

    pMenu = VALIDATEHMENU(hMenu);

    if (pMenu == NULL)
        return (UINT)-1;

    /*
     * If the position is valid and the item is not a popup, get the ID
     * Don't allow negative indexes, because that'll cause an access violation.
     */
    if (nPos < (int)pMenu->cItems && nPos >= 0) {
        pItem = &((PITEM)REBASEALWAYS(pMenu, rgItems))[nPos];
        if (pItem->spSubMenu == NULL)
            return pItem->wID;
    }

    return (UINT)-1;
}



FUNCLOG3(LOG_GENERAL, UINT, DUMMYCALLINGTYPE, GetMenuState, HMENU, hMenu, UINT, uId, UINT, uFlags)
UINT GetMenuState(
    HMENU hMenu,
    UINT uId,
    UINT uFlags)
{
    PMENU pMenu;

    pMenu = VALIDATEHMENU(hMenu);

    if (pMenu == NULL || (uFlags & ~MF_VALID) != 0) {
        return (UINT)-1;
    }

    return _GetMenuState(pMenu, uId, uFlags);
}



FUNCLOG1(LOG_GENERAL, BOOL, DUMMYCALLINGTYPE, IsWindow, HWND, hwnd)
BOOL IsWindow(
    HWND hwnd)
{
    PWND pwnd;

    /*
     * Validate the handle is of type window
     */
    pwnd = ValidateHwndNoRip(hwnd);

    /*
     * And validate this handle is valid for this desktop by trying to read it
     */
    if (pwnd != NULL) {
        try {
            if (pwnd->fnid & FNID_DELETED_BIT) {
                pwnd = 0;
            }
        } except (W32ExceptionHandler(FALSE, RIP_WARNING)) {
            RIPMSG1(RIP_WARNING, "IsWindow: Window %#p not of this desktop",
                    pwnd);
            pwnd = 0;
        }
    }
    return !!pwnd;
}



FUNCLOG2(LOG_GENERAL, HWND, DUMMYCALLINGTYPE, GetWindow, HWND, hwnd, UINT, wCmd)
HWND GetWindow(
    HWND hwnd,
    UINT wCmd)
{
    PWND pwnd;

    pwnd = ValidateHwnd(hwnd);
    if (pwnd == NULL)
        return NULL;

    pwnd = _GetWindow(pwnd, wCmd);
    return HW(pwnd);
}


FUNCLOG1(LOG_GENERAL, HWND, DUMMYCALLINGTYPE, GetParent, HWND, hwnd)
HWND GetParent(
    HWND hwnd)
{
    PWND        pwnd;
    PCLIENTINFO pci;

    pwnd = ValidateHwnd(hwnd);
    if (pwnd == NULL)
        return NULL;

    try {
        pwnd = _GetParent(pwnd);
        hwnd = HW(pwnd);
    } except (W32ExceptionHandler(FALSE, RIP_WARNING)) {
        hwnd = NULL;
    }

    pci = GetClientInfo();

    /*
     * validate the parent window's handle if a restricted process
     */
    if (pci && (pci->dwTIFlags & TIF_RESTRICTED)) {
        if (ValidateHwnd(hwnd) == NULL) {
            return NULL;
        }
    }

    return hwnd;
}


FUNCLOG2(LOG_GENERAL, HMENU, DUMMYCALLINGTYPE, GetSubMenu, HMENU, hMenu, int, nPos)
HMENU GetSubMenu(
    HMENU hMenu,
    int nPos)
{
    PMENU pMenu;

    pMenu = VALIDATEHMENU(hMenu);

    if (pMenu == NULL)
        return 0;

    pMenu = _GetSubMenu(pMenu, nPos);
    return (HMENU)PtoH(pMenu);
}



FUNCLOG1(LOG_GENERAL, DWORD, DUMMYCALLINGTYPE, GetSysColor, int, nIndex)
DWORD GetSysColor(
    int nIndex)
{

    /*
     * Currently we don't do client side checks because they do not really
     * make sense;  someone can read the data even with the checks.  We
     * leave in the attribute values in case we want to move these values
     * back to the server side someday
     */
#ifdef ENABLE_CLIENTSIDE_ACCESSCHECK
    /*
     * Make sure we have access to the system colors.
     */
    if (!(gamWinSta & WINSTA_READATTRIBUTES)) {
        return 0;
    }
#endif

    /*
     * Return 0 if the index is out of range.
     */
    if (nIndex < 0 || nIndex >= COLOR_MAX) {
        RIPERR1(ERROR_INVALID_PARAMETER,
                RIP_WARNING,
                "Invalid parameter \"nIndex\" (%ld) to GetSysColor",
                nIndex);

        return 0;
    }

    return (gpsi->argbSystem[nIndex]);
}



FUNCLOG1(LOG_GENERAL, int, DUMMYCALLINGTYPE, GetSystemMetrics, int, index)
int GetSystemMetrics(
    int index)
{
    int ret;

    BEGIN_USERAPIHOOK()
        ret = guah.pfnGetSystemMetrics(index);
    END_USERAPIHOOK()

    return ret;
}


int RealGetSystemMetrics(
    int index)
{
    ConnectIfNecessary(0);

    /*
     * First check for values that aren't in the aiSysMet array.
     */
    switch (index) {
    case SM_REMOTESESSION:
        return ISREMOTESESSION();
    }

    /*
     * If it's in the BOOLEAN system metric range then do our magic.
     */
    if (index >= SM_STARTBOOLRANGE && index <= SM_ENDBOOLRANGE) {
        return SYSMETBOOL2(index);
    }

    if ((index < 0) || (index >= SM_CMETRICS)) {
        return 0;
    }

    switch (index) {
    case SM_DBCSENABLED:
#ifdef FE_SB
        return TEST_BOOL_FLAG(gpsi->wSRVIFlags, SRVIF_DBCS);
#else
        return FALSE;
#endif
    case SM_IMMENABLED:
#ifdef FE_IME
        return TEST_BOOL_FLAG(gpsi->wSRVIFlags, SRVIF_IME);
#else
        return FALSE;
#endif

    case SM_MIDEASTENABLED:
        return TEST_BOOL_FLAG(gpsi->wSRVIFlags, SRVIF_MIDEAST);
    }

    if (!Is400Compat(GetClientInfo()->dwExpWinVer)) {
        /*
         * SCROLL BAR
         * before 4.0, the scroll bars and the border overlapped by a pixel.  Many apps
         * rely on this overlap when they compute dimensions.  Now, in 4.0, this pixel
         * overlap is no longer there.  So for old apps, we lie and pretend the overlap
         * is there by making the scroll bar widths one bigger.
         *
         * DLGFRAME
         * In Win3.1, SM_CXDLGFRAME & SM_CYDLGFRAME were border space MINUS 1
         * In Win4.0, they are border space
         *
         * CAPTION
         * In Win3.1, SM_CYCAPTION was the caption height PLUS 1
         * In Win4.0, SM_CYCAPTION is the caption height
         *
         * MENU
         * In Win3.1, SM_CYMENU was the menu height MINUS 1
         * In Win4.0, SM_CYMENU is the menu height
         */

        switch (index) {

        case SM_CXDLGFRAME:
        case SM_CYDLGFRAME:
        case SM_CYMENU:
        case SM_CYFULLSCREEN:
            return gpsi->aiSysMet[index] - 1;

        case SM_CYCAPTION:
        case SM_CXVSCROLL:
        case SM_CYHSCROLL:
            return gpsi->aiSysMet[index] + 1;
        }
    }

    return gpsi->aiSysMet[index];
}

/***************************************************************************\
* GetTopWindow (API)
*
* This poorly named API should really be called 'GetFirstChild', which is
* what it does.
*
* History:
* 11-12-90 darrinm      Ported.
* 02-19-91 JimA         Added enum access check
* 05-04-02 DarrinM      Removed enum access check and moved to USERRTL.DLL
\***************************************************************************/
FUNCLOG1(LOG_GENERAL, HWND, DUMMYCALLINGTYPE, GetTopWindow, HWND, hwnd)
HWND GetTopWindow(
    HWND hwnd)
{
    PWND pwnd;

    /*
     * Allow a NULL hwnd to go through here.
     */
    if (hwnd == NULL) {
        pwnd = _GetDesktopWindow();
    } else {
        pwnd = ValidateHwnd(hwnd);
    }
    if (pwnd == NULL)
        return NULL;

    pwnd = REBASEPWND(pwnd, spwndChild);
    return HW(pwnd);
}



FUNCLOG2(LOG_GENERAL, BOOL, DUMMYCALLINGTYPE, IsChild, HWND, hwndParent, HWND, hwnd)
BOOL IsChild(
    HWND hwndParent,
    HWND hwnd)
{
    PWND pwnd, pwndParent;

    pwnd = ValidateHwnd(hwnd);
    if (pwnd == NULL)
        return FALSE;

    pwndParent = ValidateHwnd(hwndParent);
    if (pwndParent == NULL)
        return FALSE;

    return _IsChild(pwndParent, pwnd);
}


FUNCLOG1(LOG_GENERAL, BOOL, DUMMYCALLINGTYPE, IsIconic, HWND, hwnd)
BOOL IsIconic(
    HWND hwnd)
{
    PWND pwnd;

    pwnd = ValidateHwnd(hwnd);

    if (pwnd == NULL)
        return FALSE;

    return _IsIconic(pwnd);
}


FUNCLOG1(LOG_GENERAL, BOOL, DUMMYCALLINGTYPE, IsWindowEnabled, HWND, hwnd)
BOOL IsWindowEnabled(
    HWND hwnd)
{
    PWND pwnd;

    pwnd = ValidateHwnd(hwnd);

    if (pwnd == NULL)
        return FALSE;

    return _IsWindowEnabled(pwnd);
}


FUNCLOG1(LOG_GENERAL, BOOL, DUMMYCALLINGTYPE, IsWindowVisible, HWND, hwnd)
BOOL IsWindowVisible(
    HWND hwnd)
{
    PWND pwnd;
    BOOL bRet;

    pwnd = ValidateHwnd(hwnd);

    /*
     * We have have to try - except this call because there is no
     * synchronization on the window structure on the client side.
     * If the window is deleted after it is validated then we can
     * fault so we catch that on return that the window is not
     * visible.  As soon as this API returns there is no guarentee
     * the return is still valid in a muli-tasking environment.
     */
    try {
        if (pwnd == NULL) {
            bRet = FALSE;
        } else {
            bRet = _IsWindowVisible(pwnd);
        }
    } except (W32ExceptionHandler(FALSE, RIP_WARNING)) {
        bRet = FALSE;
    }

    return bRet;
}


FUNCLOG1(LOG_GENERAL, BOOL, DUMMYCALLINGTYPE, IsZoomed, HWND, hwnd)
BOOL IsZoomed(
    HWND hwnd)
{
    PWND pwnd;

    pwnd = ValidateHwnd(hwnd);

    if (pwnd == NULL)
        return FALSE;

    return _IsZoomed(pwnd);
}


FUNCLOG2(LOG_GENERAL, BOOL, DUMMYCALLINGTYPE, ClientToScreen, HWND, hwnd, LPPOINT, ppoint)
BOOL ClientToScreen(
    HWND hwnd,
    LPPOINT ppoint)
{
    PWND pwnd;

    pwnd = ValidateHwnd(hwnd);

    if (pwnd == NULL)
        return FALSE;

    _ClientToScreen(pwnd, ppoint);
    return TRUE;
}


FUNCLOG2(LOG_GENERAL, BOOL, DUMMYCALLINGTYPE, GetClientRect, HWND, hwnd, LPRECT, prect)
BOOL GetClientRect(
    HWND   hwnd,
    LPRECT prect)
{
    PWND pwnd;

    pwnd = ValidateHwnd(hwnd);

    if (pwnd == NULL)
        return FALSE;

    _GetClientRect(pwnd, prect);
    return TRUE;
}



FUNCLOG1(LOG_GENERAL, BOOL, DUMMYCALLINGTYPE, GetCursorPos, LPPOINT, lpPoint)
BOOL GetCursorPos(
    LPPOINT lpPoint)
{
    return  (BOOL)NtUserCallOneParam((ULONG_PTR)lpPoint, SFI_XXXGETCURSORPOS);
}


FUNCLOG2(LOG_GENERAL, BOOL, DUMMYCALLINGTYPE, GetWindowRect, HWND, hwnd, LPRECT, prect)
BOOL GetWindowRect(
    HWND hwnd,
    LPRECT prect)
{
    PWND pwnd;

    pwnd = ValidateHwnd(hwnd);

    if (pwnd == NULL)
        return FALSE;

    _GetWindowRect(pwnd, prect);
    return TRUE;
}


FUNCLOG2(LOG_GENERAL, BOOL, DUMMYCALLINGTYPE, ScreenToClient, HWND, hwnd, LPPOINT, ppoint)
BOOL ScreenToClient(
    HWND hwnd,
    LPPOINT ppoint)
{
    PWND pwnd;

    pwnd = ValidateHwnd(hwnd);

    if (pwnd == NULL)
        return FALSE;

    _ScreenToClient(pwnd, ppoint);
    return TRUE;
}


FUNCLOG3(LOG_GENERAL, BOOL, DUMMYCALLINGTYPE, EnableMenuItem, HMENU, hMenu, UINT, uIDEnableItem, UINT, uEnable)
BOOL EnableMenuItem(
    HMENU hMenu,
    UINT uIDEnableItem,
    UINT uEnable)
{
    PMENU pMenu;
    PITEM pItem;

    pMenu = VALIDATEHMENU(hMenu);
    if (pMenu == NULL) {
        return (BOOL)-1;
    }

    /*
     * Get a pointer the the menu item
     */
    if ((pItem = MNLookUpItem(pMenu, uIDEnableItem, (BOOL) (uEnable & MF_BYPOSITION), NULL)) == NULL)
        return (DWORD)-1;

    /*
     * If the item is already in the state we're
     * trying to set, just return.
     */
    if ((pItem->fState & MFS_GRAYED) ==
            (uEnable & MFS_GRAYED)) {
        return pItem->fState & MFS_GRAYED;
    }

    return NtUserEnableMenuItem(hMenu, uIDEnableItem, uEnable);
}

/***************************************************************************\
* CallNextHookEx
*
* This routine is called to call the next hook in the hook chain.
*
* 05-09-91 ScottLu Created.
\***************************************************************************/
FUNCLOG4(LOG_GENERAL, LRESULT, WINAPI, CallNextHookEx, HHOOK, hhk, int, nCode, WPARAM, wParam, LPARAM, lParam)
LRESULT WINAPI CallNextHookEx(
    HHOOK hhk,
    int nCode,
    WPARAM wParam,
    LPARAM lParam)
{
    LRESULT nRet;
    BOOL  bAnsi;
    DWORD dwHookCurrent;
    PCLIENTINFO pci;
    ULONG_PTR dwHookData;
    ULONG_PTR dwFlags;

    DBG_UNREFERENCED_PARAMETER(hhk);

    ConnectIfNecessary(0);

    pci = GetClientInfo();
    dwHookCurrent = pci->dwHookCurrent;
    bAnsi = LOWORD(dwHookCurrent);

    /*
     * If this is the last hook in the hook chain then return 0; we're done
     */
    UserAssert(pci->phkCurrent);
    if (PhkNextValid((PHOOK)((KERNEL_ULONG_PTR)pci->phkCurrent - pci->ulClientDelta)) == NULL) {
        return 0;
    }

    switch ((INT)(SHORT)HIWORD(dwHookCurrent)) {
    case WH_CALLWNDPROC:
    case WH_CALLWNDPROCRET:
        /*
         * This is the hardest of the hooks because we need to thunk through
         * the message hooks in order to deal with synchronously sent messages
         * that point to structures - to get the structures passed across
         * alright, etc.
         *
         * This will call a special kernel-side routine that'll rebundle the
         * arguments and call the hook in the right format.
         *
         * Currently, the message thunk callbacks to the client-side don't take
         * enough parameters to pass wParam (which == fInterThread send msg).
         * To do this, save the state of wParam in the CLIENTINFO structure.
         */
        dwFlags = KERNEL_ULONG_PTR_TO_ULONG_PTR(pci->CI_flags) & CI_INTERTHREAD_HOOK;
        dwHookData = KERNEL_ULONG_PTR_TO_ULONG_PTR(pci->dwHookData);
        if (wParam) {
            pci->CI_flags |= CI_INTERTHREAD_HOOK;
        } else {
            pci->CI_flags &= ~CI_INTERTHREAD_HOOK;
        }

        if ((INT)(SHORT)HIWORD(dwHookCurrent) == WH_CALLWNDPROC) {
            nRet = CsSendMessage(
                    ((LPCWPSTRUCT)lParam)->hwnd,
                    ((LPCWPSTRUCT)lParam)->message,
                    ((LPCWPSTRUCT)lParam)->wParam,
                    ((LPCWPSTRUCT)lParam)->lParam,
                    0, FNID_HKINLPCWPEXSTRUCT, bAnsi);
        } else {
            pci->dwHookData = ((LPCWPRETSTRUCT)lParam)->lResult;
            nRet = CsSendMessage(
                    ((LPCWPRETSTRUCT)lParam)->hwnd,
                    ((LPCWPRETSTRUCT)lParam)->message,
                    ((LPCWPRETSTRUCT)lParam)->wParam,
                    ((LPCWPRETSTRUCT)lParam)->lParam,
                    0, FNID_HKINLPCWPRETEXSTRUCT, bAnsi);
        }

        /*
         * Restore previous hook state.
         */
        pci->CI_flags ^= ((pci->CI_flags ^ dwFlags) & CI_INTERTHREAD_HOOK);
        pci->dwHookData = dwHookData;
        break;

    default:
        nRet = NtUserCallNextHookEx(
                nCode,
                wParam,
                lParam,
                bAnsi);
    }

    return nRet;
}