/*		DirectInput Mouse device
 *
 * Copyright 1998 Marcus Meissner
 * Copyright 1998,1999 Lionel Ulmer
 * Copyright 2000-2003 TransGaming Technologies Inc.
 *
 */

#include "config.h"
#include <string.h>
#ifdef HAVE_SYS_ERRNO_H
# include <sys/errno.h>
#endif

#include "winbase.h"
#include "wingdi.h"
#include "winuser.h"
#include "winerror.h"
#include "wine/winbase16.h"
#include "dinput.h"

#include "dinput_private.h"
#include "device_private.h"
#include "wine/debug.h"

/* #define MOUSE_HACK */
#define WARP_CONDITION(This) (!(This->absolute || (This->cursor_flags & CURSOR_SHOWING)))
#define MANY_BUTTONS

WINE_DEFAULT_DEBUG_CHANNEL(dinput);

/* Wine mouse driver object instances */
#define WINE_MOUSE_X_AXIS_INSTANCE 0x0000
#define WINE_MOUSE_Y_AXIS_INSTANCE 0x0001
#define WINE_MOUSE_Z_AXIS_INSTANCE 0x0002
#define WINE_MOUSE_L_BUTTON_INSTANCE 0x0003
#define WINE_MOUSE_R_BUTTON_INSTANCE 0x0004
#define WINE_MOUSE_M_BUTTON_INSTANCE 0x0005
#define WINE_MOUSE_X1_BUTTON_INSTANCE 0x0006
#define WINE_MOUSE_X2_BUTTON_INSTANCE 0x0007

/* ------------------------------- */
/* Wine mouse internal data format */
/* ------------------------------- */

/* Constants used to access the offset array */
#define WINE_MOUSE_X_POSITION 0
#define WINE_MOUSE_Y_POSITION 1
#define WINE_MOUSE_Z_POSITION 2
#define WINE_MOUSE_L_POSITION 3
#define WINE_MOUSE_R_POSITION 4
#define WINE_MOUSE_M_POSITION 5
#define WINE_MOUSE_X1_POSITION 6
#define WINE_MOUSE_X2_POSITION 7

typedef struct {
  LONG lX;
  LONG lY;
  LONG lZ;
  BYTE rgbButtons[8];
} Wine_InternalMouseData;

#define WINE_INTERNALMOUSE_NUM_OBJS 8

static ObjectName DInputMouseName[WINE_INTERNALMOUSE_NUM_OBJS] = {
  	{WINE_MOUSE_X_POSITION, "X-Axis"},
	{WINE_MOUSE_Y_POSITION, "Y-Axis"},
	{WINE_MOUSE_Z_POSITION, "Z-Axis"},
	{WINE_MOUSE_L_POSITION, "Left-Button"},
	{WINE_MOUSE_R_POSITION, "Right-Button"},
	{WINE_MOUSE_M_POSITION, "Middle-Button"},
	{WINE_MOUSE_X1_POSITION, "X1-Button"},
	{WINE_MOUSE_X2_POSITION, "X2-Button"}};


static DIOBJECTDATAFORMAT Wine_InternalMouseObjectFormat[WINE_INTERNALMOUSE_NUM_OBJS] = {
  { &GUID_XAxis,   FIELD_OFFSET(Wine_InternalMouseData, lX),
      DIDFT_MAKEINSTANCE(WINE_MOUSE_X_AXIS_INSTANCE) | DIDFT_RELAXIS, 0 },
  { &GUID_YAxis,   FIELD_OFFSET(Wine_InternalMouseData, lY),
      DIDFT_MAKEINSTANCE(WINE_MOUSE_Y_AXIS_INSTANCE) | DIDFT_RELAXIS, 0 },
  { &GUID_ZAxis,   FIELD_OFFSET(Wine_InternalMouseData, lZ),
      DIDFT_MAKEINSTANCE(WINE_MOUSE_Z_AXIS_INSTANCE) | DIDFT_RELAXIS, 0 },
  { &GUID_Button, (FIELD_OFFSET(Wine_InternalMouseData, rgbButtons)) + 0,
      DIDFT_MAKEINSTANCE(WINE_MOUSE_L_BUTTON_INSTANCE) | DIDFT_PSHBUTTON, 0 },
  { &GUID_Button, (FIELD_OFFSET(Wine_InternalMouseData, rgbButtons)) + 1,
      DIDFT_MAKEINSTANCE(WINE_MOUSE_R_BUTTON_INSTANCE) | DIDFT_PSHBUTTON, 0 },
  { &GUID_Button, (FIELD_OFFSET(Wine_InternalMouseData, rgbButtons)) + 2,
      DIDFT_MAKEINSTANCE(WINE_MOUSE_M_BUTTON_INSTANCE) | DIDFT_PSHBUTTON, 0 },
  { &GUID_Button, (FIELD_OFFSET(Wine_InternalMouseData, rgbButtons)) + 3,
      DIDFT_MAKEINSTANCE(WINE_MOUSE_X1_BUTTON_INSTANCE) | DIDFT_PSHBUTTON, 0 },
  { &GUID_Button, (FIELD_OFFSET(Wine_InternalMouseData, rgbButtons)) + 4,
      DIDFT_MAKEINSTANCE(WINE_MOUSE_X2_BUTTON_INSTANCE) | DIDFT_PSHBUTTON, 0 }
};

static DIDATAFORMAT Wine_InternalMouseFormat = {
  0, /* dwSize - unused */
  0, /* dwObjsize - unused */
  0, /* dwFlags - unused */
  sizeof(Wine_InternalMouseData),
  WINE_INTERNALMOUSE_NUM_OBJS, /* dwNumObjs */
  Wine_InternalMouseObjectFormat
};

static ICOM_VTABLE(IDirectInputDevice8A) SysMouseAvt;
typedef struct SysMouseAImpl SysMouseAImpl;

BOOL WINAPI DINPUT_ForceSetCursorPos(INT,INT,BOOL);

typedef enum {
  WARP_NEEDED,  /* Warping is needed */
  WARP_STARTED, /* Warping has been done, waiting for the warp event */
  WARP_DONE     /* Warping has been done */
} WARP_STATUS;

struct SysMouseAImpl
{
        LPVOID                          lpVtbl;
        DWORD                           ref;
        GUID                            guid;

	IDirectInputImpl *dinput;

	/* The current data format and the conversion between internal
	   and external data formats */
	LPDIDATAFORMAT			df;
	DataFormat                     *wine_df;
	int                             offset_array[WINE_INTERNALMOUSE_NUM_OBJS];

        /* SysMouseAImpl */
        BYTE                            absolute;
        /* Previous position for relative moves */
        LONG				prevX, prevY;
        HHOOK                           hook;
        HWND				win;
        DWORD				dwCoopLevel;
        POINT      			mapped_center;
        DWORD				win_centerX, win_centerY;
        LPDIDEVICEOBJECTDATA 		data_queue;
        int				queue_head, queue_tail, queue_len;
        BOOL				overflow;
	/* warping: whether we need to move mouse back to middle once we
	 * reach window borders (for e.g. shooters, "surface movement" games) */
        WARP_STATUS			need_warp;
        DWORD				warp_time;
        DWORD				cursor_flags;
        int				acquired;
        HANDLE				hEvent;
	CRITICAL_SECTION		crit;
	/* current ddraw mode */
	DWORD				dwDDMode;
	DWORD				dwDDFlags;
	BOOL				drop_acquire_on_mode_switch;
        /* in relative mode, we need to also provide absolute mode..
         * This fakes it */
        POINT                           abs_pretend_point;
        POINT                           abs_prev_point; /* we need this because
                                                           prevX/Y don't help us when
                                                           we warped */

	/* This is for mouse reporting. */
	Wine_InternalMouseData          m_state;
};

static GUID DInput_Wine_Mouse_GUID = { /* 9e573ed8-7734-11d2-8d4a-23903fb6bdf7 */
  0x9e573ed8,
  0x7734,
  0x11d2,
  {0x8d, 0x4a, 0x23, 0x90, 0x3f, 0xb6, 0xbd, 0xf7}
};

/* FIXME: This is ugly and not thread safe :/ */
static IDirectInputDevice8A* current_lock = NULL;


static int mousedev_enum_device(IDirectInputImpl *dinput, DWORD dwDevType, DWORD dwFlags, LPDIDEVICEINSTANCEA lpddi, DWORD dwID)
{
  if ((dwDevType != 0) && (dwDevType != DIDEVTYPE_MOUSE) &&
      (dwDevType != DI8DEVTYPE_MOUSE))
    return 0;

  /* We only support 1 mouse device. */
  if (dwID > 0)
    return 0;

  TRACE("Enumerating the Mouse%ld device\n", dwID);

  /* Return mouse */
  lpddi->guidInstance = GUID_SysMouse;/* DInput's GUID */
  lpddi->guidProduct = DInput_Wine_Mouse_GUID; /* Vendor's GUID */
  if (HIBYTE(dinput->ver) >= 8)
    lpddi->dwDevType = DI8DEVTYPE_MOUSE | (DI8DEVTYPEMOUSE_UNKNOWN << 8);
  else
    lpddi->dwDevType = DIDEVTYPE_MOUSE | (DIDEVTYPEMOUSE_UNKNOWN << 8);
  strcpy(lpddi->tszInstanceName, "Mouse");
  strcpy(lpddi->tszProductName, "Wine Mouse");

  return 1;
}

static SysMouseAImpl *alloc_device(REFGUID rguid, LPVOID mvt, IDirectInputImpl *dinput)
{
    int offset_array[WINE_INTERNALMOUSE_NUM_OBJS] = {
      FIELD_OFFSET(Wine_InternalMouseData, lX),
      FIELD_OFFSET(Wine_InternalMouseData, lY),
      FIELD_OFFSET(Wine_InternalMouseData, lZ),
      FIELD_OFFSET(Wine_InternalMouseData, rgbButtons) + 0,
      FIELD_OFFSET(Wine_InternalMouseData, rgbButtons) + 1,
      FIELD_OFFSET(Wine_InternalMouseData, rgbButtons) + 2,
      FIELD_OFFSET(Wine_InternalMouseData, rgbButtons) + 3,
      FIELD_OFFSET(Wine_InternalMouseData, rgbButtons) + 4
    };
    SysMouseAImpl* newDevice;
    newDevice = HeapAlloc(GetProcessHeap(),HEAP_ZERO_MEMORY,sizeof(SysMouseAImpl));
    newDevice->lpVtbl = mvt;
    newDevice->ref = 1;
    InitializeCriticalSection(&(newDevice->crit));
    CRITICAL_SECTION_NAME(&(newDevice->crit), "SysMouse");
    memcpy(&(newDevice->guid),rguid,sizeof(*rguid));

    /* Per default, Wine uses its internal data format */
    newDevice->df = &Wine_InternalMouseFormat;
    memcpy(newDevice->offset_array, offset_array, WINE_INTERNALMOUSE_NUM_OBJS * sizeof(int));
    newDevice->wine_df = (DataFormat *) HeapAlloc(GetProcessHeap(), 0, sizeof(DataFormat));
    newDevice->wine_df->size = 0;
    newDevice->wine_df->internal_format_size = Wine_InternalMouseFormat.dwDataSize;
    newDevice->wine_df->dt = NULL;
    newDevice->dinput = dinput;

    /* Default cooperative level */
    newDevice->win = GetDesktopWindow();
    newDevice->dwCoopLevel = DISCL_NONEXCLUSIVE | DISCL_BACKGROUND;

    newDevice->queue_len = -1; /* unbuffered */
    newDevice->overflow = FALSE;

    return newDevice;
}

static HRESULT mousedev_create_device(IDirectInputImpl *dinput, REFGUID rguid, REFIID riid, LPDIRECTINPUTDEVICEA* pdev)
{
  if ((IsEqualGUID(&GUID_SysMouse,rguid)) ||             /* Generic Mouse */
      (IsEqualGUID(&DInput_Wine_Mouse_GUID,rguid))) { /* Wine Mouse */
    if ((riid == NULL) ||
	IsEqualGUID(&IID_IDirectInputDeviceA,riid) ||
	IsEqualGUID(&IID_IDirectInputDevice2A,riid) ||
	IsEqualGUID(&IID_IDirectInputDevice7A,riid) ||
	IsEqualGUID(&IID_IDirectInputDevice8A,riid)) {
      *pdev=(IDirectInputDeviceA*) alloc_device(rguid, &SysMouseAvt, dinput);

      TRACE("Creating the Mouse%d device (%p)\n", 0, *pdev);
      return DI_OK;
    } else
      return DIERR_NOINTERFACE;
  }

  return DIERR_DEVICENOTREG;
}

static dinput_device mousedev = {
  100,
  mousedev_enum_device,
  mousedev_create_device
};

void init_mouse(void)
{
  dinput_register_device(&mousedev);
}

void deinit_mouse(void)
{
}


/******************************************************************************
 *	SysMouseA (DInput Mouse support)
 */

/******************************************************************************
  *     Release : release the mouse buffer.
  */
static ULONG WINAPI SysMouseAImpl_Release(LPDIRECTINPUTDEVICE8A iface)
{
	ICOM_THIS(SysMouseAImpl,iface);

	This->ref--;
	if (This->ref)
		return This->ref;

	/* Free the data queue */
	if (This->data_queue != NULL)
	  HeapFree(GetProcessHeap(),0,This->data_queue);

        if (This->hook) {
          UnhookWindowsHookEx( This->hook );
          if (This->dwCoopLevel & DISCL_EXCLUSIVE)
            ShowCursor(TRUE); /* show cursor */
        }
	DeleteCriticalSection(&(This->crit));

	/* Free the DataFormat */
	if (This->df != &(Wine_InternalMouseFormat)) {
	  HeapFree(GetProcessHeap(), 0, This->df->rgodf);
	  HeapFree(GetProcessHeap(), 0, This->df);
	}
	if (This->wine_df) {
	  if (This->wine_df->dt) {
	    HeapFree(GetProcessHeap(), 0, This->wine_df->dt);
	  }
	  HeapFree(GetProcessHeap(), 0, This->wine_df);
	}

	HeapFree(GetProcessHeap(),0,This);
	return 0;
}


/******************************************************************************
  *     SetCooperativeLevel : store the window in which we will do our
  *   grabbing.
  */
static HRESULT WINAPI SysMouseAImpl_SetCooperativeLevel(
	LPDIRECTINPUTDEVICE8A iface,HWND hwnd,DWORD dwflags
)
{
  ICOM_THIS(SysMouseAImpl,iface);

  TRACE("(this=%p,0x%08lx,0x%08lx)\n",This,(DWORD)hwnd,dwflags);

  if (TRACE_ON(dinput)) {
      TRACE(" cooperative level : ");
      _dump_cooperativelevel_DI(dwflags);
  }

  /* Store the window which asks for the mouse */
  if (!hwnd)
    hwnd = GetDesktopWindow();
  This->win = hwnd;
  This->dwCoopLevel = dwflags;

  return 0;
}


/******************************************************************************
  *     SetDataFormat : the application can choose the format of the data
  *   the device driver sends back with GetDeviceState.
  *
  *   For the moment, only the "standard" configuration (c_dfDIMouse) is supported
  *   in absolute and relative mode.
  */
static HRESULT WINAPI SysMouseAImpl_SetDataFormat(
	LPDIRECTINPUTDEVICE8A iface,LPCDIDATAFORMAT df
)
{
  ICOM_THIS(SysMouseAImpl,iface);
  int i;

  TRACE("(this=%p,%p)\n",This,df);

  TRACE("(df.dwSize=%ld)\n",df->dwSize);
  TRACE("(df.dwObjsize=%ld)\n",df->dwObjSize);
  TRACE("(df.dwFlags=0x%08lx)\n",df->dwFlags);
  TRACE("(df.dwDataSize=%ld)\n",df->dwDataSize);
  TRACE("(df.dwNumObjs=%ld)\n",df->dwNumObjs);

  for (i=0;i<df->dwNumObjs;i++) {

    TRACE("\tdf.rgodf[%d].guid %s (%p)\n",i, debugstr_guid(df->rgodf[i].pguid), df->rgodf[i].pguid);
    TRACE("\tdf.rgodf[%d].dwOfs %ld\n",i,df->rgodf[i].dwOfs);
    TRACE("\tdwType 0x%02x,dwInstance %d\n",DIDFT_GETTYPE(df->rgodf[i].dwType),DIDFT_GETINSTANCE(df->rgodf[i].dwType));
    TRACE("\tdf.rgodf[%d].dwFlags 0x%08lx\n",i,df->rgodf[i].dwFlags);
  }

  /* Check if the mouse is in absolute or relative mode */
  if (df->dwFlags == DIDF_ABSAXIS)
    This->absolute = 1;
  else if (df->dwFlags == DIDF_RELAXIS)
    This->absolute = 0;
  else
    ERR("Neither absolute nor relative flag set\n");

  /* Store the new data format */
  This->df = HeapAlloc(GetProcessHeap(),0,df->dwSize);
  memcpy(This->df, df, df->dwSize);
  This->df->rgodf = HeapAlloc(GetProcessHeap(),0,df->dwNumObjs*df->dwObjSize);
  memcpy(This->df->rgodf,df->rgodf,df->dwNumObjs*df->dwObjSize);

  /* Prepare all the data-conversion filters */
  This->wine_df = create_DataFormat(&(Wine_InternalMouseFormat), df, This->offset_array, This->dinput->ver);

  return 0;
}

static BOOL dinput_unacquire_check(SysMouseAImpl* This)
{
    if (!This->acquired) return TRUE;
    /* Unacquire must happen for a game using: DX8, DISCL_FOREGROUND | DISCL_EXCLUSIVE */
    /* Unacquire must not happen for a game using: DX7, DISCL_BACKGROUND | DISCL_NONEXCLUSIVE */
    /* For now, I'll assume that it's the BACKGROUND that selects this behaviour,
     * but it could also be the NONEXCLUSIVE. Should perhaps be checked on Windows. */
    if (This->dwCoopLevel & DISCL_BACKGROUND) return FALSE;
    if (dinput_dd_gbl && This->drop_acquire_on_mode_switch && 
	(((dinput_dd_gbl->dwFlags ^ This->dwDDFlags) & DDRAWI_MODECHANGED) ||
         (dinput_dd_gbl->dwModeIndex != This->dwDDMode))) {
	return TRUE;
    }
    return FALSE;
}

/* low-level mouse hook */
static LRESULT CALLBACK dinput_mouse_hook( int code, WPARAM wparam, LPARAM lparam )
{
    LRESULT ret;
    MSLLHOOKSTRUCT *hook = (MSLLHOOKSTRUCT *)lparam;
    SysMouseAImpl* This = (SysMouseAImpl*) current_lock;
    BOOL suppress_warp = FALSE;
    DWORD dwCoop;
    int wdata;
#ifdef MOUSE_HACK
    static long last_event = 0;
#endif

    if (code != HC_ACTION) return CallNextHookEx( This->hook, code, wparam, lparam );

    EnterCriticalSection(&(This->crit));
    dwCoop = This->dwCoopLevel;

    if (dinput_unacquire_check(This)) {
	LeaveCriticalSection(&(This->crit));
	TRACE("ddraw mode change detected\n");
	ret = CallNextHookEx( This->hook, code, wparam, lparam );
	IDirectInputDevice8_Unacquire((LPDIRECTINPUTDEVICE8A)This);
	return ret;
    }

#ifdef MOUSE_HACK
    /* Only allow mouse events every 10 ms.
     * This is to allow the cursor to start acceleration before
     * the warps happen. But if it involves a mouse button event we
     * allow it since we dont want to loose the clicks.
     */
    if (((GetCurrentTime() - last_event) < 10)
        && wparam == WM_MOUSEMOVE
        && !(hook->flags & LLMHF_INJECTED))
    {
      if (!This->absolute)
      {
        hook->pt.x = This->abs_pretend_point.x;
        hook->pt.y = This->abs_pretend_point.y;
      }
      goto end;
    }
    else last_event = GetCurrentTime();
#endif

    /* Mouse moved -> send event if asked */
    if (This->hEvent)
        SetEvent(This->hEvent);

    if (wparam == WM_MOUSEMOVE) {
	TRACE("input: X=%ld, Y=%ld\n", hook->pt.x, hook->pt.y);
	if (!WARP_CONDITION(This)) {
	  if (This->absolute) {
	    This->m_state.lX = hook->pt.x;
	    This->m_state.lY = hook->pt.y;
	  } else if (hook->flags & LLMHF_INJECTED) {
	    /* SetCursorPos is notifying us that the app is moving the cursor.
	     * Since we're not doing our own warping in this code path,
	     * SetCursorPos will do it. We just need to make sure we treat
	     * this discontinuity in absolute coordinates right, so that the
	     * relative coordinates we return to the app aren't affected. */
	    This->prevX = hook->pt.x;
	    This->prevY = hook->pt.y;
	    goto end;
	  } else {
	    This->m_state.lX += (hook->pt.x - This->prevX);
	    This->m_state.lY += (hook->pt.y - This->prevY);
	  }
	  if (hook->pt.x != This->prevX) {
	    if (This->absolute)
	      GEN_EVENT(This->offset_array[WINE_MOUSE_X_POSITION], This->m_state.lX, hook->time, (This->dinput->evsequence)++)
	    else
	      GEN_EVENT(This->offset_array[WINE_MOUSE_X_POSITION], hook->pt.x - This->prevX, hook->time, (This->dinput->evsequence)++)
	  }
	  if (hook->pt.y != This->prevY) {
	    if (This->absolute)
	      GEN_EVENT(This->offset_array[WINE_MOUSE_Y_POSITION], This->m_state.lY, hook->time, (This->dinput->evsequence)++)
	    else
	      GEN_EVENT(This->offset_array[WINE_MOUSE_Y_POSITION], hook->pt.y - This->prevY, hook->time, (This->dinput->evsequence)++)
	  }
	  This->prevX = hook->pt.x;
	  This->prevY = hook->pt.y;
	} else {
          int max_x, max_y;
          int min_x, min_y;
          RECT window_rect;
          BOOL do_abs_calc = TRUE;
          BOOL changed = FALSE;

          GetWindowRect(This->win, &window_rect);
          min_x = window_rect.left;
          min_y = window_rect.top;
          max_x = window_rect.right - 1;
          max_y = window_rect.bottom - 1;

	  if (hook->flags & LLMHF_INJECTED) {
	    /* must make abs_pretend_point match the incoming hook->pt...
	     * without changing the dinput state seen by the app, I think,
	     * since that state is supposed to be "physical", which I think
	     * would imply it should not be affected by logical events
	     * such as injected hardware messages. */
	    This->abs_pretend_point.x = hook->pt.x;
	    This->abs_pretend_point.y = hook->pt.y;
	    goto end;
	  }

	  /* Now, warp handling */
	  if ((This->need_warp == WARP_STARTED) &&
	      (hook->pt.x == This->mapped_center.x) && (hook->pt.y == This->mapped_center.y)) {
	    /* Warp has been done... */
	    This->abs_prev_point.x = hook->pt.x;
	    This->abs_prev_point.y = hook->pt.y;
	    This->prevX = hook->pt.x;
	    This->prevY = hook->pt.y;
	    hook->pt.x = This->abs_pretend_point.x;
	    hook->pt.y = This->abs_pretend_point.y;
	    This->need_warp = WARP_DONE;
	    suppress_warp = TRUE;
	    goto end;
	  }

	  This->m_state.lX += hook->pt.x - This->prevX;
	  This->m_state.lY += hook->pt.y - This->prevY;

#ifdef MOUSE_HACK
          if ((This->need_warp == WARP_DONE) &&
              (hook->pt.x == This->mapped_center.x) && (hook->pt.y == This->mapped_center.y))
          {
            This->abs_prev_point.x = hook->pt.x;
            This->abs_prev_point.y = hook->pt.y;
            do_abs_calc = FALSE;
          }
#endif
	  /* Relative mouse input with absolute mouse event : the real fun starts here... */
	  if (hook->pt.x != This->prevX) {
	    if (This->absolute)
	      GEN_EVENT(This->offset_array[WINE_MOUSE_X_POSITION], This->m_state.lX, hook->time, (This->dinput->evsequence)++)
	    else
	      GEN_EVENT(This->offset_array[WINE_MOUSE_X_POSITION], hook->pt.x - This->prevX, hook->time, (This->dinput->evsequence)++)
	    changed = TRUE;
	  }
	  if (hook->pt.y != This->prevY) {
	    if (This->absolute)
	      GEN_EVENT(This->offset_array[WINE_MOUSE_Y_POSITION], This->m_state.lY, hook->time, (This->dinput->evsequence)++)
	    else
	      GEN_EVENT(This->offset_array[WINE_MOUSE_Y_POSITION], hook->pt.y - This->prevY, hook->time, (This->dinput->evsequence)++)
	    changed = TRUE;
	  }
	  if (changed) {
	    if (This->need_warp != WARP_NEEDED && This->need_warp != WARP_STARTED) {
	      This->need_warp = WARP_NEEDED;
	    }
	  }
          if (do_abs_calc)
          {
            if (dwCoop & DISCL_NONEXCLUSIVE) {
              This->abs_pretend_point.x += (hook->pt.x - This->abs_prev_point.x);
              This->abs_pretend_point.y += (hook->pt.y - This->abs_prev_point.y);
              if (This->abs_pretend_point.x > max_x)
                This->abs_pretend_point.x = max_x;
              if (This->abs_pretend_point.x < min_x)
                This->abs_pretend_point.x = min_x;
              if (This->abs_pretend_point.y > max_y)
                This->abs_pretend_point.y = max_y;
              if (This->abs_pretend_point.y < min_y)
                This->abs_pretend_point.y = min_y;
            }
            This->abs_prev_point.x = hook->pt.x;
            This->abs_prev_point.y = hook->pt.y;
          }
	  This->prevX = hook->pt.x;
	  This->prevY = hook->pt.y;
          hook->pt.x = This->abs_pretend_point.x;
          hook->pt.y = This->abs_pretend_point.y;
	}
    }
    else if (WARP_CONDITION(This))
    {
        if (hook->flags & LLMHF_INJECTED) goto end;

        /* if its a button or something and we are in relative
         * we still have to fix up the co-ords */
        hook->pt.x = This->abs_pretend_point.x;
        hook->pt.y = This->abs_pretend_point.y;
    }

    TRACE(" msg %x pt %ld %ld MouseData=%ld (W=%d )\n",
          wparam, hook->pt.x, hook->pt.y, hook->mouseData,
          (!This->absolute) && This->need_warp );

    switch(wparam)
    {
    case WM_LBUTTONDOWN:
        if (This->m_state.rgbButtons[0] != 0xFF) {
            GEN_EVENT(This->offset_array[WINE_MOUSE_L_POSITION], 0xFF,
                      hook->time, This->dinput->evsequence++);
            This->m_state.rgbButtons[0] = 0xFF;
        }
        break;
    case WM_LBUTTONUP:
        if (This->m_state.rgbButtons[0] != 0x00) {
            GEN_EVENT(This->offset_array[WINE_MOUSE_L_POSITION], 0x00,
                      hook->time, This->dinput->evsequence++);
            This->m_state.rgbButtons[0] = 0x00;
        }
        break;
    case WM_RBUTTONDOWN:
        if (This->m_state.rgbButtons[1] != 0xFF) {
            GEN_EVENT(This->offset_array[WINE_MOUSE_R_POSITION], 0xFF,
                      hook->time, This->dinput->evsequence++);
            This->m_state.rgbButtons[1] = 0xFF;
        }
        break;
    case WM_RBUTTONUP:
        if (This->m_state.rgbButtons[1] != 0x00) {
            GEN_EVENT(This->offset_array[WINE_MOUSE_R_POSITION], 0x00,
                      hook->time, This->dinput->evsequence++);
            This->m_state.rgbButtons[1] = 0x00;
        }
        break;
    case WM_MBUTTONDOWN:
        if (This->m_state.rgbButtons[2] != 0xFF) {
            GEN_EVENT(This->offset_array[WINE_MOUSE_M_POSITION], 0xFF,
                      hook->time, This->dinput->evsequence++);
            This->m_state.rgbButtons[2] = 0xFF;
        }
        break;
    case WM_MBUTTONUP:
        if (This->m_state.rgbButtons[2] != 0x00) {
            GEN_EVENT(This->offset_array[WINE_MOUSE_M_POSITION], 0x00,
                      hook->time, This->dinput->evsequence++);
            This->m_state.rgbButtons[2] = 0x00;
        }
        break;
    case WM_XBUTTONDOWN:
        if ((HIWORD(hook->mouseData) & XBUTTON1) && (This->m_state.rgbButtons[3] != 0xFF)) {
            GEN_EVENT(This->offset_array[WINE_MOUSE_X1_POSITION], 0xFF,
                      hook->time, This->dinput->evsequence++);
            This->m_state.rgbButtons[3] = 0xFF;
        }
        if ((HIWORD(hook->mouseData) & XBUTTON2) && (This->m_state.rgbButtons[4] != 0xFF)) {
            GEN_EVENT(This->offset_array[WINE_MOUSE_X2_POSITION], 0xFF,
                      hook->time, This->dinput->evsequence++);
            This->m_state.rgbButtons[4] = 0xFF;
        }
        break;
    case WM_XBUTTONUP:
        if ((HIWORD(hook->mouseData) & XBUTTON1) && (This->m_state.rgbButtons[3] != 0x00)) {
            GEN_EVENT(This->offset_array[WINE_MOUSE_X1_POSITION], 0x00,
                      hook->time, This->dinput->evsequence++);
            This->m_state.rgbButtons[3] = 0x00;
        }
        if ((HIWORD(hook->mouseData) & XBUTTON2) && (This->m_state.rgbButtons[4] != 0x00)) {
            GEN_EVENT(This->offset_array[WINE_MOUSE_X2_POSITION], 0x00,
                      hook->time, This->dinput->evsequence++);
            This->m_state.rgbButtons[4] = 0x00;
        }
        break;
    case WM_MOUSEWHEEL:
        wdata = (short)HIWORD(hook->mouseData);
        GEN_EVENT(This->offset_array[WINE_MOUSE_Z_POSITION], wdata,
                  hook->time, This->dinput->evsequence++);
        This->m_state.lZ += wdata;
        break;
    }

  TRACE("(X: %ld - Y: %ld  Z: %ld L: %02x M: %02x R: %02x)\n",
	This->m_state.lX, This->m_state.lY, This->m_state.lZ,
	This->m_state.rgbButtons[0], This->m_state.rgbButtons[2], This->m_state.rgbButtons[1]);

end:
  LeaveCriticalSection(&(This->crit));

  if ((dwCoop & DISCL_NONEXCLUSIVE) && !suppress_warp)
          /* if we're in absolute mode and we have just done a warp (this is
           * the resulting message) - return 1, so we dont queue the message */
  { /* pass the events down to previous handlers (e.g. win32 input) */
      ret = CallNextHookEx( This->hook, code, wparam, lparam );
  }
  else ret = 1;  /* ignore message */
  return ret;
}


static void dinput_window_check(SysMouseAImpl* This)
{
  RECT rect;

  /* the window may have moved */
  GetWindowRect(This->win, &rect);
  This->win_centerX = (rect.right  - rect.left) / 2;
  This->win_centerY = (rect.bottom - rect.top ) / 2;
  This->mapped_center.x = This->win_centerX;
  This->mapped_center.y = This->win_centerY;
  MapWindowPoints(This->win, HWND_DESKTOP, &This->mapped_center, 1);
}


static void dinput_mouse_warp(SysMouseAImpl* This, BOOL force)
{
  /* Request warps at most every 10ms, to allow cursor acceleration to happen. */
  DWORD current = GetCurrentTime();
  if (((current - This->warp_time) < 10) && !force)
    return;

  EnterCriticalSection(&This->crit);
  This->warp_time = current;
  TRACE("Warping mouse to %ld - %ld\n", This->mapped_center.x, This->mapped_center.y);
  DINPUT_ForceSetCursorPos( This->mapped_center.x, This->mapped_center.y, TRUE );

#ifdef MOUSE_HACK
  This->abs_prev_point.x = This->mapped_center.x;
  This->abs_prev_point.y = This->mapped_center.y;
  This->prevX = This->mapped_center.x;
  This->prevY = This->mapped_center.y;
  This->need_warp = WARP_DONE;
#else
  This->need_warp = WARP_STARTED;
#endif
  LeaveCriticalSection(&This->crit);
}


static void dinput_check_warp(SysMouseAImpl* This)
{
  BOOL old_cond = WARP_CONDITION(This);
  BOOL new_cond;
  CURSORINFO info;

  memset(&info, 0, sizeof(info));
  info.cbSize = sizeof(info);
  GetCursorInfo(&info);
  This->cursor_flags = info.flags;

  new_cond = WARP_CONDITION(This);
  if (old_cond && !new_cond) {
    TRACE("Mouse warp deactivated\n");
    EnterCriticalSection(&This->crit);
    This->abs_prev_point.x = This->abs_pretend_point.x;
    This->abs_prev_point.y = This->abs_pretend_point.y;
    This->prevX = This->abs_pretend_point.x;
    This->prevY = This->abs_pretend_point.y;
    This->need_warp = WARP_DONE;
    DINPUT_ForceSetCursorPos( This->abs_pretend_point.x, This->abs_pretend_point.y, FALSE );
    LeaveCriticalSection(&This->crit);
    return;
  }
  if (new_cond && !old_cond) {
    TRACE("Mouse warp activated\n");
    EnterCriticalSection(&This->crit);
    This->abs_pretend_point.x = This->prevX;
    This->abs_pretend_point.y = This->prevY;
    This->abs_prev_point.x = This->prevX;
    This->abs_prev_point.y = This->prevY;
    This->need_warp = WARP_NEEDED;
    LeaveCriticalSection(&This->crit);
    return;
  }

  /* try to recover from lost warp events by retrying
   * the warp if no event received within 20ms */
  if (This->need_warp == WARP_STARTED &&
      ((GetCurrentTime() - This->warp_time) > 20) &&
      WARP_CONDITION(This)) {
    This->need_warp = WARP_NEEDED;
  }
}


/******************************************************************************
  *     Acquire : gets exclusive control of the mouse
  */
static HRESULT WINAPI SysMouseAImpl_Acquire(LPDIRECTINPUTDEVICE8A iface)
{
  ICOM_THIS(SysMouseAImpl,iface);

  TRACE("(this=%p)\n",This);

  dinput_unacquire_check(This);

  if (This->acquired == 0) {
    char buffer[MAX_PATH];
    CURSORINFO info;

    /* Store (in a global variable) the current lock */
    current_lock = (IDirectInputDevice8A*)This;

    if (This->dwCoopLevel & DISCL_EXCLUSIVE)
      ShowCursor(FALSE); /* hide cursor */

    /* Init the mouse state */
    memset(&info, 0, sizeof(info));
    info.cbSize = sizeof(info);
    GetCursorInfo(&info);
    This->cursor_flags = info.flags;
    This->abs_pretend_point.x = info.ptScreenPos.x;
    This->abs_pretend_point.y = info.ptScreenPos.y;
    This->abs_prev_point.x = info.ptScreenPos.x;
    This->abs_prev_point.y = info.ptScreenPos.y;
    This->prevX = info.ptScreenPos.x;
    This->prevY = info.ptScreenPos.y;
    if (This->absolute) {
      This->m_state.lX = info.ptScreenPos.x;
      This->m_state.lY = info.ptScreenPos.y;
    } else {
      This->m_state.lX = 0;
      This->m_state.lY = 0;
    }
    This->m_state.lZ = 0;
    This->m_state.rgbButtons[0] = ((GetAsyncKeyState(VK_LBUTTON) & 0x8000) ? 0xFF : 0x00);
    This->m_state.rgbButtons[1] = ((GetAsyncKeyState(VK_MBUTTON) & 0x8000) ? 0xFF : 0x00);
    This->m_state.rgbButtons[2] = ((GetAsyncKeyState(VK_RBUTTON) & 0x8000) ? 0xFF : 0x00);
    This->m_state.rgbButtons[3] = ((GetAsyncKeyState(VK_XBUTTON1) & 0x8000) ? 0xFF : 0x00);
    This->m_state.rgbButtons[4] = ((GetAsyncKeyState(VK_XBUTTON2) & 0x8000) ? 0xFF : 0x00);
    This->need_warp = WARP_DONE;

    This->dwDDMode = dinput_dd_gbl->dwModeIndex;
    This->dwDDFlags = dinput_dd_gbl->dwFlags;
    /* Hitman Contracts seems to want the mouse to be unacquired by a mode switch
     * while some other apps don't like this.  A windows test case has confirmed that
     * a mode switch should *not* cause the acquire to be dropped. Until we figure
     * out what is really going on I guess we'll just put a specific check for this game. */
    This->drop_acquire_on_mode_switch = FALSE;
    if (GetModuleFileName16( GetCurrentTask(), buffer, MAX_PATH ) ||
	GetModuleFileNameA( 0, buffer, MAX_PATH )) {
      char *p, *appname = buffer;
      if ((p = strrchr( appname, '/' ))) appname = p + 1;
      if ((p = strrchr( appname, '\\' ))) appname = p + 1;
      if( strcasecmp(appname, "hitmancontracts.exe") == 0 ) {
	TRACE("activating drop_acquire_on_mode_switch\n");
        This->drop_acquire_on_mode_switch = TRUE;
      }
    }

    /* Install our mouse hook */
    This->hook = SetWindowsHookExW( WH_MOUSE_LL, dinput_mouse_hook, 0, 0 );

    This->acquired = 1;

    /* If we're in exclusive mode, the app should not see any changes in
     * the mouse state after Acquire. Since the GetCursorInfo above may return
     * a more "current" state than the one most recently reported to the app
     * via WM_MOUSEMOVE messages, we'll make sure our mouse hook is sent an
     * injected message with the most recent state received by the app,
     * so that we can restore that exact position when unacquiring.
     * Otherwise, the app may receive messages after an Unacquire that it
     * may not expect; Morrowind crashes if it does. */
    if (!(This->dwCoopLevel & DISCL_NONEXCLUSIVE)) {
      INPUT input;
      input.type = INPUT_MOUSE;
      input.u.mi.dx          = 0;
      input.u.mi.dy          = 0;
      input.u.mi.mouseData   = 0;
      input.u.mi.dwFlags     = MOUSEEVENTF_MOVE;
      input.u.mi.time        = 0;
      input.u.mi.dwExtraInfo = 0;
      SendInput(1, &input, sizeof(input));
    }

    dinput_window_check(This);
    if (WARP_CONDITION(This)) {
      TRACE("Mouse warp activated\n");
      dinput_mouse_warp(This, TRUE);
    }

    return DI_OK;
  }
  return S_FALSE;
}

/******************************************************************************
  *     Unacquire : frees the mouse
  */
static HRESULT WINAPI SysMouseAImpl_Unacquire(LPDIRECTINPUTDEVICE8A iface)
{
    ICOM_THIS(SysMouseAImpl,iface);

    TRACE("(this=%p)\n",This);

    if (This->acquired)
    {
        /* Reinstall previous mouse event handler */
        if (This->hook) {
          BOOL old_cond = WARP_CONDITION(This);
          UnhookWindowsHookEx( This->hook );
          This->hook = 0;
          if (old_cond || !(This->dwCoopLevel & DISCL_NONEXCLUSIVE)) {
            /* Warp the mouse back to where it's supposed to be */
            if ((GetForegroundWindow() == This->win) && !IsIconic(This->win))
              DINPUT_ForceSetCursorPos( This->abs_pretend_point.x, This->abs_pretend_point.y, FALSE );
            if (old_cond) {
              TRACE("Mouse warp deactivated\n");
            }
          }
          if (This->dwCoopLevel & DISCL_EXCLUSIVE)
            ShowCursor(TRUE); /* show cursor */
        }

        /* No more locks */
        current_lock = NULL;

        /* Unacquire device */
        This->acquired = 0;
    }
    else
	ERR("Unacquiring a not-acquired device !!!\n");

    return DI_OK;
}

/******************************************************************************
  *     GetDeviceState : returns the "state" of the mouse.
  *
  *   For the moment, only the "standard" return structure (DIMOUSESTATE) is
  *   supported.
  */
static HRESULT WINAPI SysMouseAImpl_GetDeviceState(
	LPDIRECTINPUTDEVICE8A iface,DWORD len,LPVOID ptr
) {
  ICOM_THIS(SysMouseAImpl,iface);

  TRACE("(this=%p,0x%08lx,%p): \n",This,len,ptr);

  if (This->acquired == 0) {
      WARN(" application tries to get data from an unacquired device !\n");
      return DIERR_NOTACQUIRED;
  }

  EnterCriticalSection(&(This->crit));

  /* zero out the memory passed in */
  memset(ptr, 0, len);

  /* Copy the current mouse state */
  fill_DataFormat(ptr, &(This->m_state), This->wine_df);

  /* Initialize the buffer when in relative mode */
  if (This->absolute == 0) {
    This->m_state.lX = 0;
    This->m_state.lY = 0;
    This->m_state.lZ = 0;
  }

  /* Check if we need to do a mouse warping */
  dinput_check_warp(This);
  if (This->need_warp == WARP_NEEDED) {
    dinput_window_check(This);
    dinput_mouse_warp(This, FALSE);
  }

  LeaveCriticalSection(&(This->crit));

  TRACE("(X: %ld - Y: %ld Z: %ld  L: %02x M: %02x R: %02x)\n",
	This->m_state.lX, This->m_state.lY, This->m_state.lZ,
	This->m_state.rgbButtons[0], This->m_state.rgbButtons[2], This->m_state.rgbButtons[1]);

  return 0;
}

/******************************************************************************
  *     GetDeviceState : gets buffered input data.
  */
static HRESULT WINAPI SysMouseAImpl_GetDeviceData(LPDIRECTINPUTDEVICE8A iface,
					      DWORD dodsize,
					      LPDIDEVICEOBJECTDATA dod,
					      LPDWORD entries,
					      DWORD flags
) {
  ICOM_THIS(SysMouseAImpl,iface);
  DWORD len, count, nqtail;
  int ret = DI_OK;

  TRACE("(%p)->(dods=%ld,entries=%ld,fl=0x%08lx)\n",This,dodsize,*entries,flags);

  if (This->acquired == 0) {
      WARN(" application tries to get data from an unacquired device !\n");
      return DIERR_NOTACQUIRED;
  }

  if (This->queue_len == -1) {
      return DIERR_NOTBUFFERED;
  }

  EnterCriticalSection(&(This->crit));

  len = ((This->queue_head < This->queue_tail) ? This->queue_len : 0)
      + (This->queue_head - This->queue_tail);
  if (len > *entries) len = *entries;

  if (dod == NULL) {
    if (len)
    	TRACE("Application discarding %ld event(s).\n", len);

    *entries = len;
    nqtail = This->queue_tail + len;
    while (nqtail >= This->queue_len) nqtail -= This->queue_len;
  } else {
    if (dodsize < sizeof(DIDEVICEOBJECTDATA_DX3)) {
      ERR("Wrong structure size !\n");
      LeaveCriticalSection(&(This->crit));
      return DIERR_INVALIDPARAM;
    }

    if (len)
    	TRACE("Application retrieving %ld event(s).\n", len);

    count = 0;
    nqtail = This->queue_tail;
    while (count < len) {
      LPDIDEVICEOBJECTDATA pd = (LPDIDEVICEOBJECTDATA)((BYTE *)dod + dodsize * count);
      pd->dwOfs       = This->data_queue[nqtail].dwOfs;
      pd->dwData      = This->data_queue[nqtail].dwData;
      pd->dwTimeStamp = This->data_queue[nqtail].dwTimeStamp;
      pd->dwSequence  = This->data_queue[nqtail].dwSequence;
      nqtail++;
      if (nqtail >= This->queue_len) nqtail -= This->queue_len;
      count++;
    }
    *entries = count;
  }
  if (This->overflow) {
    ret = DI_BUFFEROVERFLOW;
    if (!(flags & DIGDD_PEEK)) {
      This->overflow = FALSE;
    }
  }

  if (!(flags & DIGDD_PEEK)) {
    This->queue_tail = nqtail;
  }

  LeaveCriticalSection(&(This->crit));

  /* Check if we need to do a mouse warping */
  dinput_check_warp(This);
  if (This->need_warp == WARP_NEEDED) {
    dinput_window_check(This);
    dinput_mouse_warp(This, FALSE);
  }
  return ret;
}

/******************************************************************************
  *     SetProperty : change input device properties
  */
static HRESULT WINAPI SysMouseAImpl_SetProperty(LPDIRECTINPUTDEVICE8A iface,
					    REFGUID rguid,
					    LPCDIPROPHEADER ph)
{
  ICOM_THIS(SysMouseAImpl,iface);

  TRACE("(this=%p,%s,%p)\n",This,debugstr_guid(rguid),ph);

  if (!HIWORD(rguid)) {
    switch ((DWORD)rguid) {
    case (DWORD) DIPROP_BUFFERSIZE: {
      LPCDIPROPDWORD	pd = (LPCDIPROPDWORD)ph;

      TRACE("buffersize = %ld\n",pd->dwData);

      This->data_queue = (LPDIDEVICEOBJECTDATA)HeapAlloc(GetProcessHeap(),0,
							  pd->dwData * sizeof(DIDEVICEOBJECTDATA));
      This->queue_head = 0;
      This->queue_tail = 0;
      This->queue_len  = pd->dwData;
      break;
    }
    case (DWORD) DIPROP_AXISMODE: {
      LPCDIPROPDWORD    pd = (LPCDIPROPDWORD)ph;
      This->absolute = !(pd->dwData);
      TRACE("absolute mode: %d\n", This->absolute);
      break;
    }
    default:
      FIXME("Unknown type %ld (%s)\n",(DWORD)rguid,debugstr_guid(rguid));
      break;
    }
  }

  return 0;
}

/******************************************************************************
  *     GetProperty : get input device properties
  */
static HRESULT WINAPI SysMouseAImpl_GetProperty(LPDIRECTINPUTDEVICE8A iface,
						REFGUID rguid,
						LPDIPROPHEADER pdiph)
{
  ICOM_THIS(SysMouseAImpl,iface);

  TRACE("(this=%p,%s,%p)\n",
	iface, debugstr_guid(rguid), pdiph);

  if (TRACE_ON(dinput))
    _dump_DIPROPHEADER(pdiph);

  if (!HIWORD(rguid)) {
    switch ((DWORD)rguid) {
    case (DWORD) DIPROP_BUFFERSIZE: {
      LPDIPROPDWORD	pd = (LPDIPROPDWORD)pdiph;

      TRACE(" return buffersize = %d\n",This->queue_len);
      pd->dwData = This->queue_len;
      break;
    }

    case (DWORD) DIPROP_GRANULARITY: {
      LPDIPROPDWORD pr = (LPDIPROPDWORD) pdiph;

      /* We'll just assume that the app asks about the Z axis */
      TRACE(" return granularity = %d\n",WHEEL_DELTA);
      pr->dwData = WHEEL_DELTA;

      break;
    }

    case (DWORD) DIPROP_RANGE: {
      LPDIPROPRANGE pr = (LPDIPROPRANGE) pdiph;

      if ((pdiph->dwHow == DIPH_BYID) &&
	  ((pdiph->dwObj == (DIDFT_MAKEINSTANCE(WINE_MOUSE_X_AXIS_INSTANCE) | DIDFT_RELAXIS)) ||
	   (pdiph->dwObj == (DIDFT_MAKEINSTANCE(WINE_MOUSE_Y_AXIS_INSTANCE) | DIDFT_RELAXIS)))) {
	/* Querying the range of either the X or the Y axis.  As I do
	   not know the range, do as if the range were
	   unrestricted...*/
	pr->lMin = DIPROPRANGE_NOMIN;
	pr->lMax = DIPROPRANGE_NOMAX;
      }
      TRACE(" return range (%ld,%ld)\n", pr->lMin, pr->lMax);

      break;
    }

    default:
      FIXME("Unknown type %ld (%s)\n",(DWORD)rguid,debugstr_guid(rguid));
      break;
    }
  }


  return DI_OK;
}



/******************************************************************************
  *     SetEventNotification : specifies event to be sent on state change
  */
static HRESULT WINAPI SysMouseAImpl_SetEventNotification(LPDIRECTINPUTDEVICE8A iface,
							 HANDLE hnd) {
  ICOM_THIS(SysMouseAImpl,iface);

  TRACE("(this=%p,0x%08lx)\n",This,(DWORD)hnd);

  This->hEvent = hnd;

  return DI_OK;
}

/******************************************************************************
  *     GetCapabilities : get the device capablitites
  */
static HRESULT WINAPI SysMouseAImpl_GetCapabilities(
	LPDIRECTINPUTDEVICE8A iface,
	LPDIDEVCAPS lpDIDevCaps)
{
  ICOM_THIS(SysMouseAImpl,iface);

  TRACE("(this=%p,%p)\n",This,lpDIDevCaps);

  if (lpDIDevCaps->dwSize == sizeof(DIDEVCAPS)) {
    lpDIDevCaps->dwFlags = DIDC_ATTACHED;
    lpDIDevCaps->dwDevType = DIDEVTYPE_MOUSE;
    lpDIDevCaps->dwAxes = 3;
#ifdef MANY_BUTTONS
    lpDIDevCaps->dwButtons = 5;
#else
    lpDIDevCaps->dwButtons = 3;
#endif
    lpDIDevCaps->dwPOVs = 0;
    lpDIDevCaps->dwFFSamplePeriod = 0;
    lpDIDevCaps->dwFFMinTimeResolution = 0;
    lpDIDevCaps->dwFirmwareRevision = 100;
    lpDIDevCaps->dwHardwareRevision = 100;
    lpDIDevCaps->dwFFDriverVersion = 0;
  } else {
    /* DirectX 3.0 */
    FIXME("DirectX 3.0 not supported....\n");
  }

  return DI_OK;
}

static void set_name(char *dest, int i) {
  if( DInputMouseName[i].id != i ) {
    WARN("Error in DInputMouseName table: index=%d has incorrect id=%d\n", i, DInputMouseName[i].id);
  }
  strcpy(dest, DInputMouseName[i].name);
}


/******************************************************************************
  *     EnumObjects : enumerate the different buttons and axis...
  */
static HRESULT WINAPI SysMouseAImpl_EnumObjects(
	LPDIRECTINPUTDEVICE8A iface,
	LPDIENUMDEVICEOBJECTSCALLBACKA lpCallback,
	LPVOID lpvRef,
	DWORD dwFlags)
{
  ICOM_THIS(SysMouseAImpl,iface);
  DIDEVICEOBJECTINSTANCEA ddoi;
  int i;

  TRACE("(this=%p,%p,%p,%08lx)\n", This, lpCallback, lpvRef, dwFlags);
  if (TRACE_ON(dinput)) {
    DPRINTF("  - flags = ");
    _dump_EnumObjects_flags(dwFlags);
    DPRINTF("\n");
  }

  /* Only the fields till dwFFMaxForce are relevant */
  memset(&ddoi, 0, sizeof(ddoi));
  ddoi.dwSize = FIELD_OFFSET(DIDEVICEOBJECTINSTANCEA, dwFFMaxForce);

  /* In a mouse, we have : two relative axis and three buttons */
  if ((dwFlags == DIDFT_ALL) ||
      (dwFlags & DIDFT_AXIS)) {

    /* enumerate up to 3 axes (X,Y,Z) */
    for( i = WINE_MOUSE_X_POSITION; i <= WINE_MOUSE_Z_POSITION; i++ ) {
      ddoi.guidType = *Wine_InternalMouseObjectFormat[i].pguid;
      ddoi.dwOfs    =  Wine_InternalMouseObjectFormat[i].dwOfs;
      ddoi.dwType   =  Wine_InternalMouseObjectFormat[i].dwType;
      ddoi.dwFlags  =  Wine_InternalMouseObjectFormat[i].dwFlags;
      set_name(ddoi.tszName, i);
      _dump_OBJECTINSTANCEA(&ddoi);
      if (lpCallback(&ddoi, lpvRef) != DIENUM_CONTINUE) return DI_OK;
    }
  }

  if ((dwFlags == DIDFT_ALL) ||
      (dwFlags & DIDFT_BUTTON)) {
    
    int last_button = WINE_MOUSE_M_POSITION;
#ifdef MANY_BUTTONS
    last_button = WINE_MOUSE_X2_POSITION;
#endif
    
    /* enumerate up to 5 buttons (L,R,M,X1,X2) */
    for( i = WINE_MOUSE_L_POSITION; i <= last_button; i++ ) {
      ddoi.guidType = *Wine_InternalMouseObjectFormat[i].pguid;
      ddoi.dwOfs    =  Wine_InternalMouseObjectFormat[i].dwOfs;
      ddoi.dwType   =  Wine_InternalMouseObjectFormat[i].dwType;
      ddoi.dwFlags  =  Wine_InternalMouseObjectFormat[i].dwFlags;
      set_name(ddoi.tszName, i);
      _dump_OBJECTINSTANCEA(&ddoi);
      if (lpCallback(&ddoi, lpvRef) != DIENUM_CONTINUE) return DI_OK;
    }
  }

  return DI_OK;
}

int get_Position_from_ID(DWORD id){
  int instance = DIDFT_GETINSTANCE(id);
  int pos = -1;
  switch(instance) {
    case WINE_MOUSE_X_AXIS_INSTANCE: pos = WINE_MOUSE_X_POSITION; break;
    case WINE_MOUSE_Y_AXIS_INSTANCE: pos = WINE_MOUSE_Y_POSITION; break;
    case WINE_MOUSE_Z_AXIS_INSTANCE: pos = WINE_MOUSE_Z_POSITION; break;
    case WINE_MOUSE_L_BUTTON_INSTANCE: pos = WINE_MOUSE_L_POSITION; break;
    case WINE_MOUSE_R_BUTTON_INSTANCE: pos = WINE_MOUSE_R_POSITION; break;
    case WINE_MOUSE_M_BUTTON_INSTANCE: pos = WINE_MOUSE_M_POSITION; break;
    case WINE_MOUSE_X1_BUTTON_INSTANCE: pos = WINE_MOUSE_X1_POSITION; break;
    case WINE_MOUSE_X2_BUTTON_INSTANCE: pos = WINE_MOUSE_X2_POSITION; break;
    default: break;
  }
  return pos;
}
    
static HRESULT WINAPI SysMouseAImpl_GetObjectInfo(LPDIRECTINPUTDEVICE8A iface,
                                                  LPDIDEVICEOBJECTINSTANCEA pdidoi,
						  DWORD dwObj,
						  DWORD dwHow)
{
  ICOM_THIS(SysMouseAImpl,iface);
  int pos;

  TRACE("(this=%p,%p,0x%08lx,0x%08lx)\n",
	This, pdidoi, dwObj, dwHow);

  if (TRACE_ON(dinput)) {
    DPRINTF("  - dwObj = 0x%08lx\n", dwObj);
    DPRINTF("  - dwHow = %s\n",
	    ((dwHow == DIPH_DEVICE) ? "DIPH_DEVICE" :
	     ((dwHow == DIPH_BYOFFSET) ? "DIPH_BYOFFSET" :
	      ((dwHow == DIPH_BYID)) ? "DIPH_BYID" : "unknown")));
  }
  
  
  switch( dwHow ) {
    case DIPH_BYID:
      pos = get_Position_from_ID(dwObj);
      if( pos < 0 ) { 
	TRACE("id=0x%08lx not found\n", dwObj);
	return DIERR_OBJECTNOTFOUND;
      }
      pdidoi->guidType = *Wine_InternalMouseObjectFormat[pos].pguid;
      pdidoi->dwOfs    =  Wine_InternalMouseObjectFormat[pos].dwOfs;
      pdidoi->dwType   =  Wine_InternalMouseObjectFormat[pos].dwType;
      pdidoi->dwFlags  =  Wine_InternalMouseObjectFormat[pos].dwFlags;
      set_name(pdidoi->tszName, pos);
      break;
    case DIPH_DEVICE:
    case DIPH_BYOFFSET: 
    default:
      FIXME("dwHow = 0x%08lx - unsupported!\n", dwHow);
  }
  
  if (TRACE_ON(dinput))
    _dump_OBJECTINSTANCEA(pdidoi);

  return DI_OK;
}

static HRESULT WINAPI SysMouseAImpl_GetDeviceInfo(
	LPDIRECTINPUTDEVICE8A iface,
	LPDIDEVICEINSTANCEA lpddi)
{
	ICOM_THIS(SysMouseAImpl,iface);

	TRACE("(this=%p,%p)\n",
	      iface, lpddi);

	lpddi->guidInstance = GUID_SysMouse;/* DInput's GUID */
	lpddi->guidProduct = DInput_Wine_Mouse_GUID; /* Vendor's GUID */
	if (HIBYTE(This->dinput->ver) >= 8)
	  lpddi->dwDevType = DI8DEVTYPE_MOUSE | (DI8DEVTYPEMOUSE_UNKNOWN << 8);
	else
	  lpddi->dwDevType = DIDEVTYPE_MOUSE | (DIDEVTYPEMOUSE_UNKNOWN << 8);
	strcpy(lpddi->tszInstanceName, "Mouse");
	strcpy(lpddi->tszProductName, "Wine Mouse");

	return DI_OK;
}

static ICOM_VTABLE(IDirectInputDevice8A) SysMouseAvt =
{
	ICOM_MSVTABLE_COMPAT_DummyRTTIVALUE
	IDirectInputDevice2AImpl_QueryInterface,
	IDirectInputDevice2AImpl_AddRef,
	SysMouseAImpl_Release,
	SysMouseAImpl_GetCapabilities,
	SysMouseAImpl_EnumObjects,
	SysMouseAImpl_GetProperty,
	SysMouseAImpl_SetProperty,
	SysMouseAImpl_Acquire,
	SysMouseAImpl_Unacquire,
	SysMouseAImpl_GetDeviceState,
	SysMouseAImpl_GetDeviceData,
	SysMouseAImpl_SetDataFormat,
	SysMouseAImpl_SetEventNotification,
	SysMouseAImpl_SetCooperativeLevel,
	SysMouseAImpl_GetObjectInfo,
	SysMouseAImpl_GetDeviceInfo,
	IDirectInputDevice2AImpl_RunControlPanel,
	IDirectInputDevice2AImpl_Initialize,
	IDirectInputDevice2AImpl_CreateEffect,
	IDirectInputDevice2AImpl_EnumEffects,
	IDirectInputDevice2AImpl_GetEffectInfo,
	IDirectInputDevice2AImpl_GetForceFeedbackState,
	IDirectInputDevice2AImpl_SendForceFeedbackCommand,
	IDirectInputDevice2AImpl_EnumCreatedEffectObjects,
	IDirectInputDevice2AImpl_Escape,
	IDirectInputDevice2AImpl_Poll,
	IDirectInputDevice2AImpl_SendDeviceData,
	IDirectInputDevice7AImpl_EnumEffectsInFile,
	IDirectInputDevice7AImpl_WriteEffectToFile,
	IDirectInputDevice8AImpl_BuildActionMap,
	IDirectInputDevice8AImpl_SetActionMap,
	IDirectInputDevice8AImpl_GetImageInfo
};
