diff --git a/dlls/dinput/joystick_linuxinput.c b/dlls/dinput/joystick_linuxinput.c
index b5b57e7b1133328d9434d4e7105411f365839023..8463109347bbed6c7118697a890bb89c951d757f 100644
--- a/dlls/dinput/joystick_linuxinput.c
+++ b/dlls/dinput/joystick_linuxinput.c
@@ -69,6 +69,18 @@ WINE_DEFAULT_DEBUG_CHANNEL(dinput);
 #define WINE_JOYSTICK_AXIS_BASE   0
 #define WINE_JOYSTICK_BUTTON_BASE 8
 
+typedef struct EffectListItem EffectListItem;
+struct EffectListItem
+{
+        LPDIRECTINPUTEFFECT ref;
+	struct EffectListItem* next;
+};
+
+/* implemented in effect_linuxinput.c */
+HRESULT linuxinput_create_effect(int fd, REFGUID rguid, LPDIRECTINPUTEFFECT* peff);
+HRESULT linuxinput_get_info_A(int fd, REFGUID rguid, LPDIEFFECTINFOA info);
+HRESULT linuxinput_get_info_W(int fd, REFGUID rguid, LPDIEFFECTINFOW info);
+
 typedef struct JoystickImpl JoystickImpl;
 static const IDirectInputDevice8AVtbl JoystickAvt;
 static const IDirectInputDevice8WVtbl JoystickWvt;
@@ -104,6 +116,8 @@ struct JoystickImpl
 	/* Force feedback variables */
 	BOOL				has_ff;
 	int				num_effects;
+	EffectListItem*			top_effect;
+	int				ff_state;
 
 	/* data returned by the EVIOCGABS() ioctl */
 	int				axes[ABS_MAX+1][5];
@@ -288,6 +302,7 @@ static JoystickImpl *alloc_device(REFGUID rguid, const void *jvt, IDirectInputIm
   newDevice->ref = 1;
   newDevice->joyfd = -1;
   newDevice->dinput = dinput;
+  newDevice->ff_state = FF_STATUS_STOPPED;
   memcpy(&(newDevice->guid),rguid,sizeof(*rguid));
   for (i=0;i<ABS_MAX;i++) {
     newDevice->wantmin[i] = -32768;
@@ -376,6 +391,9 @@ static ULONG WINAPI JoystickAImpl_Release(LPDIRECTINPUTDEVICE8A iface)
 	if (ref)
 		return ref;
 
+	/* Reset the FF state, free all effects, etc */
+	iface->lpVtbl->SendForceFeedbackCommand(iface, DISFFC_RESET);
+
 	/* Free the data queue */
 	HeapFree(GetProcessHeap(),0,This->data_queue);
 
@@ -705,6 +723,9 @@ static void joy_polldev(JoystickImpl *This) {
 		break;
 	    }
 	    break;
+	case EV_FF_STATUS:
+	    This->ff_state = ie.value;
+	    break;
 	default:
 	    FIXME("joystick cannot handle type %d event (code %d)\n",ie.type,ie.code);
 	    break;
@@ -1104,6 +1125,350 @@ static HRESULT WINAPI JoystickAImpl_GetProperty(LPDIRECTINPUTDEVICE8A iface,
   return DI_OK;
 }
 
+/****************************************************************************** 
+  *	CreateEffect - Create a new FF effect with the specified params
+  */
+static HRESULT WINAPI JoystickAImpl_CreateEffect(LPDIRECTINPUTDEVICE8A iface,
+						 REFGUID rguid,
+						 LPCDIEFFECT lpeff,
+						 LPDIRECTINPUTEFFECT *ppdef,
+						 LPUNKNOWN pUnkOuter)
+{
+#ifdef HAVE_STRUCT_FF_EFFECT_DIRECTION
+    EffectListItem* new = NULL;
+    HRESULT retval = DI_OK;
+#endif
+
+    JoystickImpl* This = (JoystickImpl*)iface;
+    TRACE("(this=%p,%p,%p,%p,%p)\n", This, rguid, lpeff, ppdef, pUnkOuter);
+
+#ifndef HAVE_STRUCT_FF_EFFECT_DIRECTION
+    TRACE("not available (compiled w/o ff support)\n");
+    *ppdef = NULL;
+    return DI_OK; 
+#else
+
+    new = malloc(sizeof(EffectListItem));
+    new->next = This->top_effect;
+    This->top_effect = new;
+
+    retval = linuxinput_create_effect(This->joyfd, rguid, &(new->ref));
+    if (retval != DI_OK)
+	return retval;
+ 
+    if (lpeff != NULL)
+	retval = new->ref->lpVtbl->SetParameters(new->ref, lpeff, 0); 
+    if (retval != DI_OK)
+	return retval;
+
+    *ppdef = new->ref;
+
+    if (pUnkOuter != NULL)
+	FIXME("Interface aggregation not implemented.\n");
+
+    return DI_OK;
+
+#endif /* HAVE_STRUCT_FF_EFFECT_DIRECTION */
+} 
+
+/*******************************************************************************
+ *	EnumEffects - Enumerate available FF effects
+ */
+static HRESULT WINAPI JoystickAImpl_EnumEffects(LPDIRECTINPUTDEVICE8A iface,
+						LPDIENUMEFFECTSCALLBACKA lpCallback,
+						LPVOID pvRef,
+						DWORD dwEffType)
+{
+    DIEFFECTINFOA dei; /* feif */
+    DWORD type = DIEFT_GETTYPE(dwEffType);
+    JoystickImpl* This = (JoystickImpl*)iface;
+    int xfd = This->joyfd;
+
+    TRACE("(this=%p,%p,%ld) type=%ld fd=%d\n", This, pvRef, dwEffType, type, xfd);
+
+#ifdef HAVE_STRUCT_FF_EFFECT_DIRECTION
+    dei.dwSize = sizeof(DIEFFECTINFOA);          
+
+    /* We need to return something even if we're not yet acquired */
+    if (xfd == -1)
+	iface->lpVtbl->Acquire(iface);
+
+    if ((type == DIEFT_ALL || type == DIEFT_CONSTANTFORCE)
+	&& test_bit(This->ffbits, FF_CONSTANT)) {
+	iface->lpVtbl->GetEffectInfo(iface, &dei, &GUID_ConstantForce);
+	(*lpCallback)(&dei, pvRef);
+    }
+
+    if ((type == DIEFT_ALL || type == DIEFT_PERIODIC)
+	&& test_bit(This->ffbits, FF_PERIODIC)) {
+	if (test_bit(This->ffbits, FF_SQUARE)) {
+	    iface->lpVtbl->GetEffectInfo(iface, &dei, &GUID_Square);
+	    (*lpCallback)(&dei, pvRef);
+	}
+	if (test_bit(This->ffbits, FF_SINE)) {
+            iface->lpVtbl->GetEffectInfo(iface, &dei, &GUID_Sine);
+	    (*lpCallback)(&dei, pvRef);
+	}
+	if (test_bit(This->ffbits, FF_TRIANGLE)) {
+	    iface->lpVtbl->GetEffectInfo(iface, &dei, &GUID_Triangle);
+	    (*lpCallback)(&dei, pvRef);
+	}
+	if (test_bit(This->ffbits, FF_SAW_UP)) {
+	    iface->lpVtbl->GetEffectInfo(iface, &dei, &GUID_SawtoothUp);
+	    (*lpCallback)(&dei, pvRef);
+	}
+	if (test_bit(This->ffbits, FF_SAW_DOWN)) {
+	    iface->lpVtbl->GetEffectInfo(iface, &dei, &GUID_SawtoothDown);
+	    (*lpCallback)(&dei, pvRef);
+	}
+    } 
+
+    if ((type == DIEFT_ALL || type == DIEFT_RAMPFORCE)
+	&& test_bit(This->ffbits, FF_RAMP)) {
+        iface->lpVtbl->GetEffectInfo(iface, &dei, &GUID_RampForce);
+        (*lpCallback)(&dei, pvRef);
+    }
+
+    if (type == DIEFT_ALL || type == DIEFT_CONDITION) {
+	if (test_bit(This->ffbits, FF_SPRING)) {
+	    iface->lpVtbl->GetEffectInfo(iface, &dei, &GUID_Spring);
+	    (*lpCallback)(&dei, pvRef);
+	}
+	if (test_bit(This->ffbits, FF_DAMPER)) {
+	    iface->lpVtbl->GetEffectInfo(iface, &dei, &GUID_Damper);
+	    (*lpCallback)(&dei, pvRef);
+	}
+	if (test_bit(This->ffbits, FF_INERTIA)) {
+	    iface->lpVtbl->GetEffectInfo(iface, &dei, &GUID_Inertia);
+	    (*lpCallback)(&dei, pvRef);
+	}
+	if (test_bit(This->ffbits, FF_FRICTION)) {
+	    iface->lpVtbl->GetEffectInfo(iface, &dei, &GUID_Friction);
+	    (*lpCallback)(&dei, pvRef);
+	}
+    }
+
+    /* return to unaquired state if that's where it was */
+    if (xfd == -1)
+	iface->lpVtbl->Unacquire(iface);
+#endif
+
+    return DI_OK;
+}
+
+static HRESULT WINAPI JoystickWImpl_EnumEffects(LPDIRECTINPUTDEVICE8W iface,
+                                                LPDIENUMEFFECTSCALLBACKW lpCallback,
+                                                LPVOID pvRef,
+                                                DWORD dwEffType)
+{
+    /* seems silly to duplicate all this code but all the structures and functions
+     * are actually different (A/W) */
+    DIEFFECTINFOW dei; /* feif */
+    DWORD type = DIEFT_GETTYPE(dwEffType);
+    JoystickImpl* This = (JoystickImpl*)iface;
+    int xfd = This->joyfd;
+
+    TRACE("(this=%p,%p,%ld) type=%ld fd=%d\n", This, pvRef, dwEffType, type, xfd);
+
+#ifdef HAVE_STRUCT_FF_EFFECT_DIRECTION
+    dei.dwSize = sizeof(DIEFFECTINFOW);          
+
+    /* We need to return something even if we're not yet acquired */
+    if (xfd == -1)
+	iface->lpVtbl->Acquire(iface);
+
+    if ((type == DIEFT_ALL || type == DIEFT_CONSTANTFORCE)
+	&& test_bit(This->ffbits, FF_CONSTANT)) {
+	iface->lpVtbl->GetEffectInfo(iface, &dei, &GUID_ConstantForce);
+	(*lpCallback)(&dei, pvRef);
+    }
+
+    if ((type == DIEFT_ALL || type == DIEFT_PERIODIC)
+	&& test_bit(This->ffbits, FF_PERIODIC)) {
+	if (test_bit(This->ffbits, FF_SQUARE)) {
+	    iface->lpVtbl->GetEffectInfo(iface, &dei, &GUID_Square);
+	    (*lpCallback)(&dei, pvRef);
+	}
+	if (test_bit(This->ffbits, FF_SINE)) {
+            iface->lpVtbl->GetEffectInfo(iface, &dei, &GUID_Sine);
+	    (*lpCallback)(&dei, pvRef);
+	}
+	if (test_bit(This->ffbits, FF_TRIANGLE)) {
+	    iface->lpVtbl->GetEffectInfo(iface, &dei, &GUID_Triangle);
+	    (*lpCallback)(&dei, pvRef);
+	}
+	if (test_bit(This->ffbits, FF_SAW_UP)) {
+	    iface->lpVtbl->GetEffectInfo(iface, &dei, &GUID_SawtoothUp);
+	    (*lpCallback)(&dei, pvRef);
+	}
+	if (test_bit(This->ffbits, FF_SAW_DOWN)) {
+	    iface->lpVtbl->GetEffectInfo(iface, &dei, &GUID_SawtoothDown);
+	    (*lpCallback)(&dei, pvRef);
+	}
+    } 
+
+    if ((type == DIEFT_ALL || type == DIEFT_RAMPFORCE)
+	&& test_bit(This->ffbits, FF_RAMP)) {
+        iface->lpVtbl->GetEffectInfo(iface, &dei, &GUID_RampForce);
+        (*lpCallback)(&dei, pvRef);
+    }
+
+    if (type == DIEFT_ALL || type == DIEFT_CONDITION) {
+	if (test_bit(This->ffbits, FF_SPRING)) {
+	    iface->lpVtbl->GetEffectInfo(iface, &dei, &GUID_Spring);
+	    (*lpCallback)(&dei, pvRef);
+	}
+	if (test_bit(This->ffbits, FF_DAMPER)) {
+	    iface->lpVtbl->GetEffectInfo(iface, &dei, &GUID_Damper);
+	    (*lpCallback)(&dei, pvRef);
+	}
+	if (test_bit(This->ffbits, FF_INERTIA)) {
+	    iface->lpVtbl->GetEffectInfo(iface, &dei, &GUID_Inertia);
+	    (*lpCallback)(&dei, pvRef);
+	}
+	if (test_bit(This->ffbits, FF_FRICTION)) {
+	    iface->lpVtbl->GetEffectInfo(iface, &dei, &GUID_Friction);
+	    (*lpCallback)(&dei, pvRef);
+	}
+    }
+
+    /* return to unaquired state if that's where it was */
+    if (xfd == -1)
+	iface->lpVtbl->Unacquire(iface);
+#endif
+
+    return DI_OK;
+}
+
+/*******************************************************************************
+ *      GetEffectInfo - Get information about a particular effect 
+ */
+static HRESULT WINAPI JoystickAImpl_GetEffectInfo(LPDIRECTINPUTDEVICE8A iface,
+						  LPDIEFFECTINFOA pdei,
+						  REFGUID guid)
+{
+    JoystickImpl* This = (JoystickImpl*)iface;
+
+    TRACE("(this=%p,%p,%s)\n", This, pdei, _dump_dinput_GUID(guid));
+
+#ifdef HAVE_STRUCT_FF_EFFECT_DIRECTION
+    return linuxinput_get_info_A(This->joyfd, guid, pdei); 
+#else
+    return DI_OK;
+#endif
+}
+
+static HRESULT WINAPI JoystickWImpl_GetEffectInfo(LPDIRECTINPUTDEVICE8W iface,
+                                                  LPDIEFFECTINFOW pdei,
+                                                  REFGUID guid)
+{
+    JoystickImpl* This = (JoystickImpl*)iface;
+            
+    TRACE("(this=%p,%p,%s)\n", This, pdei, _dump_dinput_GUID(guid));
+        
+#ifdef HAVE_STRUCT_FF_EFFECT_DIRECTION
+    return linuxinput_get_info_W(This->joyfd, guid, pdei);
+#else
+    return DI_OK;
+#endif
+}
+
+/*******************************************************************************
+ *      GetForceFeedbackState - Get information about the device's FF state 
+ */
+static HRESULT WINAPI JoystickAImpl_GetForceFeedbackState(
+	LPDIRECTINPUTDEVICE8A iface,
+	LPDWORD pdwOut)
+{
+    JoystickImpl* This = (JoystickImpl*)iface;
+
+    TRACE("(this=%p,%p)\n", This, pdwOut);
+
+    (*pdwOut) = 0;
+
+#ifdef HAVE_STRUCT_FF_EFFECT_DIRECTION
+    /* DIGFFS_STOPPED is the only mandatory flag to report */
+    if (This->ff_state == FF_STATUS_STOPPED)
+	(*pdwOut) |= DIGFFS_STOPPED;
+#endif
+
+    return DI_OK;
+}
+
+/*******************************************************************************
+ *      SendForceFeedbackCommand - Send a command to the device's FF system
+ */
+static HRESULT WINAPI JoystickAImpl_SendForceFeedbackCommand(
+	LPDIRECTINPUTDEVICE8A iface,
+	DWORD dwFlags)
+{
+    JoystickImpl* This = (JoystickImpl*)iface;
+    TRACE("(this=%p,%ld)\n", This, dwFlags);
+
+#ifdef HAVE_STRUCT_FF_EFFECT_DIRECTION
+    if (dwFlags == DISFFC_STOPALL) {
+	/* Stop all effects */
+	EffectListItem* itr = This->top_effect;
+	while (itr) {
+	    itr->ref->lpVtbl->Stop(itr->ref);
+	    itr = itr->next;
+	}
+    } else if (dwFlags == DISFFC_RESET) {
+	/* Stop, unload, release and free all effects */
+	/* This returns the device to its "bare" state */
+	while (This->top_effect) {
+	    EffectListItem* temp = This->top_effect; 
+	    temp->ref->lpVtbl->Stop(temp->ref);
+	    temp->ref->lpVtbl->Unload(temp->ref);
+	    temp->ref->lpVtbl->Release(temp->ref);
+	    This->top_effect = temp->next; 
+	    free(temp);
+	}
+    } else if (dwFlags == DISFFC_PAUSE || dwFlags == DISFFC_CONTINUE) {
+	FIXME("No support for Pause or Continue in linux\n");
+    } else if (dwFlags == DISFFC_SETACTUATORSOFF 
+		|| dwFlags == DISFFC_SETACTUATORSON) {
+	FIXME("No direct actuator control in linux\n");
+    } else {
+	FIXME("Unknown Force Feedback Command!\n");
+	return DIERR_INVALIDPARAM;
+    }
+    return DI_OK;
+#else
+    return DIERR_UNSUPPORTED;
+#endif
+}
+
+/*******************************************************************************
+ *      EnumCreatedEffectObjects - Enumerate all the effects that have been
+ *		created for this device.
+ */
+static HRESULT WINAPI JoystickAImpl_EnumCreatedEffectObjects(
+	LPDIRECTINPUTDEVICE8A iface,
+	LPDIENUMCREATEDEFFECTOBJECTSCALLBACK lpCallback,
+	LPVOID pvRef,
+	DWORD dwFlags)
+{
+    /* this function is safe to call on non-ff-enabled builds */
+
+    JoystickImpl* This = (JoystickImpl*)iface;
+    EffectListItem* itr = This->top_effect;
+    TRACE("(this=%p,%p,%p,%ld)\n", This, lpCallback, pvRef, dwFlags);
+
+    if (!lpCallback)
+	return DIERR_INVALIDPARAM;
+
+    if (dwFlags != 0)
+	FIXME("Flags specified, but no flags exist yet (DX9)!");
+
+    while (itr) {
+	(*lpCallback)(itr->ref, pvRef);
+	itr = itr->next;
+    }
+
+    return DI_OK;
+}
+
 static const IDirectInputDevice8AVtbl JoystickAvt =
 {
 	IDirectInputDevice2AImpl_QueryInterface,
@@ -1124,12 +1489,12 @@ static const IDirectInputDevice8AVtbl JoystickAvt =
 	IDirectInputDevice2AImpl_GetDeviceInfo,
 	IDirectInputDevice2AImpl_RunControlPanel,
 	IDirectInputDevice2AImpl_Initialize,
-	IDirectInputDevice2AImpl_CreateEffect,
-	IDirectInputDevice2AImpl_EnumEffects,
-	IDirectInputDevice2AImpl_GetEffectInfo,
-	IDirectInputDevice2AImpl_GetForceFeedbackState,
-	IDirectInputDevice2AImpl_SendForceFeedbackCommand,
-	IDirectInputDevice2AImpl_EnumCreatedEffectObjects,
+	JoystickAImpl_CreateEffect,
+	JoystickAImpl_EnumEffects,
+	JoystickAImpl_GetEffectInfo,
+	JoystickAImpl_GetForceFeedbackState,
+	JoystickAImpl_SendForceFeedbackCommand,
+	JoystickAImpl_EnumCreatedEffectObjects,
 	IDirectInputDevice2AImpl_Escape,
 	JoystickAImpl_Poll,
 	IDirectInputDevice2AImpl_SendDeviceData,
@@ -1166,12 +1531,12 @@ static const IDirectInputDevice8WVtbl JoystickWvt =
 	IDirectInputDevice2WImpl_GetDeviceInfo,
 	XCAST(RunControlPanel)IDirectInputDevice2AImpl_RunControlPanel,
 	XCAST(Initialize)IDirectInputDevice2AImpl_Initialize,
-	XCAST(CreateEffect)IDirectInputDevice2AImpl_CreateEffect,
-	IDirectInputDevice2WImpl_EnumEffects,
-	IDirectInputDevice2WImpl_GetEffectInfo,
-	XCAST(GetForceFeedbackState)IDirectInputDevice2AImpl_GetForceFeedbackState,
-	XCAST(SendForceFeedbackCommand)IDirectInputDevice2AImpl_SendForceFeedbackCommand,
-	XCAST(EnumCreatedEffectObjects)IDirectInputDevice2AImpl_EnumCreatedEffectObjects,
+	XCAST(CreateEffect)JoystickAImpl_CreateEffect,
+	JoystickWImpl_EnumEffects,
+	JoystickWImpl_GetEffectInfo,
+	XCAST(GetForceFeedbackState)JoystickAImpl_GetForceFeedbackState,
+	XCAST(SendForceFeedbackCommand)JoystickAImpl_SendForceFeedbackCommand,
+	XCAST(EnumCreatedEffectObjects)JoystickAImpl_EnumCreatedEffectObjects,
 	XCAST(Escape)IDirectInputDevice2AImpl_Escape,
 	XCAST(Poll)JoystickAImpl_Poll,
 	XCAST(SendDeviceData)IDirectInputDevice2AImpl_SendDeviceData,