/*****************************************************************************
 *
 *  DIRaw.c
 *
 *  Copyright (c) 2000 Microsoft Corporation.  All Rights Reserved.
 *
 *  Abstract:
 *
 *      DirectInput Raw Input Device processor.
 *
 *  Contents:
 *
 *      CDIRaw_RegisterRawInputDevice
 *      CDIRaw_UnregisterRawInputDevice
 *      CDIRaw_ProcessInput
 *      CDIRaw_OnInput
 *
 *****************************************************************************/

#include "dinputpr.h"

#ifdef USE_WM_INPUT

#include "ntddkbd.h"

#define sqfl sqflRaw

extern DIMOUSESTATE_INT s_msEd; //in diemm.c
extern ED s_edMouse;            //in diemm.c
extern ED s_edKbd;              //in diemk.c
extern LPBYTE g_pbKbdXlat;      //in diemk.c

static RAWMOUSE s_absrm;
static BOOL s_fFirstRaw;

#ifndef RIDEV_INPUTSINK
  // RIDEV_INPUTSINK is defined in winuserp.h
  #define RIDEV_INPUTSINK   0x00000100
#endif

#ifndef RIDEV_NOHOTKEYS
  #define RIDEV_NOHOTKEYS   0x00000200
#endif

RAWINPUTDEVICE ridOn[] = {
    { HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_MOUSE,    RIDEV_INPUTSINK },
    { HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_MOUSE,    RIDEV_NOLEGACY | RIDEV_CAPTUREMOUSE },
    { HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_KEYBOARD, RIDEV_INPUTSINK },
    { HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_KEYBOARD, RIDEV_NOLEGACY | RIDEV_NOHOTKEYS },
    { HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_KEYBOARD, RIDEV_NOHOTKEYS },
};

RAWINPUTDEVICE ridOff[] = {
    { HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_MOUSE,    RIDEV_REMOVE },
    { HID_USAGE_PAGE_GENERIC, HID_USAGE_GENERIC_KEYBOARD, RIDEV_REMOVE },
};


/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULE | CDIRaw_RegisterRawInputDevice |
 *
 *          register raw input device.
 *
 *  @parm   IN UINT | uirim |
 *
 *          the type of device: RIM_TYPEMOUSE or RIM_TYPEKEYBOARD
 *
 *  @parm   IN DWORD | dwOrd |
 *
 *          dwOrd determines which item of ridOn will be used for registration.
 *
 *  @parm   IN HWND | hwnd |
 *
 *          the window handler used by RegisterRawInputDevices.
 *
 *  @returns
 *
 *          S_OK - successful
 *          E_FAIL - otherwise
 *
 *****************************************************************************/

HRESULT CDIRaw_RegisterRawInputDevice( UINT uirim, DWORD dwOrd, HWND hwnd)
{
    HRESULT hres;

    AssertF( (uirim == RIM_TYPEMOUSE) || (uirim == RIM_TYPEKEYBOARD) );

    if( hwnd ) {
        ridOn[uirim*2+dwOrd].hwndTarget = hwnd;
    }

    if( RegisterRawInputDevices &&
        RegisterRawInputDevices(&ridOn[uirim*2+dwOrd], 1, sizeof(RAWINPUTDEVICE)) ) {
        SquirtSqflPtszV(sqfl, TEXT("RegisterRawInputDevice: %s, mode: %s, hwnd: 0x%08lx"),
                            uirim==0 ? TEXT("mouse"):TEXT("keyboard"), 
                            dwOrd==0 ? TEXT("NONEXCL") : dwOrd==1 ? TEXT("EXCL") : TEXT("NOWIN"),
                            hwnd);
        hres = S_OK;
    } else {
        hres = E_FAIL;
    }

    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULE | CDIRaw_UnregisterRawInputDevice |
 *
 *          unregister raw input device.
 *
 *  @parm   IN UINT | uirim |
 *
 *          the type of device: RIM_TYPEMOUSE or RIM_TYPEKEYBOARD
 *
 *  @parm   IN HWND | hwnd |
 *
 *          the window handler used by RegisterRawInputDevices.
 *
 *  @returns
 *
 *          S_OK - successful
 *          E_FAIL - otherwise
 *
 *****************************************************************************/

HRESULT CDIRaw_UnregisterRawInputDevice( UINT uirim, HWND hwnd )
{
    HRESULT hres;

    AssertF( (uirim == RIM_TYPEMOUSE) || (uirim == RIM_TYPEKEYBOARD) );

    if( hwnd ) {
        ridOn[uirim].hwndTarget = hwnd;
    }

    if( RegisterRawInputDevices &&
        RegisterRawInputDevices(&ridOff[uirim], 1, sizeof(RAWINPUTDEVICE)) ) {
        SquirtSqflPtszV(sqfl, TEXT("UnregisterRawInputDevice: %s, hwnd: 0x%08lx"),
                            uirim==0 ? TEXT("mouse"):TEXT("keyboard"), hwnd);
        hres = S_OK;
    } else {
        hres = E_FAIL;
    }

    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   void | CDIRaw_ProcessInput |
 *
 *          Process raw input device data.
 *
 *  @parm   IN PRAWINPUT | pRawInput |
 *
 *          pointer to RAWINPUT data
 *
 *  @returns
 *
 *          void
 *
 *****************************************************************************/

void CDIRaw_ProcessInput(PRAWINPUT pRawInput)
{
    HANDLE hDevice = pRawInput->header.hDevice;

    if ( g_plts ) {
        if( g_plts->rglhs[LLTS_MSE].cHook && pRawInput->header.dwType == RIM_TYPEMOUSE )
        {
            DIMOUSESTATE_INT ms;
            RAWMOUSE        *prm = &pRawInput->data.mouse;

            memcpy(ms.rgbButtons, s_msEd.rgbButtons, cbX(ms.rgbButtons));

            if( prm->usFlags & MOUSE_MOVE_ABSOLUTE ) {
                if( s_fFirstRaw ) {
                    memcpy( &s_absrm, prm, sizeof(RAWMOUSE) );
                    s_fFirstRaw = FALSE;
                    return;
                } else {
                    RAWMOUSE rm;

                    memcpy( &rm, prm, sizeof(RAWMOUSE) );

                    prm->lLastX -= s_absrm.lLastX;
                    prm->lLastY -= s_absrm.lLastY;
                    if ( prm->usButtonFlags & RI_MOUSE_WHEEL ) {
                        prm->usButtonData -= s_absrm.usButtonData;
                    }

                    memcpy( &s_absrm, &rm, sizeof(RAWMOUSE) );
                }
            }

            ms.lX = prm->lLastX;
            ms.lY = prm->lLastY;
            if ( prm->usButtonFlags & RI_MOUSE_WHEEL ) {
                ms.lZ = (short)prm->usButtonData;
            } else {
                ms.lZ = 0;
            }

            if( prm->usButtonFlags & RI_MOUSE_LEFT_BUTTON_DOWN ) {
                ms.rgbButtons[0] = 0x80;
            } else if (prm->usButtonFlags & RI_MOUSE_LEFT_BUTTON_UP ) {
                ms.rgbButtons[0] = 0x00;
            }

            if( prm->usButtonFlags & RI_MOUSE_RIGHT_BUTTON_DOWN ) {
                ms.rgbButtons[1] = 0x80;
            } else if (prm->usButtonFlags & RI_MOUSE_RIGHT_BUTTON_UP) {
                ms.rgbButtons[1] = 0x00;
            }

            if( prm->usButtonFlags & RI_MOUSE_MIDDLE_BUTTON_DOWN ) {
                ms.rgbButtons[2] = 0x80;
            } else if( prm->usButtonFlags & RI_MOUSE_MIDDLE_BUTTON_UP ) {
                ms.rgbButtons[2] = 0x00;
            }

            if( prm->usButtonFlags & RI_MOUSE_BUTTON_4_DOWN ) {
                ms.rgbButtons[3] = 0x80;
            } else if( prm->usButtonFlags & RI_MOUSE_BUTTON_4_UP ) {
                ms.rgbButtons[3] = 0x00;
            }

            if( prm->usButtonFlags & RI_MOUSE_BUTTON_5_DOWN ) {
                ms.rgbButtons[4] = 0x80;
            } else if ( prm->usButtonFlags & RI_MOUSE_BUTTON_5_UP ) {
                ms.rgbButtons[4] = 0x00;
            }

            #if 0
            {
                char buf[128];
                static DWORD cnt = 0;

                wsprintfA(buf, "%d: x: %ld (%ld), y: %ld (%ld), z: %ld, rgb[0]: 0x%lx, rgb[4]: 0x%lx", cnt, prm->lLastX,ms.lX, prm->lLastY, ms.lY, (short)prm->usButtonData,*(DWORD *)ms.rgbButtons,*(DWORD *)&ms.rgbButtons[4]);
                RPF(buf);
                cnt++;
            }
            #endif

            CEm_Mouse_AddState(&ms, GetTickCount());

        } else
        if ( g_plts->rglhs[LLTS_KBD].cHook && pRawInput->header.dwType == RIM_TYPEKEYBOARD ) {
            RAWKEYBOARD *prk = &pRawInput->data.keyboard;
            BYTE bAction, bScan;
            static BOOL fE1 = FALSE;

            bAction = (prk->Flags & KEY_BREAK) ? 0 : 0x80;
            bScan   = (BYTE)prk->MakeCode;

            if( prk->Flags & KEY_E0 ) {
                if( bScan == 0x2A ) {  //special extra scancode when pressing PrtScn
                    return;
                } else {
                    bScan |= 0x80;
                }
            } else if( prk->Flags & KEY_E1 ) {  //special key: PAUSE
                fE1 = TRUE;

                // now, we need to bypass 0x1d key for compitibility with low level hook.
                if( bScan == 0x1d ) {
                    return;
                }
            }

            if( fE1 ) {
                // This is the work around for bug 288535.
                // But we really don't want to fix it in this way.
                //if( !bAction ) {
                //  Sleep(80);
                //}
                bScan |= 0x80;
                fE1 = FALSE;
            }

            AssertF(g_pbKbdXlat);
            if( bScan != 0x45 && bScan != 0xc5 ) {
                bScan = g_pbKbdXlat[bScan];
            }

            #if 0
            {
                char buf[128];
                static DWORD cnt = 0;
                
                wsprintfA(buf, "%d: bAction: 0x%lx, bScan: 0x%lx, Flags: 0x%lx, Make: 0x%lx", cnt, bAction, bScan, prk->Flags,prk->MakeCode);
                RPF(buf);
                cnt++;
            }
            #endif

            CEm_AddEvent(&s_edKbd, bAction, bScan, GetTickCount());
        }
    }

    return;
}


/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   void | CDIRaw_OnInput |
 *
 *          WM_INPUT message handler used by CEm_LL_ThreadProc (in diem.c).
 *
 *  @parm   IN MSG * | pmsg |
 *
 *          pointer to MSG
 *
 *  @returns
 *
 *          TRUE = Successful
 *          FALSE = otherwise
 *
 *****************************************************************************/

BOOL CDIRaw_OnInput(MSG *pmsg)
{
    BOOL  fRtn = FALSE;
    HRAWINPUT hRawInput = (HRAWINPUT)pmsg->lParam;
    PRAWINPUT pRawInput;
    UINT cbSize;
    BYTE pbBuf[512];
    BOOL fMalloc;
    UINT uiRtn;

    //
    // Firstly, get the size of this Raw Input.
    //
    if ( (uiRtn = GetRawInputData(hRawInput, RID_INPUT, NULL, &cbSize, sizeof(RAWINPUTHEADER))) != 0) {
        return FALSE;
    }

    //
    // Allocate required memory.
    //
    if( cbSize < cbX(pbBuf) ) {
        pRawInput = (PRAWINPUT)pbBuf;
        fMalloc = FALSE;
    } else {
        pRawInput = (PRAWINPUT)malloc(cbSize);
        if (pRawInput == NULL) {
            RPF("Failed to allocate pRawInput\n");
            return FALSE;
        }
        fMalloc = TRUE;
    }

    //
    // Receive the content of the Raw Input.
    //
    if (GetRawInputData(hRawInput, RID_INPUT, pRawInput, &cbSize, sizeof(RAWINPUTHEADER)) > 0) {
        //
        // Call the handler of ours, to start/continue/stop drawing.
        //
        CDIRaw_ProcessInput(pRawInput);
    }

    // no longer needed.
    if( fMalloc ) {
        free(pRawInput);
        pRawInput = NULL;
    }

    return fRtn;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   HRESULT | CDIRaw_Mouse_InitButtons |
 *
 *          Initialize the mouse state in preparation for acquisition.
 *
 *****************************************************************************/

HRESULT INTERNAL
CDIRaw_Mouse_InitButtons(void)
{
    if (s_edMouse.cAcquire < 0) {
        s_fFirstRaw = TRUE;
    }

    return S_OK;
}

/*****************************************************************************
 *
 *  @doc    EXTERNAL
 *
 *  @func   int | DIRaw_GetKeyboardType |
 *
 *          Return keyboard type (nTypeFlag==0) or subtype (nTypeFlag==1).
 *
 *****************************************************************************/

int EXTERNAL
DIRaw_GetKeyboardType(int nTypeFlag)
{
    PRAWINPUTDEVICELIST pList = NULL;
    UINT  uiNumDevices = 0;
    DWORD dwType;
    int   nResult = 0;

    if (GetRawInputDeviceList(NULL, &uiNumDevices, sizeof(RAWINPUTDEVICELIST)) == (UINT)-1) {
        SquirtSqflPtszV(sqfl, TEXT("DIRaw_GetKeyboardType: failed to get the number of devices."));
        goto _DIRAWGKT_EXIT;
    }

    if( uiNumDevices ) {
        pList = malloc(sizeof(RAWINPUTDEVICELIST) * uiNumDevices);
        if( !pList ) {
            SquirtSqflPtszV(sqfl, TEXT("DIRaw_GetKeyboardType: malloc failed."));
            goto _DIRAWGKT_EXIT;
        }
        
        if (GetRawInputDeviceList(pList, &uiNumDevices, sizeof(RAWINPUTDEVICELIST)) == (UINT)-1) {
            SquirtSqflPtszV(sqfl, TEXT("DIRaw_GetKeyboardType:  failed to get device list."));
            goto _DIRAWGKT_EXIT;
        } else {
            UINT  i;
            UINT  uiLen;
            UINT  cbSize;
            RID_DEVICE_INFO info;

            info.cbSize = sizeof(RID_DEVICE_INFO);

            for (i = 0; i<uiNumDevices; ++i) 
            {
                if (pList[i].dwType == RIM_TYPEKEYBOARD) 
                {
                    uiLen = 0;

                    // Get device name
                    if (GetRawInputDeviceInfo(pList[i].hDevice, RIDI_DEVICENAME, NULL, &uiLen)) {
                        continue;
                    }

                    // Get device type info.
                    cbSize = sizeof(RID_DEVICE_INFO);
                    if (GetRawInputDeviceInfo(pList[i].hDevice, RIDI_DEVICEINFO, &info, &cbSize) == (UINT)-1) {
                        continue;
                    }

                    if( nTypeFlag == 0 || nTypeFlag == 1)   //keyboard type or subtype
                    {
                        dwType = info.keyboard.dwType;
                        if( dwType == 4 || dwType == 7 || dwType == 8 ) {
                            nResult = (nTypeFlag==0) ? info.keyboard.dwType : info.keyboard.dwSubType;
                            break;
                        }
                    } else 
                    {
                        RPF("DIRaw_GetKeyboardType: wrong argument, %d is not supported.", nTypeFlag);
                    }
                }
            }
        }
    }

_DIRAWGKT_EXIT:
    if( pList ) {
        free(pList);
    }

    if( !nResult ) {
        nResult = GetKeyboardType(nTypeFlag);
    }

    SquirtSqflPtszV(sqfl, TEXT("DIRaw_GetKeyboardType: %s: %d"),
                          nTypeFlag==0 ? TEXT("type"):TEXT("sybtype"), nResult);
    
    return nResult;
}

#endif