/*****************************************************************************
 *
 *  DIGenM.c
 *
 *  Copyright (c) 1996 Microsoft Corporation.  All Rights Reserved.
 *
 *  Abstract:
 *
 *      Practice generic IDirectInputDevice callback for mouse.
 *
 *  Contents:
 *
 *      CMouse_New
 *
 *****************************************************************************/

#include "dinputpr.h"

/*****************************************************************************
 *
 *      The sqiffle for this file.
 *
 *****************************************************************************/

#define sqfl sqflMouse

/*****************************************************************************
 *
 *      Declare the interfaces we will be providing.
 *
 *****************************************************************************/

Primary_Interface(CMouse, IDirectInputDeviceCallback);

Interface_Template_Begin(CMouse)
    Primary_Interface_Template(CMouse, IDirectInputDeviceCallback)
Interface_Template_End(CMouse)

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @global DIOBJECTDATAFORMAT | c_rgodfMouse[] |
 *
 *          Device object data formats for the generic mouse device.
 *          The axes come first, then the buttons.
 *
 *****************************************************************************/

#pragma BEGIN_CONST_DATA

#define MAKEODF(guid, f, type, inst, aspect)                \
    { &GUID_##guid,                                         \
      FIELD_OFFSET(DIMOUSESTATE_INT, f),                        \
      DIDFT_##type | DIDFT_MAKEINSTANCE(inst),              \
      DIDOI_ASPECT##aspect,                                 \
    }                                                       \

/*
 * Warning!  If you change this table, you must adjust the IDS_MOUSEOBJECT
 * table in dinput.rc to match!
 */

DIOBJECTDATAFORMAT c_rgodfMouse[] = {
    MAKEODF( XAxis,            lX,   RELAXIS, 0, POSITION),
    MAKEODF( YAxis,            lY,   RELAXIS, 1, POSITION),
    MAKEODF( ZAxis,            lZ,   RELAXIS, 2, POSITION),
    MAKEODF(Button, rgbButtons[0], PSHBUTTON, 3,  UNKNOWN),
    MAKEODF(Button, rgbButtons[1], PSHBUTTON, 4,  UNKNOWN),
    MAKEODF(Button, rgbButtons[2], PSHBUTTON, 5,  UNKNOWN),
    MAKEODF(Button, rgbButtons[3], PSHBUTTON, 6,  UNKNOWN),
    MAKEODF(Button, rgbButtons[4], PSHBUTTON, 7,  UNKNOWN),
    MAKEODF(Button, rgbButtons[5], PSHBUTTON, 8,  UNKNOWN),
    MAKEODF(Button, rgbButtons[6], PSHBUTTON, 9,  UNKNOWN),
    MAKEODF(Button, rgbButtons[7], PSHBUTTON,10,  UNKNOWN),
};

#define c_podfMouseAxes     (&c_rgodfMouse[0])
#define c_podfMouseButtons  (&c_rgodfMouse[3])

#pragma END_CONST_DATA

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @struct CMouse |
 *
 *          The <i IDirectInputDeviceCallback> object for the generic mouse.
 *
 *  @field  IDirectInputDeviceCalllback | didc |
 *
 *          The object (containing vtbl).
 *
 *  @field  LPDIMOUSESTATE_INT | pdmsPhys |
 *
 *          Pointer to physical mouse status information kept down in the
 *          VxD.
 *
 *  @field  POINT | ptPrev |
 *
 *          Location of the mouse at the time we stole it exclusively.
 *
 *  @field  HWND | hwndCaptured |
 *
 *          The window that captured the mouse.
 *
 *  @field  VXDINSTANCE * | pvi |
 *
 *          The DirectInput instance handle.  Even though we manipulate
 *          the flags field, we do not need to mark it volatile because
 *          we modify the flags only when unacquired, whereas the device
 *          driver modifies the flags only when acquired.
 *
 *  @field  UINT | dwAxes |
 *
 *          Number of axes on the mouse.
 *
 *  @field  UINT | dwButtons |
 *
 *          Number of buttons on the mouse.
 *
 *  @field  DWORD | flEmulation |
 *
 *          The emulation flags forced by the application.  If any of
 *          these flags is set (actually, at most one will be set), then
 *          we are an alias device.
 *
 *  @field  DIDATAFORMAT | df |
 *
 *          The dynamically-generated data format based on the
 *          mouse type.
 *
 *  @field  DIOBJECTDATAFORMAT | rgodf[] |
 *
 *          Object data format table generated as part of the
 *          <e CMouse.df>.
 *
 *  @comm
 *
 *          It is the caller's responsibility to serialize access as
 *          necessary.
 *
 *****************************************************************************/

typedef struct CMouse {

    /* Supported interfaces */
    IDirectInputDeviceCallback dcb;

    LPDIMOUSESTATE_INT pdmsPhys;            /* Physical mouse state */

    POINT ptPrev;
    HWND hwndCapture;

    VXDINSTANCE *pvi;

    UINT dwAxes;
    UINT dwButtons;
    DWORD flEmulation;

    DIDATAFORMAT df;
    DIOBJECTDATAFORMAT rgodf[cA(c_rgodfMouse)];

} CMouse, DM, *PDM;

#define ThisClass CMouse
#define ThisInterface IDirectInputDeviceCallback
#define riidExpected &IID_IDirectInputDeviceCallback

/*****************************************************************************
 *
 *      CMouse::QueryInterface      (from IUnknown)
 *      CMouse::AddRef              (from IUnknown)
 *      CMouse::Release             (from IUnknown)
 *
 *****************************************************************************/

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CMouse | QueryInterface |
 *
 *          Gives a client access to other interfaces on an object.
 *
 *  @cwrap  LPDIRECTINPUT | lpDirectInput
 *
 *  @parm   IN REFIID | riid |
 *
 *          The requested interface's IID.
 *
 *  @parm   OUT LPVOID * | ppvObj |
 *
 *          Receives a pointer to the obtained interface.
 *
 *  @returns
 *
 *          Returns a COM error code.
 *
 *  @xref   OLE documentation for <mf IUnknown::QueryInterface>.
 *
 *****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CMouse | AddRef |
 *
 *          Increments the reference count for the interface.
 *
 *  @cwrap  LPDIRECTINPUT | lpDirectInput
 *
 *  @returns
 *
 *          Returns the object reference count.
 *
 *  @xref   OLE documentation for <mf IUnknown::AddRef>.
 *
 *****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CMouse | Release |
 *
 *          Decrements the reference count for the interface.
 *          If the reference count on the object falls to zero,
 *          the object is freed from memory.
 *
 *  @cwrap  LPDIRECTINPUT | lpDirectInput
 *
 *  @returns
 *
 *      Returns the object reference count.
 *
 *  @xref   OLE documentation for <mf IUnknown::Release>.
 *
 *****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CMouse | QIHelper |
 *
 *      We don't have any dynamic interfaces and simply forward
 *      to <f Common_QIHelper>.
 *
 *  @parm   IN REFIID | riid |
 *
 *      The requested interface's IID.
 *
 *  @parm   OUT LPVOID * | ppvObj |
 *
 *      Receives a pointer to the obtained interface.
 *
 *****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CMouse | AppFinalize |
 *
 *          We don't have any weak pointers, so we can just
 *          forward to <f Common_Finalize>.
 *
 *  @parm   PV | pvObj |
 *
 *          Object being released from the application's perspective.
 *
 *****************************************************************************/

#ifdef DEBUG

Default_QueryInterface(CMouse)
Default_AddRef(CMouse)
Default_Release(CMouse)

#else

#define CMouse_QueryInterface   Common_QueryInterface
#define CMouse_AddRef           Common_AddRef
#define CMouse_Release          Common_Release

#endif

#define CMouse_QIHelper         Common_QIHelper
#define CMouse_AppFinalize      Common_AppFinalize

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @func   void | CMouse_Finalize |
 *
 *          Releases the resources of the device.
 *
 *  @parm   PV | pvObj |
 *
 *          Object being released.  Note that it may not have been
 *          completely initialized, so everything should be done
 *          carefully.
 *
 *****************************************************************************/

void INTERNAL
CMouse_Finalize(PV pvObj)
{
    PDM this = pvObj;

    if (this->pvi) {
        HRESULT hres;
        hres = Hel_DestroyInstance(this->pvi);
        AssertF(SUCCEEDED(hres));
    }
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method void | CMouse | GetPhysicalPosition |
 *
 *          Read the physical mouse position into <p pmstOut>.
 *
 *          Note that it doesn't matter if this is not atomic.
 *          If a mouse motion occurs while we are reading it,
 *          we will get a mix of old and new data.  No big deal.
 *
 *  @parm   PDM | this |
 *
 *          The object in question.
 *
 *  @parm   LPDIMOUSESTATE_INT | pdmsOut |
 *
 *          Where to put the mouse position.
 *  @returns
 *          None.
 *
 *****************************************************************************/

void INLINE
CMouse_GetPhysicalPosition(PDM this, LPDIMOUSESTATE_INT pdmsOut)
{
    AssertF(this->pdmsPhys);
    *pdmsOut = *this->pdmsPhys;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CMouse | Acquire |
 *
 *          Tell the device driver to begin data acquisition.
 *          Give the device driver the current mouse button states
 *          in case it needs them.
 *
 *          It is the caller's responsibility to have set the
 *          data format before obtaining acquisition.
 *
 *  @returns
 *
 *          Returns a COM error code.  The following error codes are
 *          intended to be illustrative and not necessarily comprehensive.
 *
 *          <c DI_OK> = <c S_OK>: The operation completed successfully.
 *
 *          <c S_FALSE>: The operation was begun and should be completed
 *          by the caller by communicating with the <t VXDINSTANCE>.
 *
 *****************************************************************************/

STDMETHODIMP
CMouse_Acquire(PDICB pdcb)
{
    HRESULT hres;
    PDM this;
    VXDDWORDDATA vdd;
    DWORD mef;
    EnterProcI(IDirectInputDeviceCallback::Mouse::Acquire,
               (_ "p", pdcb));

    /*
     *  This is an internal interface, so we can skimp on validation.
     */
    this = _thisPvNm(pdcb, dcb);

    AssertF(this->pvi);

    vdd.pvi = this->pvi;
    vdd.dw = 0;

    /*
     *  Collect information about which buttons are down.
     */
    mef = 0;
    if (GetAsyncKeyState(VK_LBUTTON) < 0) {
        mef |= MOUSEEVENTF_LEFTUP;
        vdd.dw |= 0x80;
    }
    if (GetAsyncKeyState(VK_RBUTTON) < 0) {
        mef |= MOUSEEVENTF_RIGHTUP;
        vdd.dw |= 0x8000;
    }
    if (GetAsyncKeyState(VK_MBUTTON) < 0) {
        mef |= MOUSEEVENTF_MIDDLEUP;
        vdd.dw |= 0x800000;
    }

    /*
     *  HACKHACK - This, strictly speaking, belongs in dihel.c,
     *  but we need to maintain some state, and it's easier to
     *  put the state in our own object.
     */

    /*
     *  A bit of work needs to be done at ring 3 now.
     */
    if (this->pvi->fl & VIFL_CAPTURED) {
        RECT rc;

        /*
         *  Hide the mouse cursor (for compatibility with NT emulation)
         */
        GetCursorPos(&this->ptPrev);
        GetWindowRect(this->hwndCapture, &rc);
        SetCursorPos((rc.left + rc.right) >> 1,
                     (rc.top + rc.bottom) >> 1);
        ShowCursor(0);

	    if (!(this->pvi->fl & VIFL_EMULATED)) {
			/*
			 *  Force all mouse buttons up from USER's point of view
			 *  to avoid "stuck mouse button" problems.  However, don't
			 *  force a button up unless it is actually down.
			 */
			if (mef) {
				mouse_event(mef, 0, 0, 0, 0);
			}
		}
    }

    if (!(this->pvi->fl & VIFL_EMULATED)) {
        hres = IoctlHw(IOCTL_MOUSE_INITBUTTONS, &vdd.dw, cbX(vdd.dw), 0, 0);
    } else {
      #ifdef USE_WM_INPUT
        if( g_fRawInput ) {
            hres = CDIRaw_Mouse_InitButtons();
        }
      #endif
        hres = CEm_Mouse_InitButtons(&vdd);
    }

    AssertF(SUCCEEDED(hres));

    hres = S_FALSE;                 /* Please finish for me */

    ExitOleProcR();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CMouse | Unacquire |
 *
 *          Tell the device driver to stop data acquisition.
 *
 *          It is the caller's responsibility to call this only
 *          when the device has been acquired.
 *
 *  @returns
 *
 *          Returns a COM error code.  The following error codes are
 *          intended to be illustrative and not necessarily comprehensive.
 *
 *          <c DI_OK> = <c S_OK>: The operation completed successfully.
 *
 *          <c S_FALSE>: The operation was begun and should be completed
 *          by the caller by communicating with the <t VXDINSTANCE>.
 *
 *****************************************************************************/

STDMETHODIMP
CMouse_Unacquire(PDICB pdcb)
{
    HRESULT hres;
    PDM this;
  #ifdef WANT_TO_FIX_MANBUG43879  
    DWORD mef;
  #endif
    EnterProcI(IDirectInputDeviceCallback::Mouse::Unacquire,
               (_ "p", pdcb));

    /*
     *  This is an internal interface, so we can skimp on validation.
     */
    this = _thisPvNm(pdcb, dcb);

    AssertF(this->pvi);

  #ifdef WANT_TO_FIX_MANBUG43879
    /*
     *  Collect information about which buttons are down.
     */
    mef = 0;
    if (GetAsyncKeyState(VK_LBUTTON) < 0) {
        mef |= MOUSEEVENTF_LEFTUP;
    }
    if (GetAsyncKeyState(VK_RBUTTON) < 0) {
        mef |= MOUSEEVENTF_RIGHTUP;
    }
    if (GetAsyncKeyState(VK_MBUTTON) < 0) {
        mef |= MOUSEEVENTF_MIDDLEUP;
    }

    if (this->pvi->fl & VIFL_FOREGROUND) {
        /*
         *  Force all mouse buttons up from USER's point of view
         *  to avoid "stuck mouse button" problems.  However, don't
         *  force a button up unless it is actually down.
         *  This could happen if DInput loses UP events due to slow
         *  low-level hook. See bug: 43879.
         */
        if (mef) {
            mouse_event(mef, 0, 0, 0, 0);
        }
    }
  #endif

    /*
     *  HACKHACK - This is the corresponding half of the HACKHACK
     *  in CMouse_Acquire.
     */

    /*
     *  A bit of work needs to be done at ring 3 now.
     */
    if (this->pvi->fl & VIFL_CAPTURED) {
        RECT rcDesk;
        RECT rcApp;

        /*
         *  Reposition and restore the mouse cursor
         *  (for compatibility with NT emulation)
         *
         *  Do not reposition the mouse cursor if we lost to a
         *  window that covers the screen.  Otherwise, our
         *  repositioning will nuke the screen saver.
         */
        GetWindowRect(GetDesktopWindow(), &rcDesk);
        GetWindowRect(GetForegroundWindow(), &rcApp);
        SubtractRect(&rcDesk, &rcDesk, &rcApp);
        if (!IsRectEmpty(&rcDesk)) {
            SetCursorPos(this->ptPrev.x, this->ptPrev.y);
        }
        ShowCursor(1);
    }

    hres = S_FALSE;
    ExitOleProcR();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CMouse | GetInstance |
 *
 *          Obtains the DirectInput instance handle.
 *
 *  @parm   OUT PPV | ppvi |
 *
 *          Receives the instance handle.
 *
 *****************************************************************************/

STDMETHODIMP
CMouse_GetInstance(PDICB pdcb, PPV ppvi)
{
    HRESULT hres;
    PDM this;
    EnterProcI(IDirectInputDeviceCallback::Mouse::GetInstance, (_ "p", pdcb));

    /*
     *  This is an internal interface, so we can skimp on validation.
     */
    this = _thisPvNm(pdcb, dcb);

    AssertF(this->pvi);
    *ppvi = (PV)this->pvi;
    hres = S_OK;

    ExitOleProcPpvR(ppvi);
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CMouse | GetDataFormat |
 *
 *          Obtains the device's preferred data format.
 *
 *  @parm   OUT LPDIDEVICEFORMAT * | ppdf |
 *
 *          <t LPDIDEVICEFORMAT> to receive pointer to device format.
 *
 *  @returns
 *
 *          Returns a COM error code.  The following error codes are
 *          intended to be illustrative and not necessarily comprehensive.
 *
 *          <c DI_OK> = <c S_OK>: The operation completed successfully.
 *
 *          <c DIERR_INVALIDPARAM> = <c E_INVALIDARG>:  The
 *          <p lpmdr> parameter is not a valid pointer.
 *
 *****************************************************************************/

STDMETHODIMP
CMouse_GetDataFormat(PDICB pdcb, LPDIDATAFORMAT *ppdf)
{
    HRESULT hres;
    PDM this;
    EnterProcI(IDirectInputDeviceCallback::Mouse::GetDataFormat,
               (_ "p", pdcb));

    /*
     *  This is an internal interface, so we can skimp on validation.
     */
    this = _thisPvNm(pdcb, dcb);

    *ppdf = &this->df;
    hres = S_OK;

    ExitOleProcPpvR(ppdf);
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CMouse | GetDeviceInfo |
 *
 *          Obtain general information about the device.
 *
 *  @parm   OUT LPDIDEVICEINSTANCEW | pdiW |
 *
 *          <t DEVICEINSTANCE> to be filled in.  The
 *          <e DEVICEINSTANCE.dwSize> and <e DEVICEINSTANCE.guidInstance>
 *          have already been filled in.
 *
 *          Secret convenience:  <e DEVICEINSTANCE.guidProduct> is equal
 *          to <e DEVICEINSTANCE.guidInstance>.
 *
 *****************************************************************************/

STDMETHODIMP
CMouse_GetDeviceInfo(PDICB pdcb, LPDIDEVICEINSTANCEW pdiW)
{
    HRESULT hres;
    PDM this;
    EnterProcI(IDirectInputDeviceCallback::Mouse::GetDeviceInfo,
               (_ "pp", pdcb, pdiW));

    /*
     *  This is an internal interface, so we can skimp on validation.
     */
    this = _thisPvNm(pdcb, dcb);

    AssertF(IsValidSizeDIDEVICEINSTANCEW(pdiW->dwSize));

    AssertF(IsEqualGUID(&GUID_SysMouse   , &pdiW->guidInstance) ||
            IsEqualGUID(&GUID_SysMouseEm , &pdiW->guidInstance) ||
            IsEqualGUID(&GUID_SysMouseEm2, &pdiW->guidInstance));
    pdiW->guidProduct = GUID_SysMouse;

    pdiW->dwDevType = MAKE_DIDEVICE_TYPE(DI8DEVTYPE_MOUSE,
                                         DI8DEVTYPEMOUSE_UNKNOWN);

    LoadStringW(g_hinst, IDS_STDMOUSE, pdiW->tszProductName, cA(pdiW->tszProductName));
    LoadStringW(g_hinst, IDS_STDMOUSE, pdiW->tszInstanceName, cA(pdiW->tszInstanceName));

    hres = S_OK;

    ExitOleProcR();
    return hres;

}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method void | CMouse | GetProperty |
 *
 *          Get a mouse device property.
 *
 *  @parm   IN LPCDIPROPINFO | ppropi |
 *
 *          Information describing the property being retrieved.
 *
 *  @parm   LPDIPROPHEADER | pdiph |
 *
 *          Structure to receive property value.
 *
 *  @returns
 *
 *          <c E_NOTIMPL> nothing happened.  The caller will do
 *          the default thing in response to <c E_NOTIMPL>.
 *
 *****************************************************************************/

STDMETHODIMP
CMouse_GetProperty(PDICB pdcb, LPCDIPROPINFO ppropi, LPDIPROPHEADER pdiph)
{
    HRESULT hres;
    PDM this;
    EnterProcI(IDirectInputDeviceCallback::Mouse::GetProperty,
               (_ "pxxp", pdcb, ppropi->pguid, ppropi->iobj, pdiph));

    /*
     *  This is an internal interface, so we can skimp on validation.
     */
    this = _thisPvNm(pdcb, dcb);

    /*
     *  Granularity is only supported for wheels.
     */
    if (ppropi->pguid == DIPROP_GRANULARITY &&
        ppropi->dwDevType == (DIDFT_RELAXIS | DIDFT_MAKEINSTANCE(2)) )
    {
        LPDIPROPDWORD pdipdw = (PV)pdiph;
        pdipdw->dwData = g_lWheelGranularity? (DWORD)g_lWheelGranularity : 120;
        hres = S_OK;
    }
    else
    {
        hres = E_NOTIMPL;
    }

    ExitOleProcR();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method void | CMouse | GetCapabilities |
 *
 *          Get mouse device capabilities.
 *
 *  @parm   LPDIDEVCAPS | pdc |
 *
 *          Device capabilities structure to receive result.
 *
 *  @returns
 *          <c S_OK> on success.
 *
 *****************************************************************************/

STDMETHODIMP
CMouse_GetCapabilities(PDICB pdcb, LPDIDEVCAPS pdc)
{
    HRESULT hres;
    PDM this;
    EnterProcI(IDirectInputDeviceCallback::Mouse::GetCapabilities,
               (_ "pp", pdcb, pdc));

    /*
     *  This is an internal interface, so we can skimp on validation.
     */
    this = _thisPvNm(pdcb, dcb);

    pdc->dwDevType = MAKE_DIDEVICE_TYPE(DI8DEVTYPE_MOUSE,
                                        DI8DEVTYPEMOUSE_UNKNOWN);
    pdc->dwFlags = DIDC_ATTACHED;
    if (this->flEmulation) {
        pdc->dwFlags |= DIDC_ALIAS;
    }

    pdc->dwAxes = this->dwAxes;
    pdc->dwButtons = this->dwButtons;
    AssertF(pdc->dwPOVs == 0);

    hres = S_OK;
    ExitOleProcR();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CMouse | GetDeviceState |
 *
 *          Obtains the state of the mouse device.
 *
 *          It is the caller's responsibility to have validated all the
 *          parameters and ensure that the device has been acquired.
 *
 *  @parm   OUT LPVOID | lpvData |
 *
 *          Mouse data in the preferred data format.
 *
 *  @returns
 *
 *          Returns a COM error code.  The following error codes are
 *          intended to be illustrative and not necessarily comprehensive.
 *
 *          <c DI_OK> = <c S_OK>: The operation completed successfully.
 *
 *          <c DIERR_INVALIDPARAM> = <c E_INVALIDARG>:  The
 *          <p lpmdr> parameter is not a valid pointer.
 *
 *****************************************************************************/

STDMETHODIMP
CMouse_GetDeviceState(PDICB pdcb, LPVOID pvData)
{
    HRESULT hres;
    PDM this;
    EnterProcI(IDirectInputDeviceCallback::Mouse::GetDeviceState,
               (_ "pp", pdcb, pvData));

    /*
     *  This is an internal interface, so we can skimp on validation.
     */
    this = _thisPvNm(pdcb, dcb);

    AssertF(this->pvi);
    AssertF(this->pdmsPhys);

    if (this->pvi->fl & VIFL_ACQUIRED) {
        CMouse_GetPhysicalPosition(this, pvData);
        hres = S_OK;
    } else {
        hres = DIERR_INPUTLOST;
    }

    ExitOleProcR();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CMouse | GetObjectInfo |
 *
 *          Obtain the friendly name of an object, passwed by index
 *          into the preferred data format.
 *
 *  @parm   IN LPCDIPROPINFO | ppropi |
 *
 *          Information describing the object being accessed.
 *
 *  @parm   IN OUT LPDIDEVICEOBJECTINSTANCEW | pdidioiW |
 *
 *          Structure to receive information.  The
 *          <e DIDEVICEOBJECTINSTANCE.guidType>,
 *          <e DIDEVICEOBJECTINSTANCE.dwOfs>,
 *          and
 *          <e DIDEVICEOBJECTINSTANCE.dwType>
 *          fields have already been filled in.
 *
 *  @returns
 *
 *          Returns a COM error code.
 *
 *****************************************************************************/

STDMETHODIMP
CMouse_GetObjectInfo(PDICB pdcb, LPCDIPROPINFO ppropi,
                                 LPDIDEVICEOBJECTINSTANCEW pdidoiW)
{
    HRESULT hres;
    PDM this;
    EnterProcI(IDirectInputDeviceCallback::Mouse::GetObjectInfo,
               (_ "pxp", pdcb, ppropi->iobj, pdidoiW));

    /*
     *  This is an internal interface, so we can skimp on validation.
     */
    this = _thisPvNm(pdcb, dcb);

    AssertF(IsValidSizeDIDEVICEOBJECTINSTANCEW(pdidoiW->dwSize));

    if (ppropi->iobj < this->df.dwNumObjs) {
        AssertF(this->rgodf == this->df.rgodf);
        AssertF(ppropi->dwDevType == this->rgodf[ppropi->iobj].dwType);

        AssertF(DIDFT_GETTYPE(ppropi->dwDevType) == DIDFT_RELAXIS ||
                DIDFT_GETTYPE(ppropi->dwDevType) == DIDFT_PSHBUTTON);


        LoadStringW(g_hinst, IDS_MOUSEOBJECT +
                             DIDFT_GETINSTANCE(ppropi->dwDevType),
                             pdidoiW->tszName, cA(pdidoiW->tszName));

        /*
         *  We do not support force feedback on mice, so
         *  there are no FF flags to report.
         */
        hres = S_OK;
    } else {
        hres = E_INVALIDARG;
    }

    ExitOleProcR();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CMouse | SetCooperativeLevel |
 *
 *          Notify the device of the cooperative level.
 *
 *  @parm   IN HWND | hwnd |
 *
 *          The window handle.
 *
 *  @parm   IN DWORD | dwFlags |
 *
 *          The cooperativity level.
 *
 *  @returns
 *
 *          Returns a COM error code.
 *
 *****************************************************************************/

STDMETHODIMP
CMouse_SetCooperativeLevel(PDICB pdcb, HWND hwnd, DWORD dwFlags)
{
    HRESULT hres;
    PDM this;
    EnterProcI(IDirectInputDeviceCallback::Mouse::SetCooperativityLevel,
               (_ "pxx", pdcb, hwnd, dwFlags));

    /*
     *  This is an internal interface, so we can skimp on validation.
     */
    this = _thisPvNm(pdcb, dcb);

    AssertF(this->pvi);

#ifdef USE_SLOW_LL_HOOKS
    AssertF(DIGETEMFL(this->pvi->fl) == 0 ||
            DIGETEMFL(this->pvi->fl) == DIEMFL_MOUSE ||
            DIGETEMFL(this->pvi->fl) == DIEMFL_MOUSE2);
#else
    AssertF(DIGETEMFL(this->pvi->fl) == 0 ||
            DIGETEMFL(this->pvi->fl) == DIEMFL_MOUSE2);
#endif
    /*
     *  Even though we can do it, we don't let the app
     *  get background exclusive access.  As with the keyboard,
     *  there is nothing that technically prevents us from
     *  supporting it; we just don't feel like it because it's
     *  too dangerous.
     */

    /*
     *  VxD and LL (emulation 1) behave the same, so we check
     *  if it's "not emulation 2".
     */

    if (!(this->pvi->fl & DIMAKEEMFL(DIEMFL_MOUSE2))) {

        if (dwFlags & DISCL_EXCLUSIVE) {
            if (dwFlags & DISCL_FOREGROUND) {
              #ifdef WANT_TO_FIX_MANBUG43879  
                this->pvi->fl |= VIFL_FOREGROUND;
              #endif
                this->pvi->fl |= VIFL_CAPTURED;
                hres = S_OK;
            } else {                /* Disallow exclusive background */
                SquirtSqflPtszV(sqfl | sqflError,
                                TEXT("Exclusive background mouse access disallowed"));
                hres = E_NOTIMPL;
            }
        } else {
          #ifdef WANT_TO_FIX_MANBUG43879
            if (dwFlags & DISCL_FOREGROUND) {
                this->pvi->fl |= VIFL_FOREGROUND;
            }
          #endif

            this->pvi->fl &= ~VIFL_CAPTURED;
            hres = S_OK;
        }

    } else {
        /*
         *  Emulation 2 supports only exclusive foreground.
         */
        if ((dwFlags & (DISCL_EXCLUSIVE | DISCL_FOREGROUND)) ==
                       (DISCL_EXCLUSIVE | DISCL_FOREGROUND)) {
          #ifdef WANT_TO_FIX_MANBUG43879  
            this->pvi->fl |= VIFL_FOREGROUND;
          #endif
            this->pvi->fl |= VIFL_CAPTURED;
            hres = S_OK;
        } else {
            SquirtSqflPtszV(sqfl | sqflError,
                            TEXT("Mouse access must be exclusive foreground in Emulation 2."));
            hres = E_NOTIMPL;
        }
    }

    if (SUCCEEDED(hres)) {
        this->hwndCapture = hwnd;
    }

    ExitOleProcR();
    return hres;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CMouse | RunControlPanel |
 *
 *          Run the mouse control panel.
 *
 *  @parm   IN HWND | hwndOwner |
 *
 *          The owner window.
 *
 *  @parm   DWORD | dwFlags |
 *
 *          Flags.
 *
 *****************************************************************************/

#pragma BEGIN_CONST_DATA

TCHAR c_tszMouse[] = TEXT("mouse");

#pragma END_CONST_DATA

STDMETHODIMP
CMouse_RunControlPanel(PDICB pdcb, HWND hwnd, DWORD dwFlags)
{
    HRESULT hres;
    EnterProcI(IDirectInputDeviceCallback::Mouse::RunControlPanel,
               (_ "pxx", pdcb, hwnd, dwFlags));

    hres = hresRunControlPanel(c_tszMouse);

    ExitOleProcR();
    return hres;
}


/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method HRESULT | CMouse | BuildDefaultActionMap |
 *
 *          Validate the passed action map, blanking out invalid ones.
 *
 *  @parm   LPDIACTIONFORMATW | pActionFormat |
 *
 *          Actions to map.
 *
 *  @parm   DWORD | dwFlags |
 *
 *          Flags used to indicate mapping preferences.
 *
 *  @parm   REFGUID | guidInst |
 *
 *          Device instance GUID.
 *
 *  @returns
 *
 *          <c E_NOTIMPL>
 *
 *****************************************************************************/

STDMETHODIMP
CMouse_BuildDefaultActionMap
(
    PDICB               pdcb,
    LPDIACTIONFORMATW   paf,
    DWORD               dwFlags,
    REFGUID             guidInst
)
{
    HRESULT hres;
    PDM this;

    /*
     *  This is an internal interface, so we can skimp on validation.
     */
    EnterProcI(IDirectInputDeviceCallback::Mouse::BuildDefaultActionMap,
        (_ "ppxG", pdcb, paf, dwFlags, guidInst));

    this = _thisPvNm(pdcb, dcb);

    hres = CMap_BuildDefaultSysActionMap ( paf, dwFlags, DIPHYSICAL_MOUSE,
        guidInst, &this->df, 3 /* Generic mice buttons start at instnace 3 */ );

    ExitOleProcR();
    return hres;
}


/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method UINT | CMouse | NumAxes |
 *
 *          Determine the number of mouse axes.
 *
 *          On Windows NT, we can use the new <c SM_MOUSEWHEELPRESENT>
 *          system metric.  On Windows 95, we have to hunt for the
 *          Magellan window (using the mechanism documented in the
 *          Magellan SDK).
 *
 *****************************************************************************/

#pragma BEGIN_CONST_DATA

TCHAR c_tszMouseZClass[] = TEXT("MouseZ");
TCHAR c_tszMouseZTitle[] = TEXT("Magellan MSWHEEL");
TCHAR c_tszMouseZActive[] = TEXT("MSH_WHEELSUPPORT_MSG");

#pragma END_CONST_DATA

#ifdef WINNT
#define CMouse_IsMagellanWheel() FALSE
#else
BOOL INLINE
CMouse_IsMagellanWheel(void)
{
    HWND hwnd = FindWindow(c_tszMouseZClass, c_tszMouseZTitle);
    return hwnd && SendMessage(hwnd, RegisterWindowMessage(c_tszMouseZActive), 0, 0);
}
#endif

#ifndef SM_MOUSEWHEELPRESENT
#define SM_MOUSEWHEELPRESENT            75
#endif

UINT INLINE
CMouse_NumAxes(void)
{
    UINT dwAxes;

    if (GetSystemMetrics(SM_MOUSEWHEELPRESENT) || CMouse_IsMagellanWheel()) {
        dwAxes = 3;
    } else {
        dwAxes = 2;
    }

    if (dwAxes == 2) {
        //Should avoid rebuilding too frequently.
        DIHid_BuildHidList(FALSE);

        DllEnterCrit();

        if (g_phdl) {
            int ihdi;
            for (ihdi = 0; ihdi < g_phdl->chdi; ihdi++) {
                if (dwAxes < g_phdl->rghdi[ihdi].osd.uiAxes) {
                    dwAxes = g_phdl->rghdi[ihdi].osd.uiAxes;
                }
            }
        }
        DllLeaveCrit();
    }

    return dwAxes;
}

UINT INLINE
CMouse_NumButtons(DWORD dwAxes)
{
    UINT dwButtons;

    dwButtons = GetSystemMetrics(SM_CMOUSEBUTTONS);

#ifndef WINNT
  #ifdef HID_SUPPORT
    {
        /*
         *  ISSUE-2001/03/29-timgill Should try to avoid rebuilding Hid List too frequently.
         */
        DIHid_BuildHidList(FALSE);

        DllEnterCrit();

        if (g_phdl) {
            int ihdi;
            for (ihdi = 0; ihdi < g_phdl->chdi; ihdi++) {
                if (dwButtons < g_phdl->rghdi[ihdi].osd.uiButtons) {
                    dwButtons = g_phdl->rghdi[ihdi].osd.uiButtons;
                }
            }
        }
        DllLeaveCrit();
    }
  #endif
#endif

    if( dwButtons >= 8 ) {
        dwButtons = 8;
    }
    else if (dwAxes == 3 && dwButtons < 3) {
        /*
         *  HACK FOR MAGELLAN!
         *
         *  They return 2 from GetSystemMetrics(SM_CMOUSEBUTTONS).
         *  So if we see a Z-axis, then assume that
         *  there is also a third button.
         */
        dwButtons = 3;
    }

    return dwButtons;
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method void | CMouse | AddObjects |
 *
 *          Add a number of objects to the device format.
 *
 *  @parm   LPCDIOBJECTDATAFORMAT | rgodf |
 *
 *          Array of objects to be added.
 *
 *  @parm   UINT | cObj |
 *
 *          Number of objects to add.
 *
 *****************************************************************************/

void INTERNAL
CMouse_AddObjects(PDM this, LPCDIOBJECTDATAFORMAT rgodf, UINT cObj)
{
    UINT iodf;
    EnterProc(CMouse_AddObjects, (_ "pxx", this, rgodf, cObj));

    for (iodf = 0; iodf < cObj; iodf++) {
        this->rgodf[this->df.dwNumObjs++] = rgodf[iodf];
    }
    AssertF(this->df.dwNumObjs <= cA(this->rgodf));
}

/*****************************************************************************
 *
 *  @doc    INTERNAL
 *
 *  @method void | CMouse | Init |
 *
 *          Initialize the object by establishing the data format
 *          based on the mouse type.
 *
 *          Code for detecting the IntelliMouse (formerly known as
 *          Magellan) pointing device is swiped from zmouse.h.
 *
 *  @parm   REFGUID | rguid |
 *
 *          The instance GUID we are being asked to create.
 *
 *****************************************************************************/

HRESULT INTERNAL
CMouse_Init(PDM this, REFGUID rguid)
{
    HRESULT hres;
    VXDDEVICEFORMAT devf;
    EnterProc(CMouse_Init, (_ "pG", this, rguid));

    this->df.dwSize = cbX(DIDATAFORMAT);
    this->df.dwObjSize = cbX(DIOBJECTDATAFORMAT);
    this->df.dwDataSize = cbX(DIMOUSESTATE_INT);
    this->df.rgodf = this->rgodf;
    AssertF(this->df.dwFlags == 0);
    AssertF(this->df.dwNumObjs == 0);

    /*
     *  Need to know early if we have a Z-axis, so we can disable
     *  the Z-wheel if it doesn't exist.
     *
     *  Note that this disabling needs to be done only on Win95.
     *  Win98 and NT4 have native Z-axis support, so there is
     *  nothing bogus that needs to be hacked.
     */
    this->dwAxes = CMouse_NumAxes();
    devf.dwExtra = this->dwAxes;
    if (this->dwAxes < 3) {
        DWORD dwVer = GetVersion();
        if ((LONG)dwVer >= 0 ||
            MAKEWORD(HIBYTE(LOWORD(dwVer)), LOBYTE(dwVer)) >= 0x040A) {
            devf.dwExtra = 3;
        }
    }
    CMouse_AddObjects(this, c_podfMouseAxes, this->dwAxes);

    /*
     *  Create the object with the most optimistic data format.
     *  This is important, because DINPUT.VXD builds the
     *  data format only once, and we need to protect ourselves against
     *  the user going into Control Panel and enabling the Z-Wheel
     *  after DINPUT.VXD has already initialized.
     */

    devf.cbData = cbX(DIMOUSESTATE_INT);
    devf.cObj = cA(c_rgodfMouse);
    devf.rgodf = c_rgodfMouse;

    /*
     *  But first a word from our other sponsor:  Figure out the
     *  emulation flags based on the GUID.
     */

    AssertF(GUID_SysMouse   .Data1 == 0x6F1D2B60);
    AssertF(GUID_SysMouseEm .Data1 == 0x6F1D2B80);
    AssertF(GUID_SysMouseEm2.Data1 == 0x6F1D2B81);

    switch (rguid->Data1) {

    default:
    case 0x6F1D2B60:
        AssertF(IsEqualGUID(rguid, &GUID_SysMouse));
        AssertF(this->flEmulation == 0);
        break;

    case 0x6F1D2B80:
        AssertF(IsEqualGUID(rguid, &GUID_SysMouseEm));
        this->flEmulation = DIEMFL_MOUSE;
        break;

    case 0x6F1D2B81:
        AssertF(IsEqualGUID(rguid, &GUID_SysMouseEm2));
        this->flEmulation = DIEMFL_MOUSE2;
        break;

    }

    devf.dwEmulation = this->flEmulation;

    hres = Hel_Mouse_CreateInstance(&devf, &this->pvi);
    if (SUCCEEDED(hres)) {

        AssertF(this->pvi);
        this->pdmsPhys = this->pvi->pState;

        this->dwButtons = CMouse_NumButtons( this->dwAxes );

        CMouse_AddObjects(this, c_podfMouseButtons, this->dwButtons);

        hres = S_OK;

    } else {
        SquirtSqflPtszV(sqfl | sqflError,
                        TEXT("Mismatched version of dinput.vxd"));
        hres = E_FAIL;
    }

    ExitOleProc();
    return hres;
}

/*****************************************************************************
 *
 *      CMouse_New       (constructor)
 *
 *      Fail the create if the machine has no mouse.
 *
 *****************************************************************************/

STDMETHODIMP
CMouse_New(PUNK punkOuter, REFGUID rguid, RIID riid, PPV ppvObj)
{
    HRESULT hres;
    EnterProcI(IDirectInputDeviceCallback::Mouse::<constructor>,
               (_ "Gp", riid, ppvObj));

    AssertF(IsEqualGUID(rguid, &GUID_SysMouse) ||
            IsEqualGUID(rguid, &GUID_SysMouseEm) ||
            IsEqualGUID(rguid, &GUID_SysMouseEm2));

    if (GetSystemMetrics(SM_MOUSEPRESENT)) {
        hres = Common_NewRiid(CMouse, punkOuter, riid, ppvObj);

        if (SUCCEEDED(hres)) {
            /* Must use _thisPv in case of aggregation */
            PDM this = _thisPv(*ppvObj);

            if (SUCCEEDED(hres = CMouse_Init(this, rguid))) {
            } else {
                Invoke_Release(ppvObj);
            }

        }
    } else {
        RPF("Warning: System does not have a mouse");
        /*
         *  Since we by-passed the parameter checks and we failed to create
         *  the new interface, try to zero the pointer now.
         */
        if (!IsBadWritePtr(ppvObj, sizeof(UINT_PTR) ))
        {
            *(PUINT_PTR)ppvObj = 0;
        }
        hres = DIERR_DEVICENOTREG;
    }

    ExitOleProcPpvR(ppvObj);
    return hres;
}

/*****************************************************************************
 *
 *      The long-awaited vtbls and templates
 *
 *****************************************************************************/

#pragma BEGIN_CONST_DATA

#define CMouse_Signature        0x53554F4D      /* "MOUS" */

Primary_Interface_Begin(CMouse, IDirectInputDeviceCallback)
    CMouse_GetInstance,
    CDefDcb_GetVersions,
    CMouse_GetDataFormat,
    CMouse_GetObjectInfo,
    CMouse_GetCapabilities,
    CMouse_Acquire,
    CMouse_Unacquire,
    CMouse_GetDeviceState,
    CMouse_GetDeviceInfo,
    CMouse_GetProperty,
    CDefDcb_SetProperty,
    CDefDcb_SetEventNotification,
    CMouse_SetCooperativeLevel,
    CMouse_RunControlPanel,
    CDefDcb_CookDeviceData,
    CDefDcb_CreateEffect,
    CDefDcb_GetFFConfigKey,
    CDefDcb_SendDeviceData,
    CDefDcb_Poll,
    CDefDcb_GetUsage,
    CDefDcb_MapUsage,
    CDefDcb_SetDIData,
    CMouse_BuildDefaultActionMap,
Primary_Interface_End(CMouse, IDirectInputDeviceCallback)