//
// IM.C
// Input Manager
//
// Copyright(c) 1997-
//

#include <as16.h>



//
// IM_DDInit()
// This creates the resources we need for controlling
//
BOOL IM_DDInit(void)
{
    UINT    uSel;
    BOOL    rc = FALSE;

    DebugEntry(IM_DDInit);

    //
    // Create interrupt patches for mouse_event and keybd_event
    //
    uSel = CreateFnPatch(mouse_event, ASMMouseEvent, &g_imPatches[IM_MOUSEEVENT], 0);
    if (!uSel)
    {
        ERROR_OUT(("Couldn't find mouse_event"));
        DC_QUIT;
    }
    g_imPatches[IM_MOUSEEVENT].fInterruptable = TRUE;


    if (!CreateFnPatch(keybd_event, ASMKeyboardEvent, &g_imPatches[IM_KEYBOARDEVENT], uSel))
    {
        ERROR_OUT(("Couldn't find keybd_event"));
        DC_QUIT;
    }
    g_imPatches[IM_KEYBOARDEVENT].fInterruptable = TRUE;

    //
    // Create patch for SignalProc32 so we can find out when fault/hung
    // dialogs from KERNEL32 come up.
    //
    if (!CreateFnPatch(SignalProc32, DrvSignalProc32, &g_imPatches[IM_SIGNALPROC32], 0))
    {
        ERROR_OUT(("Couldn't patch SignalProc32"));
        DC_QUIT;
    }

    //
    // Create patches for win16lock pulsing in 16-bit app modal loops
    //
    uSel = CreateFnPatch(RealGetCursorPos, DrvGetCursorPos, &g_imPatches[IM_GETCURSORPOS], 0);
    if (!uSel)
    {
        ERROR_OUT(("Couldn't find GetCursorPos"));
        DC_QUIT;
    }

    if (!CreateFnPatch(GetAsyncKeyState, DrvGetAsyncKeyState, &g_imPatches[IM_GETASYNCKEYSTATE], 0))
    {
        ERROR_OUT(("Couldn't find GetAsyncKeyState"));
        DC_QUIT;
    }

    rc = TRUE;

DC_EXIT_POINT:
    DebugExitBOOL(IM_DDInit, rc);
    return(rc);
}


//
// IM_DDTerm()
// This cleans up any resources we needed for controlling
//
void IM_DDTerm(void)
{
    IM_PATCH    imPatch;

    DebugEntry(IM_DDTerm);

    //
    // Force undo of hooks
    //
    OSIInstallControlledHooks16(FALSE);

    //
    // Destroy patches
    //
    for (imPatch = IM_FIRST; imPatch < IM_MAX; imPatch++)
    {
        DestroyFnPatch(&g_imPatches[imPatch]);
    }

    DebugExitVOID(IM_DDTerm);
}



//
// OSIInstallControlledHooks16()
//
// This installs/removes the input hooks we need to allow this machine to
// be controlled.  
//
BOOL WINAPI OSIInstallControlledHooks16(BOOL fEnable)
{               
    BOOL        rc = TRUE;
    IM_PATCH    imPatch;

    DebugEntry(OSIInstallControlledHooks16);

    if (fEnable)
    {
        if (!g_imWin95Data.imLowLevelHooks)
        {
            g_imWin95Data.imLowLevelHooks = TRUE;

            g_imMouseDowns = 0;

            //
            // GlobalSmartPageLock() stuff we need:
            //      * Our code segment
            //      * Our data segment
            //
            GlobalSmartPageLock((HGLOBAL)SELECTOROF((LPVOID)DrvMouseEvent));
            GlobalSmartPageLock((HGLOBAL)SELECTOROF((LPVOID)&g_imSharedData));

            //
            // Install hooks
            //
            for (imPatch = IM_FIRST; imPatch < IM_MAX; imPatch++)
            {
                EnableFnPatch(&g_imPatches[imPatch], PATCH_ACTIVATE);
            }
        }
    }
    else
    {
        if (g_imWin95Data.imLowLevelHooks)
        {
            //
            // Uninstall hooks
            //
            for (imPatch = IM_MAX; imPatch > 0; imPatch--)
            {
                EnableFnPatch(&g_imPatches[imPatch-1], PATCH_DEACTIVATE);
            }

            //
            // GlobalSmartUnPageLock() stuff we needed
            //
            GlobalSmartPageUnlock((HGLOBAL)SELECTOROF((LPVOID)&g_imSharedData));
            GlobalSmartPageUnlock((HGLOBAL)SELECTOROF((LPVOID)DrvMouseEvent));

            g_imWin95Data.imLowLevelHooks = FALSE;
        }
    }

    DebugExitBOOL(OSIInstallControlledHooks16, rc);
    return(rc);
}


#pragma optimize("gle", off)
void IMInject(BOOL fOn)
{
    if (fOn)
    {
#ifdef DEBUG
        DWORD   tmp;

        //
        // Disable interrupts then turn injection global on
        // But before we do this, we must make sure that we aren't going
        // to have to fault in a new stack page.  Since this is on a 32-bit
        // thread, we will be in trouble.
        //
        tmp = GetSelectorBase(SELECTOROF(((LPVOID)&fOn))) +
            OFFSETOF((LPVOID)&fOn);
        if ((tmp & 0xFFFFF000) != ((tmp - 0x100) & 0xFFFFF000))
        {
            ERROR_OUT(("Close to page boundary on 32-bit stack %08lx", tmp));
        }
#endif // DEBUG

        _asm    cli
        g_imWin95Data.imInjecting = TRUE;
    }
    else
    {
        //
        // Turn injection global off then enable interrupts
        //
        g_imWin95Data.imInjecting = FALSE;
        _asm    sti
    }
}
#pragma optimize("", on)


//
// OSIInjectMouseEvent16()
//
void WINAPI OSIInjectMouseEvent16
(
    UINT    flags,
    int     x,
    int     y,
    UINT    mouseData,
    DWORD   dwExtraInfo
)
{
    DebugEntry(OSIInjectMouseEvent16);

    if (flags & IM_MOUSEEVENTF_BUTTONDOWN_FLAGS)
    {
        ++g_imMouseDowns;
    }

    //
    // We disable interrupts, call the real mouse_event, reenable
    // interrupts.  That way our mouse_event patch is serialized.
    // And we can check imInjecting.
    //
    IMInject(TRUE);
    CallMouseEvent(flags, x, y, mouseData, LOWORD(dwExtraInfo), HIWORD(dwExtraInfo));
    IMInject(FALSE);

    if (flags & IM_MOUSEEVENTF_BUTTONUP_FLAGS)
    {
        --g_imMouseDowns;
        ASSERT(g_imMouseDowns >= 0);
    }

    DebugExitVOID(OSIInjectMouseEvent16);
}



//
// OSIInjectKeyboardEvent16()
//
void WINAPI OSIInjectKeyboardEvent16
(
    UINT    flags,
    WORD    vkCode,
    WORD    scanCode,
    DWORD   dwExtraInfo
)
{
    DebugEntry(OSIInjectKeyboardEvent16);

    //
    // First, fix up the flags
    //
    if (flags & KEYEVENTF_KEYUP)
    {
        // Put 0x80 in the HIBYTE of vkCode, this means a keyup
        vkCode = (WORD)(BYTE)vkCode | USERKEYEVENTF_KEYUP;
    }

    if (flags & KEYEVENTF_EXTENDEDKEY)
    {                         
        // Put 0x01 in the HIBYTE of scanCode, this means extended
        scanCode = (WORD)(BYTE)scanCode | USERKEYEVENTF_EXTENDEDKEY;
    }

    //
    // We disable interrupts, call the real keybd_event, reenable
    // interrupts.  That way our keybd_event patch is serialized.
    // And we can check the imfInject variable.
    //
    IMInject(TRUE);
    CallKeyboardEvent(vkCode, scanCode, LOWORD(dwExtraInfo), HIWORD(dwExtraInfo));
    IMInject(FALSE);

    DebugExitVOID(OSIInjectKeyboardEvent16);
}



//
// Win16lock pulse points when injecting mouse down/up sequences into 16-bit
// modal loop apps.
//


//
// IMCheckWin16LockPulse()
// This pulses the win16lock if we are in the middle of injecting a mouse
// down-up sequence to a 16-bit app shared on this machine.  We do this to
// prevent deadlock, caused by 16-bit dudes going into modal loops, not
// releasing the win16lock.  Our 32-bit thread would get stuck on the win16
// lock trying to play back the rest of the sequence.
//
void IMCheckWin16LockPulse(void)
{
    DebugEntry(IMCheckWin16LockPulse);

    if ((g_imMouseDowns > 0) &&
        (GetProcessDword(0, GPD_FLAGS) & GPF_WIN16_PROCESS))
    {
        TRACE_OUT(("Pulsing win16lock for 16-bit app; mouse down count %d", g_imMouseDowns));

        _LeaveWin16Lock();
        _EnterWin16Lock();

        TRACE_OUT(("Pulsed win16lock for 16-bit app; mouse down count %d", g_imMouseDowns));
    }

    DebugExitVOID(IMCheckWin16LockPulse);
}



int WINAPI DrvGetAsyncKeyState(int vk)
{
    int     retVal;

    DebugEntry(DrvGetAsyncKeyState);

    // Pulse BEFORE we call to USER
    IMCheckWin16LockPulse();

    EnableFnPatch(&g_imPatches[IM_GETASYNCKEYSTATE], PATCH_DISABLE);
    retVal = GetAsyncKeyState(vk);
    EnableFnPatch(&g_imPatches[IM_GETASYNCKEYSTATE], PATCH_ENABLE);

    DebugExitBOOL(DrvGetAsyncKeyState, retVal);
    return(retVal);
}



//
// DrvGetCursorPos()
//
BOOL WINAPI DrvGetCursorPos(LPPOINT lppt)
{
    BOOL    retVal;

    DebugEntry(DrvGetCursorPos);

    // Pulse BEFORE calling USER
    IMCheckWin16LockPulse();

    EnableFnPatch(&g_imPatches[IM_GETCURSORPOS], PATCH_DISABLE);
    retVal = RealGetCursorPos(lppt);
    EnableFnPatch(&g_imPatches[IM_GETCURSORPOS], PATCH_ENABLE);

    DebugExitBOOL(DrvGetCursorPos, retVal);
    return(retVal);
}


//
// DrvMouseEvent()
// mouse_event interrupt patch
//
void WINAPI DrvMouseEvent
(
    UINT    regAX,
    UINT    regBX,
    UINT    regCX,
    UINT    regDX,
    UINT    regSI,
    UINT    regDI
)
{
    BOOL    fAllow;

    //
    // If this is injected by us, just pass it through.
    //
    fAllow = TRUE;
    if (g_imWin95Data.imInjecting)
    {
        DC_QUIT;
    }

    //
    // NOTE:
    //      flags is in     AX
    //      x coord is in   BX
    //      y coord is in   CX
    //      mousedata is in DX
    //      dwExtraInfo is in DI, SI
    //

    if (g_imSharedData.imControlled && !g_imSharedData.imPaused)
    {
        //
        // If this is a button click, take control back
        //
        if (regAX &
            (MOUSEEVENTF_LEFTDOWN | MOUSEEVENTF_RIGHTDOWN | MOUSEEVENTF_MIDDLEDOWN))
        {
            if (!g_imSharedData.imUnattended)
            {
                PostMessage(g_asMainWindow, DCS_REVOKECONTROL_MSG, FALSE, 0);
            }
        }

        if (!g_imSharedData.imSuspended)
            fAllow = FALSE;
    }

DC_EXIT_POINT:
    if (fAllow)
    {
        EnableFnPatch(&g_imPatches[IM_MOUSEEVENT], PATCH_DISABLE);
        CallMouseEvent(regAX, regBX, regCX, regDX, regSI, regDI);
        EnableFnPatch(&g_imPatches[IM_MOUSEEVENT], PATCH_ENABLE);
    }
}


//
// DrvKeyboardEvent()
// keybd_event interrupt patch
//
void WINAPI DrvKeyboardEvent
(
    UINT    regAX,
    UINT    regBX,
    UINT    regSI,
    UINT    regDI
)
{
    BOOL    fAllow;

    //
    // If this is injected by us, pass it through.  Do the same for
    // critical errors, since everything is frozen and we can't play back
    // input if we wanted to.
    //
    // If the scan-code (in regBX) is 0 we assume that the input
    // is injected by an application (such as an IME) and we don't
    // want to block this or take control.
    //

    fAllow = TRUE;
    if (g_imWin95Data.imInjecting || !regBX)
    {
        DC_QUIT;
    }

    //
    // NOTE:
    //      vkCode is in    AX, LOBYTE is vkCode, HIBYTE is state
    //      scanCode is in  BX
    //      dwExtraInfo is in   DI, SI
    //

    if (g_imSharedData.imControlled && !g_imSharedData.imPaused)
    {
        if (!(regAX & USERKEYEVENTF_KEYUP))
        {
            //
            // This is a key down.  Take control back (except for ALT key),
            // and kill control allowability if it's the ESC key.
            //

            if (LOBYTE(regAX) == VK_ESCAPE)
            {
                PostMessage(g_asMainWindow, DCS_ALLOWCONTROL_MSG, FALSE, 0);
            }
            else if (LOBYTE(regAX != VK_MENU))
            {
                if (!g_imSharedData.imUnattended)
                {
                    PostMessage(g_asMainWindow, DCS_REVOKECONTROL_MSG, 0, 0);
                }
            }
        }

        //
        // Don't discard toggle keys.  The enabled/disabled function
        // is already set before we see the keystroke.  If we discard,
        // the lights are incorrect.
        //
        if (!IM_KEY_IS_TOGGLE(LOBYTE(regAX)) && !g_imSharedData.imSuspended)
        {
            fAllow = FALSE;
        }
    }

DC_EXIT_POINT:
    if (fAllow)
    {
        EnableFnPatch(&g_imPatches[IM_KEYBOARDEVENT], PATCH_DISABLE);
        CallKeyboardEvent(regAX, regBX, regSI, regDI);
        EnableFnPatch(&g_imPatches[IM_KEYBOARDEVENT], PATCH_ENABLE);
    }
}



//
// DrvSignalProc32()
// This patches USER's SignalProc32 export and watches for the FORCE_LOCK
// signals.  KERNEL32 calls them before/after putting up critical error and
// fault dialogs.  That's how we know when one is coming up, and can 
// temporarily suspend remote control of your machine so you can dismiss
// them.  Usually the thread they are on is boosted so high priority that
// nothing else can run, and so NM can't pump in input from the remote.
//
BOOL WINAPI DrvSignalProc32
(
    DWORD   dwSignal,
    DWORD   dwID,
    DWORD   dwFlags,
    WORD    hTask16
)
{
    BOOL    fRet;

    DebugEntry(DrvSignalProc32);

    if (dwSignal == SIG_PRE_FORCE_LOCK)
    {
        TRACE_OUT(("Disabling remote control before critical dialog, count %ld",
            g_imSharedData.imSuspended));
        ++g_imSharedData.imSuspended;
    }

    EnableFnPatch(&g_imPatches[IM_SIGNALPROC32], PATCH_DISABLE);
    fRet = SignalProc32(dwSignal, dwID, dwFlags, hTask16);
    EnableFnPatch(&g_imPatches[IM_SIGNALPROC32], PATCH_ENABLE);

    if (dwSignal == SIG_POST_FORCE_LOCK)
    {
        --g_imSharedData.imSuspended;
        TRACE_OUT(("Enabling remote control after critical dialog, count %ld",
            g_imSharedData.imSuspended));
    }

    DebugExitBOOL(DrvSignalProc32, fRet);
    return(fRet);
}



